Spring Boot-使用 AOP 切片拦截请求

AOP

AOP即面向切面编程,在一个web服务中,比如说我们需要每次记录请求的处理时间,或者要记录日志等等功能的时候,如果要把这些写在代码中的话,就很麻烦,产生很多相同的代码,这时候就可以使用AOP, 通过对现有的程序定义一个切入点,然后在其前后切入不同的执行内容,下面来看一个简单的例子,就比如要记录每个请求的处理耗时.

AOP示例

首先需要引入AOP的依赖,如下:

1
2
3
4
5
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后我们来创建一个类 TimerAspect,然后加入 @Aspect注解 表示这是一个切面类,还有一个是 @Component 交给Spring容器去管理

定义好类之后,还需要一个具体处理逻辑的方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Component
@Slf4j
public class TimerAspect {

@Around("execution(* com.security.example.demo.controller.TestController.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// 拿到请求的所有参数
Object[] params = pjp.getArgs();

// 记录开始时间
long startTime = System.currentTimeMillis();

// 执行controller中具体的业务逻辑
Object object = pjp.proceed();

// 方法执行完成打印结束时间
log.info("方法执行结束,耗时:{}ms", System.currentTimeMillis() - startTime);

// 返回的是具体controller的返回值
return object;
}

}

这里首先来看一下方法上面这个注解,一共有以下几种:

  • @Before: 在切入点开始处执行
  • @After: 在切入点的结尾处执行
  • @AfterReturning: 在切入点方法return之后执行(可以对返回值做一些处理)
  • @Around: 可以在切入点前后都执行内容,可以自己控制何时执行切入点自身的内容
  • @AfterThrowing: 用来处理当切入内容部分抛出异常之后的处理逻辑

然后再来看一下我们上面用的是@Around,这个也是最常用的,方法里面可以通过 ProceedingJoinPoint 对象,拿到方法的参数等,然后可以通过pjp.proceed();执行切入点自身的内容,并且拿到返回值,方法最后再把返回值返回去就ok了

这里还有一个是注解后面定义的切入点,在上面的例子中execution(* com.security.example.demo.controller.TestController.*(..)),就表示 com.security.example.demo.controller.TestController 这个类里面的所有方法 (..) 表示任意的参数,可以根据自己的实际业务调整

测试

我们现在有这样一个接口:

1
2
3
4
5
@GetMapping("{id:\\d+}")
public User getInfo(@PathVariable Long id){
log.info("查询id是[{}]的数据.....", id);
return new User();
}

然后启动请求接口,看一下控制台的输出日志:
image

AOP中的同步问题

我们上面用的是@Around实现了打印请求耗时,当然也可以通过使用@doBefore@doAfterReturning 这两个方法来实现, 这样的话就需要在类里面定义一个成员变量,这里就会有线程同步的问题, 可以通过使用 ThreadLocal 解决,但是会增加内存开销,所以还是直接用 @Around 方便

AOP切面的优先级

假设我们有多个切面,比如一个切面先做参数处理, 另一个做返回处理, 这里就需要规定每个切面的执行顺序了, 可以通过 @Order(i) 这个注解来设置切面的优先级, i的值越小,优先级越高

举个例子, 一个是处理参数的切面 设置@Order(2), 一个是记录耗时的切面,设置@Order(1), 这里记录耗时的切面优先级就高,这时候的执行顺序是这样的:

  • @Before 中优先执行 @Order(1) 的内容, 再执行 @Order(2) 的内容
  • @After@AfterReturning 中优先执行 @Order(2) 的内容,再执行 @Order(1) 的内容

总结一下就是:

  • 在切入点前的操作,按order的值由小到大执行
  • 在切入点后的操作,按order的值由大到小执行