spring框架最大的特点之一就是AOP(Aspect Oriented Programming)。也就是面向切面编程。

可以在对原本的逻辑不产生修改的情况下,对原来的函数进行增强。大大降低代码的耦合度,提高程序的可重用性。可以说是面向对象思想下的一个衍生,但是在某些情况下,要优于面向对象。比如日志、鉴权等情况下,使用AOP,可以很轻松的进行实现。本文对系统访问日志采用AOP的方式进行开发。

引入依赖

不论是spring boot 还是 springMVC都需要引用下面的两个包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.6</version>
</dependency>

在spring boot中还需要引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在springMVC中,需要引入

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>

创建切面类

一个切面类需要定义一个切点(一般情况下是某个函数,也可能是某个属性等)。再根据切点进行增强处理。对于系统访问日志来说,我做了如下两个后置增强

@Aspect
@Component
public class LogAspect {

	private HttpServletRequest request;

	//创建切点。所有的控制器
	@Pointcut("execution(* com.bc.center.controller.*.*Controller.*(..))")
	public void cutPoint(){
	}

	/**
	 * 前置赋值
	 */
	@Before("cutPoint()")
	public void beforeController(){
	    request = getRequest();
	}

	/**
	 * 请求成功记录
	 */
	@AfterReturning("cutPoint()")
	public void requestSuccess(){
	    SystemLogDTO systemLogDTO = new SystemLogDTO();
	    systemLogDTO.setIsError(0);
	    saveLog(systemLogDTO);
	}

	/**
	 * 请求记录失败
	 */
	@AfterThrowing(value = "cutPoint()",throwing = "e")
	public void requestFail(Throwable e){
	    SystemLogDTO systemLogDTO = new SystemLogDTO();
	    systemLogDTO.setIsError(1);
	    //获取异常位置
	    Map errorDetail = new HashMap(2);
	    StackTraceElement[] stack = e.getStackTrace();
	    for (StackTraceElement stackTraceElement : stack){
	        String className = stackTraceElement.getClassName();
	        int lineNumber = stackTraceElement.getLineNumber();
	        if(className.contains("com.bc") && lineNumber > 0){
	            //存在当前项目目录异常,记录异常类和行数
	            errorDetail.put("className",className);
	            errorDetail.put("lineNumber",lineNumber);
	        }
	    }
	    if(errorDetail.size() > 0){
	        errorDetail.put("message",e.getMessage());
	        //记录成功,保存到对象中
	        systemLogDTO.setErrorDetail(JsonUtil.toJSONString(errorDetail));
	    }

	    //此位置可以根据需求保存到数据库或文件中
	    saveLog(systemLogDTO);
	}
}

DTO类的属性如下:

public class SystemLogDTO {

    /**
     * 请求url
     */
    private String uri;

    /**
     * 请求方式
     */
    private String requestType;

    /**
     * 请求参数
     */
    private String params;

    /**
     * 是否是抛出异常时的请求
     */
    private Integer isError;

    /**
     * 存在异常时,记录应用内的
     */
    private String errorDetail;

}

@Aspect:声明当前类是切面类。

@Pointcut:设置切点。通过execution关键字进行设置

  • com.bc.center.controller..Controller.(..))
    第一个 * 参数是返回值 *表示任何返回值
    第二个 com.bc.center.controller.
    .Controller. 是匹配得切点路径
    第三个 (..) 是请求参数。 ..表示任意参数

@Before:在切点之前执行
@After:在切点之后执行
@AfterReturn:切点正常返回时执行
@AfterThrowing:切点抛出异常时执行

这里我使用了@AfterReturn@AfterThrowing,为了区分记录正常返回和异常返回的日志。

设置切面启动开关

按照上述操作,设置好了定义好了切面类之后,还需要在入口或配置文件中进行设置开启。

spring boot

在入口文件中增加@EnableAspectJAutoProxy(proxyTargetClass = true)注解

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class,org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

spring mvc

springmvc需要在spring-mvc文件中增加配置进行开启

<aop:aspectj-autoproxy proxy-target-class="true"/>

记得在命名空间中,加入对应的地址

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

小结

以往采用的方案是以拦截器的方式进行记录。需要配置拦截器以及放行的url,相对要繁琐一些。使用切面的话,可以以所有的控制器为切点,只需要一个切面类,就可以实现日志的访问记录,最重要的是对原来的逻辑没有任何入侵。