找回密码
 立即注册
首页 业界区 业界 Spring AOP 应用

Spring AOP 应用

株兆凝 2025-6-2 00:31:04
Spring AOP 应用

1. 介绍

AOP:面向切面编程,对面向对象编程的一种补充。
AOP可以将一些公用的代码,自然的嵌入到指定方法的指定位置。
比如:
1.png

如上图,我们现在有四个方法,我们想在每个方法执行一开始,输出一个日志信息。但是这样做很麻烦,如果有100个、1000个方法,工作量会很大,而且难以维护。这时候就可以通过AOP进行解决。
2.png

2. 案例实战

2.1 需求分析及环境搭建

环境:SpringBoot + SpringBoot Web + SpringBoot AOP。
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     spring-boot-starter</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>org.springframework.boot</groupId>
  7.     spring-boot-starter-test</artifactId>
  8.     <scope>test</scope>
  9. </dependency>
  10. <dependency>
  11.     <groupId>org.springframework.boot</groupId>
  12.     spring-boot-starter-web</artifactId>
  13. </dependency>
  14. <dependency>
  15.     <groupId>org.springframework.boot</groupId>
  16.     spring-boot-starter-aop</artifactId>
  17. </dependency>
  18. <dependency>
  19.     <groupId>org.projectlombok</groupId>
  20.     lombok</artifactId>
  21. </dependency>
复制代码
目标:控制器业务方法,统一进行日志输出。
新建User类,包含id和name属性。
新建UserController
  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4.     @GetMapping("/list")
  5.     public List<User> list(){
  6.         return Arrays.asList(
  7.                 new User(1,"张三"),
  8.                 new User(2,"李四"),
  9.                 new User(3,"王五")
  10.         );
  11.     }
  12.     @GetMapping("/getById/{id}")
  13.     public User getById(@PathVariable("id") Integer id){
  14.         return new User(id,"张三");
  15.     }
  16.     @GetMapping("/deleteById/{id}")
  17.     public boolean deleteById(@PathVariable("id") Integer id){
  18.         return true;
  19.     }
  20. }
复制代码
此时,我们的目标就是使用AOP的方式,给这个list、deleteById和getById方法加上日志。
日志要包括调用方法的名称、返回值以及参数列表。
2.3 AOP实现

1. 首先我们要让AOP知道哪些方法需要被AOP处理 -> 通过注解方式进行处理
  1. // 定义一个注解,来标记需要添加日志的方法
  2. @Target(ElementType.METHOD)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. public @interface LogAnnotation {
  6.     String value() default "";
  7. }
复制代码
定义好注解后,给需要使用日志的方法添加注解,如:
  1. @LogAnnotation("查询用户")   // 标记目标方法
  2. @GetMapping("/getById/{id}")
  3. public User getById(@PathVariable("id") Integer id){
  4.     return new User(id,"张三");
  5. }
复制代码
2. 实现切面任务
新建LogAspect类,这就是生成切面对象的类。我们需要用@Component注解进行标注,交给IOC容器进行管理。此外,我们要用@Aspect注解标注其为一个切面。
然后,我们要将这个切面与我们刚刚标注的@LogAnnotation注解建立联系,让切面知道从哪个位置进行切入。实现的方法为,新建一个方法,然后给这个方法添加@Pointcut("@annotation(自定义注解的全类名)")。这样就成功建立的联系。
确定切入点后,我们就可以写切面的实际任务了。新建一个方法around。此时,我们要将确定切点的方法与切面实际处理任务的方法进行关联。实现的方法为,给实际处理任务的方法添加@Around("标记切点的方法名()")注解。
此时,我们只有一个around方法,要用这一个方法对list、getById、deleteById三个方法进行处理。那么around方法如何分辨这三个不同的方法呢?这时就需要用到一个连接点对象ProceedingJoinPoint。around的返回值为object类型,其要返回所切入方法的返回值。
然后,就可以实现日志输出功能了。
  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class LogAspect {
  5.     @Pointcut("@annotation(cn.codewei.aopstudy.annotation.LogAnnotation)")
  6.     public void logPointCut() {
  7.     }
  8.     @Around("logPointCut()")
  9.     public Object around(ProceedingJoinPoint point) throws Throwable {
  10.         // 方法名称
  11.         String name = point.getSignature().getName();
  12.         // 通过反射 获取注解中的内容
  13.         MethodSignature signature = (MethodSignature) point.getSignature();
  14.         Method method = signature.getMethod();
  15.         LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
  16.         String value = annotation.value();
  17.         // 输出日志
  18.         log.info("方法名称:{}, 方法描述: {}, 返回值: {}, 参数列表: {}", name, value, point.proceed(), point.getArgs());
  19.         // 返回切入方法的返回值
  20.         return point.proceed();
  21.     }
  22. }
复制代码
@Around、@Before、@After区别

  • @Before前置通知,是在所拦截方法执行之前执行一段逻辑,返回值类型为void。
  • @After 后置通知,是在所拦截方法执行之后执行一段逻辑,返回值类型为void。
  • @Around 环绕通知,是可以同时在所拦截方法的前后执行一段逻辑,用这个注解的方法入参传的是ProceedingJoinPoint,返回结果类型为Object,返回结果为ProceedingJoinPoint对象的.proceed();
3. @Pointcut


  • 使用within表达式匹配
​        匹配com.leo.controller包下所有的类的方法
  1. @Pointcut("within(com.leo.controller..*)")
  2. public void pointcutWithin(){
  3. }
复制代码

  • this匹配目标指定的方法,此处就是HelloController的方法
  1. @Pointcut("this(com.leo.controller.HelloController)")
  2. public void pointcutThis(){
  3. }
复制代码

  • target匹配实现UserInfoService接口的目标对象
  1. @Pointcut("target(com.leo.service.UserInfoService)")
  2. public void pointcutTarge(){
  3. }
复制代码

  • bean匹配所有以Service结尾的bean里面的方法
    注意:使用自动注入的时候默认实现类首字母小写为bean的id
  1. @Pointcut("bean(*ServiceImpl)")
  2. public void pointcutBean(){
  3. }
复制代码

  • args匹配第一个入参是String类型的方法
  1. @Pointcut("args(String, ..)")
  2. public void pointcutArgs(){
  3. }
复制代码

  • @annotation匹配是@Controller类型的方法
  1. @Pointcut("@annotation(org.springframework.stereotype.Controller)")
  2. public void pointcutAnnocation(){
  3. }
复制代码

  • @within匹配@Controller注解下的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.CLASS)
  1. @Pointcut("@within(org.springframework.stereotype.Controller)")
  2. public void pointcutWithinAnno(){
  3. }
复制代码

  • @target匹配的是@Controller的类下面的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.RUNTIME)
  1. @Pointcut("@target(org.springframework.stereotype.Controller)")
  2. public void pointcutTargetAnno(){
  3. }
复制代码

  • @args匹配参数中标注为@Sevice的注解的方法
  1. @Pointcut("@args(org.springframework.stereotype.Service)")
  2. public void pointcutArgsAnno(){
  3. }
复制代码

  • 使用excution表达式
  1. @Pointcut(value = "execution(public * com.leo.controller.HelloController.hello*(..))")
  2. public void pointCut() {
  3. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册