SpringBoot 使用 AOP 统一处理日志
🏷️ Spring Boot
POM 文件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP 代码:
//申明是个切面
@Aspect
//申明是个 spring 管理的 bean
@Component
@Order(1)
public class AspectDemo {
private Logger log = Logger.getLogger(getClass());
private Gson gson = new Gson();
//申明一个切点 里面是 execution 表达式
@Pointcut("execution(public * com.example.DemoApplication.*(..))")
private void controllerAspect(){}
//请求 method 前打印内容
@Before(value = "controllerAspect()")
public void methodBefore(JoinPoint joinPoint){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//打印请求内容
log.info("===============请求内容===============");
log.info("请求地址:"+request.getRequestURL().toString());
log.info("请求方式:"+request.getMethod());
log.info("请求类方法:"+joinPoint.getSignature());
log.info("请求类方法参数:"+ Arrays.toString(joinPoint.getArgs()));
log.info("===============请求内容===============");
}
//在方法执行完结后打印返回内容
@AfterReturning(returning = "o",pointcut = "controllerAspect()")
public void methodAfterReturing(Object o ){
log.info("--------------返回内容----------------");
log.info("Response 内容:"+gson.toJson(o));
log.info("--------------返回内容----------------");
}
}
类注解:
@Aspect
将一个类定义为一个切面类@order(i)
标记切面类的处理优先级,i
值越小,优先级别越高。PS:可以注解类,也能注解到方法上。
方法注解:
@Pointcut
定义一个方法为切点里面的内容为一个表达式,下面详细介绍@Before
在切点前执行方法,内容为指定的切点@After
在切点后,return
前执行@AfterReturning
在切入点,return
后执行,如果想对某些方法的返回参数进行处理,可以在这操作@Around
环绕切点,在进入切点前,跟切点后执行@AfterThrowing
在切点后抛出异常进行处理@order(i)
标记切点的优先级,i
越小,优先级越高
@Pointcut
注解组合使用:
上面代码中, 我们定义了一个切点, 该切点只进行处理指定路径的:
@Pointcut("execution(public * com.example.DemoApplication.*(..))") private void controllerAspect(){}
现在, 我们在定义一个处理其他路径下的切点:
@Pointcut("execution(public * com.demo.*.*(..))") private void controllerDemo(){}
以上切点, 都是分别处理不同的内容, 如果我们需要一个切点来处理他们两者, 我们可以这么配置:
@Pointcut(value = "controllerAspect()||controllerDemo()") private void all(){}
在 @Pointcut
注解内, 直接引用其它被 @Pointcut
注解过的方法名称, 这样, 该切点就可以处理两个路径下的方法
@Pointcut
注解中的 execution
表达式: public * com.demo.*.*(..)
第一个
public
表示方法的修饰符,可以用*
代替第一个
*
表示 返回值,*
代表所有com.demo.*
包路径,.*
表示路径下的所有包第三个
.*
表示路径下,所有包下的所有类的方法(..)
表示不限方法参数
关于 @Order(i)
注解的一些注意事项:
注解类,
i
值是:值越小,优先级越高注解方法,分两种情况
注解的是
@Before
是i
值越小,优先级越高注解的是
@After
或者@AfterReturning
中,i
值越大,优先级越高
总结两者的概括就是:
在切入点前的操作,按
order
的值由小到大执行在切入点后的操作,按
order
的值由大到小执行延伸
有看官可能会问, 如果我要打印请求从进来,到结束,所需要的时间,定义一个成员变量,用来统计时间,同时给 @Before
跟 @AfterReturning
访问,可能会有同步的问题。所以我们引用一个 ThreadLocal<Long>
指定泛型的对象,在 @Before
记录请求的时间,在 @AfterReturning
扣除记录的时间,就是消耗的时间。
代码如下:
//申明是个切面
@Aspect
//申明是个 spring 管理的 bean
@Component
@Order(1)
public class AspectDemo {
private Logger log = Logger.getLogger(getClass());
private Gson gson = new Gson();
ThreadLocal<Long> startTime = new ThreadLocal<Long>();
//申明一个切点 里面是 execution 表达式
@Pointcut("execution(public * com.example.DemoApplication.*(..))")
private void controllerAspect() {
}
//请求 method 前打印内容
@Before(value = "controllerAspect()")
public void methodBefore(JoinPoint joinPoint) {
startTime.set(System.currentTimeMillis());
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//打印请求内容
log.info("===============请求内容===============");
log.info("请求地址:" + request.getRequestURL().toString());
log.info("请求方式:" + request.getMethod());
log.info("请求类方法:" + joinPoint.getSignature());
log.info("请求类方法参数:" + Arrays.toString(joinPoint.getArgs()));
log.info("===============请求内容===============");
}
//在方法执行完结后打印返回内容
@AfterReturning(returning = "o", pointcut = "controllerAspect()")
public void methodAfterReturing(Object o) {
log.info("--------------返回内容----------------");
log.info("Response 内容:" + gson.toJson(o));
log.info("--------------返回内容----------------");
log.info("请求处理时间为:"+(System.currentTimeMillis() - startTime.get()));
}
}