前言:本文不介绍 AOP 的基本概念、动态代理方式实现 AOP,以及 Spring 框架去实现 AOP。本文重点介绍 Spring Boot 项目中如何使用 AOP,也就是实际项目开发中如何使用 AOP 去实现相关功能。
如果有需要了解 AOP 的概念、动态代理实现 AOP 的,请查看我的另外一篇文章:
一篇文章带你深入了解 AOP
正文开始:
1、之前介绍的实现 AOP 的方式中是有 XML 文件设置。但在 Spring Boot 中,没有 XML 文件,那怎么设置 AOP?
2、实际应用中:MVC三层架构,现需要在控制器中统一进行日志的输出(有各种各样的控制器),那怎么实现?(也就是说实际应用中如何实现?)
1、Cal 接口、CalIml 接口实现类 还是跟 Spring框架实现AOP 中一样。之前的两个依赖不要,添加这个依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
2、控制器中需要打印日志,比如:调控制器中的哪个方法(接口)、方法有哪些参数、返回值。当然,你肯定可以在每个方法中写,但显然这不现实。这里用 AOP 方法将打印日志抽离出来,然后需要的时候嵌入到每个方法中
//以下代码仅是为了测试,代码不规范,请注意。
@RestController
@RequestMapping("/aop")
public class AopSpringBootTestHandler {
@GetMapping("/findAll")
public List<Account> findAll() {
return Arrays.asList(new Account(1, "张三", 25), new Account(2, "李四", 26));
}
@GetMapping("/findById/{id}")
public Account findById(@PathVariable Integer id) {
return new Account(1, "张三", 25);
}
@GetMapping("/add")
public boolean add() {
return true;
}
@GetMapping("/update")
public boolean update() {
return true;
}
@GetMapping("/delete/{id}")
public boolean delete(@PathVariable Integer id) {
return true;
}
}
3、自定义注解(比如:创建一个 annotation 的包,然后创建一个 LogAnnotation(可自定义,比如这里是打印日志的注解))
为什么要这个自定义注解?----> 首先你要让 AOP 知道你调了哪些方、哪些方法需要让 AOP 进行处理,所以就要让 AOP 知道这些方法,怎么知道? ----> 通过自定义注解
其次并不是所有的方法都需要进行 AOP 处理,所以通过 注解 标记。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
//使用注解时,给注解中添加值
String value() default "";
}
说明:@Target、@Retention、@Documented 都是元注解(描述注解的注解)
@Target
表示该注解的使用目标,其中 @Target(ElementType.METHOD)
表示只能使用在方法上:
@Retention(RetentionPolicy.RUNTIME)
表示在运行时使用该注解。
@interface
标志这是一个注解。
String value() default "";
固定写法,表示在使用注解的时候,可以添加值,默认为空:
**使用自定义注解:**需要进行 AOP 处理的方法,标记即可
4、标记完后,同理,需要切面类,执行非业务代码(比如这里是输出日志):
@Component
@Aspect
public class CreateAspectUtil {
/*
我们利用自定义注解标记了哪些方法需要进行AOP处理,那真正需要处理的时候,怎么找到这些标记?
通过 Pointcut(切入点)找到这些标记
所以这个方法就是为了找到标记,必须是空方法体
*/
@Pointcut("@annotation(com.example.test.aopspringboottest.annotation.LogAnnotation)")
public void logPointCut() {
}
/*
找到标记后,怎么执行日志?
1、跟之前一样,有@Before前置通知、@After后置通知等等
@Around注解就是将各种通知统一到一起,然后将找标记的方法放进去
2、连接点 ProceedingJoinPoint 是 joinPoint 的子接口,
只是ProceedingJoinPoint中有这个proceed()方法,为了获取方法的返回值
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String methodArgs = Arrays.toString(joinPoint.getArgs());
System.out.println(methodName + "方法的参数是:" + methodArgs);
return joinPoint.proceed(); //返回目标方法(也就是业务代码)中的返回值
}
}
演示: 启动启动类后,直接访问:
没加标记的就不会打印日志。
注意:现在还要求输出自定义注解中的值, 怎么办?----> 通过反射获取注解即可
既然获取注解,注解是添加在方法上的,所以先通过反射获取方法,怎么获取?----> 只有一个连接点,所以还是通过连接点:
@Around("logPointcut()")
public Object around(ProceedingJointPoint jointPoint) throws Throwable {
//通过连接点获取到方法的签名
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
//通过方法签名获取到方法
Method method = methodSignature.getMethod();
//拿到方法后,拿注解:把自定义注解的运行时类给它
LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
if (annotation != null) {
//拿注解中的值,通过里面的value方法
String value = annotation.value();
//这个value就是注解里面的内容
System.out.println(value);
}
}