Spring AOP 应用

笔记哥 / 03-27 / 36点赞 / 0评论 / 917阅读
# Spring AOP 应用 ## 1. 介绍 AOP:面向切面编程,对面向对象编程的一种补充。 AOP可以将一些公用的代码,自然的嵌入到指定方法的指定位置。 比如: ![image-20240801160803027](https://cdn.res.knowhub.vip/c/2503/27/1cdd54b5.png?G1AAAER17rw2umkU%2fT7CwUzMIBkc2oPYIhFWMvlS25wj7vQjm6FycY%2bg6mTF2299cXmfb3kN7hWRFLojQ%2bRAxgY9eXgD) 如上图,我们现在有四个方法,我们想在每个方法执行一开始,输出一个日志信息。但是这样做很麻烦,如果有100个、1000个方法,工作量会很大,而且难以维护。这时候就可以通过AOP进行解决。 ![image-20240801161149526](https://cdn.res.knowhub.vip/c/2503/27/7984c785.png?G1AAAMTc2vO1tRsm2e8nPlwQU5g%2bqgexRSKsZPKltrVm3MePbBZF4RFBzcmq99%2fG5vo%2b3%2fYW3CsiqWiSS4AMpHJq5ukd) ## 2. 案例实战 ### 2.1 需求分析及环境搭建 环境:SpringBoot + SpringBoot Web + SpringBoot AOP。 ```csharp org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.projectlombok lombok ``` 目标:控制器业务方法,统一进行日志输出。 新建`User`类,包含`id`和`name`属性。 新建`UserController` ```csharp @RestController @RequestMapping("/user") public class UserController { @GetMapping("/list") public List list(){ return Arrays.asList( new User(1,"张三"), new User(2,"李四"), new User(3,"王五") ); } @GetMapping("/getById/{id}") public User getById(@PathVariable("id") Integer id){ return new User(id,"张三"); } @GetMapping("/deleteById/{id}") public boolean deleteById(@PathVariable("id") Integer id){ return true; } } ``` 此时,我们的目标就是使用AOP的方式,给这个`list`、`deleteById`和`getById`方法加上日志。 日志要包括调用方法的名称、返回值以及参数列表。 ### 2.3 AOP实现 **1. 首先我们要让AOP知道哪些方法需要被AOP处理 -> 通过注解方式进行处理** ```csharp // 定义一个注解,来标记需要添加日志的方法 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { String value() default ""; } ``` 定义好注解后,给需要使用日志的方法添加注解,如: ```csharp @LogAnnotation("查询用户") // 标记目标方法 @GetMapping("/getById/{id}") public User getById(@PathVariable("id") Integer id){ return new User(id,"张三"); } ``` **2. 实现切面任务** 新建`LogAspect`类,这就是生成切面对象的类。我们需要用`@Component`注解进行标注,交给IOC容器进行管理。此外,我们要用`@Aspect`注解标注其为一个切面。 然后,我们要将这个切面与我们刚刚标注的`@LogAnnotation`注解建立联系,让切面知道从哪个位置进行切入。实现的方法为,新建一个方法,然后给这个方法添加`@Pointcut("@annotation(自定义注解的全类名)")`。这样就成功建立的联系。 确定切入点后,我们就可以写切面的实际任务了。新建一个方法`around`。此时,我们要将确定切点的方法与切面实际处理任务的方法进行关联。实现的方法为,给实际处理任务的方法添加`@Around("标记切点的方法名()")`注解。 此时,我们只有一个`around`方法,要用这一个方法对`list`、`getById`、`deleteById`三个方法进行处理。那么`around`方法如何分辨这三个不同的方法呢?这时就需要用到一个连接点对象`ProceedingJoinPoint`。`around`的返回值为object类型,其要返回所切入方法的返回值。 然后,就可以实现日志输出功能了。 ```csharp @Aspect @Component @Slf4j public class LogAspect { @Pointcut("@annotation(cn.codewei.aopstudy.annotation.LogAnnotation)") public void logPointCut() { } @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { // 方法名称 String name = point.getSignature().getName(); // 通过反射 获取注解中的内容 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); LogAnnotation annotation = method.getAnnotation(LogAnnotation.class); String value = annotation.value(); // 输出日志 log.info("方法名称:{}, 方法描述: {}, 返回值: {}, 参数列表: {}", name, value, point.proceed(), point.getArgs()); // 返回切入方法的返回值 return point.proceed(); } } ``` > > > @Around、@Before、@After区别 > > > - @Before前置通知,是在所拦截方法执行之前执行一段逻辑,返回值类型为void。 > - @After 后置通知,是在所拦截方法执行之后执行一段逻辑,返回值类型为void。 > - @Around 环绕通知,是可以同时在所拦截方法的前后执行一段逻辑,用这个注解的方法入参传的是ProceedingJoinPoint,返回结果类型为Object,返回结果为ProceedingJoinPoint对象的.proceed(); > > ## 3. @Pointcut - *使用within表达式匹配* ​ 匹配com.leo.controller包下所有的类的方法 ```csharp @Pointcut("within(com.leo.controller..*)") public void pointcutWithin(){ } ``` - *this匹配目标指定的方法,此处就是HelloController的方法* ```csharp @Pointcut("this(com.leo.controller.HelloController)") public void pointcutThis(){ } ``` - *target匹配实现UserInfoService接口的目标对象* ```csharp @Pointcut("target(com.leo.service.UserInfoService)") public void pointcutTarge(){ } ``` - *bean匹配所有以Service结尾的bean里面的方法* 注意:使用自动注入的时候默认实现类首字母小写为bean的id ```csharp @Pointcut("bean(*ServiceImpl)") public void pointcutBean(){ } ``` - *args匹配第一个入参是String类型的方法* ```csharp @Pointcut("args(String, ..)") public void pointcutArgs(){ } ``` - *@annotation匹配是@Controller类型的方法* ```csharp @Pointcut("@annotation(org.springframework.stereotype.Controller)") public void pointcutAnnocation(){ } ``` - *@within匹配@Controller注解下的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.CLASS)* ```csharp @Pointcut("@within(org.springframework.stereotype.Controller)") public void pointcutWithinAnno(){ } ``` - *@target匹配的是@Controller的类下面的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.RUNTIME)* ```csharp @Pointcut("@target(org.springframework.stereotype.Controller)") public void pointcutTargetAnno(){ } ``` - *@args匹配参数中标注为@Sevice的注解的方法* ```csharp @Pointcut("@args(org.springframework.stereotype.Service)") public void pointcutArgsAnno(){ } ``` - *使用excution表达式* ```csharp @Pointcut(value = "execution(public * com.leo.controller.HelloController.hello*(..))") public void pointCut() { } ```