Spring Boot AOP 入门

本文展示了在 Spring Boot 项目中 AOP 的简单实现

Pom 依赖

为了实现 AOP,需要在项目中引入 aspectjweaveraspectjrt 两个依赖,其中 aspectjweaver 是 aspectj 的织入包, aspectjrt 是 aspectj 的运行时包

1
2
3
4
5
6
7
8
9
10
<!-- AOP 依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>

AOP 层类

在类上添加 @Aspect 注释表明这是一个 AOP 类,@Component 注释表明这个类可以作为一个 JavaBean 被注入到 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
25
26
27
28
29
30
31
32
package com.ziqian.yudao.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoginAspect {

@Pointcut("execution( * com.ziqian.yudao.controller.*.*(..)) && @annotation(Login)")
public void cut(){}

@Before("cut()")
public void before() {
System.out.println("已经记录下操作日志@Before 方法执行前");
}

@Around("cut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("已经记录下操作日志@Around 方法执行前");
Object object = proceedingJoinPoint.proceed();
System.out.println("已经记录下操作日志@Around 方法执行后");
return object;
}

@After("cut()")
public void after() {
System.out.println("已经记录下操作日志@After 方法执行后");
}

}

切入 AOP 代码需要一个切入点 PointCut,代码中 @Pointcut("execution( * com.ziqian.yudao.controller.*.*(..)) && @annotation(Login)") 声明的切入点是 com.ziqian.yudao.controller 下面的类中的所有带有 @Login 注释的方法。也就是 AOP 代码会被插入到满足上述条件的方法中。引入注解作为触发 AOP 的条件后可以更方便地在需要的地方切入代码,声明注解的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
package com.ziqian.yudao.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Login {
boolean loginRequired() default true;
}

在切入代码时我们也需要控制代码插入的位置,为此 Spring 提供了 @Before@After@Around 三个注解来帮助控制代码插入的大致位置。其中:

  • @Before 在所拦截方法执行前执行一段逻辑
  • @After 在所拦截方法执行后执行一段逻辑
  • @Around 可以同时在所拦截方法前后执行一段逻辑
    同样是在拦截方法的前后,三个注解也存在优先关系。为了搞清楚三者的先后关系,实现一个 http 接口进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.ziqian.yudao.controller;

import com.ziqian.yudao.aop.Login;
import com.ziqian.yudao.service.UserService;
import com.ziqian.yudao.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
UserService userService;

/**
* 查询用户列表
*
* @return 用户列表
*/
@Login
@GetMapping("/list") // URL 修改成 /list
public List<UserVO> list() {
// 查询列表
UserVO user1 = new UserVO(); user1.setId(1L); user1.setUserName("yudaoyuanma");
UserVO user2 = new UserVO(); user2.setId(2L); user2.setUserName("woshiyutou");
UserVO user3 = new UserVO(); user3.setId(3L); user3.setUserName("chifanshuijiao");
List<UserVO> result = new ArrayList<>();
result.add(user1);
result.add(user2);
result.add(user3);
// 返回列表
return result;
}

}

最后命令行的打印结果为:

已经记录下操作日志@Around 方法执行前
已经记录下操作日志@Before 方法执行前
已经记录下操作日志@After 方法执行后
已经记录下操作日志@Around 方法执行后

可见 @Around 中的逻辑在最外层

需要注意的是,@Around 注解下的 around() 方法中,proceedingJoinPoint.proceed(); 相当于切入的 list() 方法的方法体。如果 around() 的返回值为空,list() 对应的 /list 接口的返回值也为空。所以一定要为 around() 方法设定一个返回值。其中 Object object = proceedingJoinPoint.proceed(); 中的 obejct 对象就是原接口返回的值。

参考

spring AOP @Around @Before @After 区别
SpringBoot—集成AOP详解(面向切面编程Aspect)