本文展示了在 Spring Boot 项目中 AOP 的简单实现
Pom 依赖
为了实现 AOP,需要在项目中引入 aspectjweaver
和 aspectjrt
两个依赖,其中 aspectjweaver
是 aspectj 的织入包, aspectjrt
是 aspectj 的运行时包
1 2 3 4 5 6 7 8 9 10
| <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;
@Login @GetMapping("/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)