Spring AOP 使用方法总结

时间:2024-10-30 17:25:30

AOP切面编程的最佳应用场景

  • 记录日志
  • 性能监控
  • 事务管理
  • 处理异常
  • 数据验证,验证传入参数的正确性(一般不用这个方法做,而是用拦截器)

spring提供了以下注解供开发者使用,编写AOP程序

  • @Aspect 申明切面
  • @Pointcut 切点,申明AOP的作用范围(如:一个类下所有方法,某个方法,带有指定注解的方法)
  • @Before 前置通知
  • @After 后置通知
  • @Around 环绕通知
  • @AfterThrowing 异常通知,当被切入点中代码执行异常时触发

实操

举个例子

  1. 定义
    @RestController
    @RequestMapping(path = {"test"})
    public class DemoController {
    
        @GetMapping(path = {"test01/{name}"})
        public ResponseEntity<String> test01(@PathVariable("name") String name) {
            return new ResponseEntity<>(name.toUpperCase(), HttpStatus.OK);
        }
    }
    
    如果我希望在上面方法使用AOP,定义一个类,实现如下
    @Slf4j
    @Aspect
    @Component
    public class AOPAction {
        // 定义切点,标记此切点在什么范围内此切面起作用
        @Pointcut("execution(* com.train.controller.DemoController.test02(..))")
        public void pointcut() {}
      
        //前置通知
        @Before(value = "pointcut()")
        public void before(JoinPoint joinPoint) {
            log.info("Before Method : {}...{}", joinPoint.getSignature().getName(), args[0].toString());
        }
        // 后置通知
        @After(value = "pointcut()")
        public void after(JoinPoint joinPoint) {
            log.info("After Method : {}...{}", joinPoint.getSignature().getName(), args[0].toString());
        }
        // 环绕通知
        @Around(value = "pointcut()")
        public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("Around 1 Method before: {}...", joinPoint.getSignature().getName());
            Object result = joinPoint.proceed();
            log.info("Around 1 Method after: {}...", joinPoint.getSignature().getName());
            return result;
        }
    }
    

切点执行范围

此章节介绍多种切点定义形式,开发中使用哪一种视情况而定

1.指定包下所有类的所有方法

应用在此包下所有类的所有方法上

// 包名:com.train.controller
@Pointcut("execution(* com.train.controller..*.*(..))")
public void pointcut() {}

2.指定类中所有方法

应用在指定类的所有方法上

// 全类名:com.train.controller.DemoController
@Pointcut("execution(* com.train.controller.DemoController.*(..))")
public void pointcut() {}

3.指定某个类中某个方法

应用在指定类的指定方法上,而此类中的其他方法不会被应用

// 全类名:com.train.controller.DemoController
@Pointcut("execution(* com.train.controller.DemoController.test01(..))")
public void pointcut() {}

4.1 按参数数量指定

AOP切入点表达式规范中,用*表示一个参数,用表示无固定个数参数

可以这么理解:把*理解成通配符,且仅匹配一个。

// 仅作用在com.train.controller包下所有类中仅有2个参数的方法上
@Pointcut(value = "execution(* com.train.controller..*.*(*,*))")
public void pointcut02(){}
// 仅作用在com.train.controller包下所有类中仅有5个参数的方法上
@Pointcut(value = "execution(* com.train.controller..*.*(*,*,*,*,*))")
public void pointcut02(){}

4.2 按参数注解指定

⚠️ 研究中,未完成


5.加有指定注解的方法

仅应用在使用了注解的方法上,可以使用自定义注解,也可以使用框架提供注解。此形式最灵活,推荐开发中使用

// 自定义注解
public @interface LogPointCut {}
// 加注解的方法
@GetMapping(path = {"test/{name}"})
@LogPointCut
public ResponseEntity<String> test(@PathVariable("name") String name) {
   return new ResponseEntity<>(name.toUpperCase(), HttpStatus.OK);
}
// 定义
@Pointcut("@annotation(com.train.annotation.LogPointCut)")
public void pointcut() {
}

6.组合条件【难点】

使用A符合 Spring AOP 的切入点表达式规范组合多个条件,组合条件最好用&&连接各个条件。用+匹配接口。用*匹配一个。用匹配无固定数

举例

// 仅作用在com.train.controller包下所有类中所有标记了@LogPointCut注解的方法上
@Pointcut(value = "execution(* com.train.controller..*.*(..)) && @annotation(com.train.spr.annotation.LogPointCut)")
public void pointcut02(){}
// 仅作用在com.train.spr.controller包下所有实现了MyInterface接口的类的方法上
@Pointcut(value = "execution(* (com.train.controller..* && com.train.spr.interfaces.MyInterface+).*(..))")
public void pointcut02(){}

// 仅作用在com.train.spr.controller包下所有实现了MyInterface接口和MyInterface2接口的类的方法上
@Pointcut(value = "execution(* (com.train.controller..* " +
        "&& com.train.spr.interfaces.MyInterface+ " +
        "&& com.train.spr.interfaces.MyInterface2+ ).*(..))")
public void pointcut02(){}

当然!也可以组合多个已定义的条件作为一个条件,例如

import org.aspectj.lang.annotation.Pointcut;

// 仅作用在com.train.spr.controller包下类的方法上
@Pointcut(value = "execution(* com.train.controller..*.*(..))")
public void pointcut01() {}

// 仅作用在实现了MyInterface接口的类的方法上
@Pointcut(value = "execution(* com.train.spr.interfaces.MyInterface+.*(..))")
public void pointcut02() {}

// 组合上述两个切入点的条件
@Pointcut(value = "pointcut02() && pointcut01()")
public void pointcut03() {}

AOP切入点表达式规范请查阅官网

获取参数

AOP的方法可以获取被切入方法的参数,但是修改这些参数并不会在被切入方法中生效。举个例子,方法A有一个前置通知方法B,且方法B获取方法A的参数,即便方法B中修改了参数值,方法A获取到的参数仍然不会改变,原来是怎样就是怎样

获取参数这块注意3点就够了。

  1. 前置通知/后置通知的参数是org.aspectj.lang.JoinPoint
  2. 环绕通知的参数是org.aspectj.lang.ProceedingJoinPoint
  3. 被切入点的参数信息都被封装到JoinPointProceedingJoinPoint中,直接从中获取即可【注意类型】

特殊的切入方法——异常通知

研究中,未完成