SpringBoot中AOP使用

  • A+

Spring AOP是纯java实现的,并不需要额外的编译,默认使用JDK动态代理,当然也可以通过配置使用CGLIB代理,Spring AOP默认仅支持方法层面的连接点。

1.引入AOP

  • SpringBoot中引入AOP
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>
    
  • 使用SpringBoot将不需要配置注解@EnableAspectJAutoProxy来启动AOP,SpringBoot自动配置spring.aop.auto添加了AOP支持,并且将代理方式的配置spring.aop.proxy-target-class修改为了CGLIB方式。

2.简单范例

  • 如下直观的展示了在SpringBoot中如何使用AOP

    @Aspect
    @Component
    public class AopConfig {
        @Pointcut("execution(* com.friends.springbootaop.AspectController.aspectTest())")
        public void pointcut(){}
    
        @Before("pointcut()")
        public void aspectTestAop() {
            System.out.println("do something");
        }
    }
    
    1. @Aspect声明AOP切入面
    2. @Component声明bean
    3. @Pointcut声明切入点,使用表达式标明拦截点
    4. @Before在请求到达方法前执行此方法逻辑
  • 如下是被AOP拦截的方法
    @RestController
    public class AspectController {
    
        @GetMapping
        public String aspectTest(){
            return "aspect";
        }
    }
    

3.@Pointcut切入点表达式一(execution的使用)

  • Spring AOP支持几种不同的切入点指示符
  • execution是spring AOP最常用的切入点指示器,以下是语法
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                    throws-pattern?)
    
    1. 除了返回类型模式,命名模式和参数模式以外的所有部分都是可选的
    2. modifiers-pattern访问修饰符public,private等
    3. ret-type-pattern返回类型,*是最常用的返回类型
    4. declaring-type-pattern声明类型模式(包加类),如果有声明类型模式,需要在声明模式和命名模式之间添加一个.进行链接
    5. name-pattern命名模式(方法名),*代表包含所有方法
    6. (param-pattern)参数匹配,()无参数,(..)任何参数,(*)任何单参数,(*,String)第一个参数为任何参数,第二个为String类型
    7. throws-pattern异常
  • 范例:
    1.任何公共方法 public 都被拦截

     execution(public * *(..))
    

    2.任何包含set字段的方法都被拦截

    execution(* set*(..))
    

    3.AccountService接口中的任何方法

     execution(* com.xyz.service.AccountService.*(..))
    

    4.service包下的任何方法

    execution(* com.xyz.service.*.*(..))
    

    5.service包以及其子包下的任何方法

    execution(* com.xyz.service..*.*(..))
    

4.@Pointcut切入点表达式二(within,target,@annotation)

  • within使用范例
    1. service包下的任何方法
    within(com.xyz.service.*)
    
    1. service及其子包下的任何方法
    within(com.xyz.service..*)
    
  • target使用范例
    1. 实现AccountService接口的所有方法
    target(com.xyz.service.AccountService)
    
  • @annotation使用范例
    1. 方法上包含Transactional注解,将被拦截
    @annotation(org.springframework.transaction.annotation.Transactional)
    

5.@Pointcut切入点表达式三(混合使用)

  • 我们可以使用&&, || 和 ! 来组合多个表达式
    @Pointcut("execution(public * (..))")
    private void anyPublicOperation() {} 
    
    @Pointcut("within(com.xyz.someapp.trading..)")
    private void inTrading() {} 
    
    @Pointcut("anyPublicOperation() && inTrading()")
    private void tradingOperation() {} 
    
  • 上述示例中表示在trading包及其子包下所有public的方法都被拦截

6.@Pointcut切入点表达式范例

  • @Pointcut切入点表达式范例
      //================================execution
        @Pointcut("execution(* com.friends.springbootaop.pointcut.AspectController.aspectTest())")
        public void pointcut(){}
    
        @Before("pointcut()")
        public void aspectTestAop() {
            System.out.println("do something");
        }
        //================================within
        @Pointcut("within(com.friends.springbootaop.pointcut.whithin.*)")
        public void pointcutWithin(){}
    
        @Before("pointcutWithin()")
        public void aspectWithinAop() {
            System.out.println("hi within do something");
        }
    
        //================================target
        @Pointcut("target(com.friends.springbootaop.pointcut.target.TargetInterface)")
        public void pointcutTarget(){}
    
        @Before("pointcutTarget()")
        public void aspectTargetAop() {
            System.out.println("hi target do something");
        }
    
        //================================@annotation
    
        @Pointcut( "@annotation(org.springframework.validation.annotation.Validated)")
        public void pointcutAnnotation(){}
    
        @Before("pointcutAnnotation()")
        public void aspectAnnotationAop() {
            System.out.println("hi annotation do something");
        }
    

7.定义通知

  • 在通知中可以直接书写切入点表达式也可以直接引用切入点
  • @Before方法执行前拦截
        @Before(value = "execution(* com.friends.springbootaop.advice.AspectAdviceController.aspectBeforeExample(..))")
        public void aspectBeforeAop() {
            System.out.println("Before  do something");
        }
    
  • @After方法执行后拦截
        @After("execution(* com.friends.springbootaop.advice.AspectAdviceController.aspectBeforeExample(..))")
        public void aspectAfterAop() {
            System.out.println("After  do something");
        }
    
  • @AfterReturning方法执行后,正常返回前拦截
     @AfterReturning(
                value="execution(* com.friends.springbootaop.advice.*.*(..))",
                returning="retVal")
        public void aspectAfterReturningAop(Object retVal) {
            System.out.println("AfterReturning "+retVal.toString());
        }
    
  • @AfterThrowing方法执行后,返回异常拦截
     @AfterThrowing(
                pointcut="execution(* com.friends.springbootaop.advice.*.*(..))",
                throwing="ex")
        public void aspectAfterThrowingAop(Exception ex) {
            System.out.println(ex.getMessage());
        }
    
  • @Around环绕通知,方法执行前后拦截,最为常用
     @Around("execution(* com.friends.springbootaop.advice.AspectAdviceController.around(..))")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            // start stopwatch
            System.out.println("Around start do something");
            Object retVal = pjp.proceed();
            // stop stopwatch
            System.out.println("Around return do something");
            return retVal;
        }
    

8.处理通知中的参数

  • 所有的通知类型都支持org.aspectj.lang.JoinPoint作为方法中的第一个参数,在环绕通知中使用ProceedingJoinPoint,其是JoinPoint的子类,JoinPoint中方法详解如下:
    1. getArgs(): 返回请求参数
    2. getThis(): 返回代理对象
    3. getTarget(): 返回被代理对象
    4. getSignature(): 返回被拦截方法的签名描述
    5. toString(): 返回切点表达式被替换后的描述
  • 环绕通知中的ProceedingJoinPoint对象,添加了proceed方法,用于区分拦截前后和方法增强操作

  • 在通知中我们可以直接获取到参数的实例对象,使用args标识入参,如下

     @Pointcut("execution(* com.friends.springbootaop.advice.AspectAdviceController.instanceParameter(..)))")
        public void InstanceParameter(){}
    
        @Before("InstanceParameter() && args(guy,..)")
        public void aspectInstanceParameterAop(Guy guy) {
            System.out.println("第一种:"+guy.toString());
        }
    
  • 在通知中我们可以直接获取到方法上的注解,使用@annotation标识传入注解,范例如下:
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Auditable {
        String value();
    }
    
    @GetMapping("annotationParameter")
    @Auditable("hi guys!! I'm annotationParameter")
    public String annotationParameter(){
        return "hi annotationParameter!!";
    }
    
    @GetMapping("annotationParameter")
    @Auditable("hi guys!! I'm annotationParameter")
    public String annotationParameter(){
        return "hi annotationParameter!!";
    }
    
    @Pointcut("execution(* com.friends.springbootaop.advice.AspectAdviceController.annotationParameter(..)))")
    public void annotationParameter(){}
    
    @Before("annotationParameter() && @annotation(auditable)")
    public void aspectAnnotationParameterAop(Auditable auditable) {
        System.out.println(auditable.value());
    }
    
    1. 定义Auditable注解在方法上生效,执行时机是运行时
    2. annotationParameter方法上使用此注解
    3. 在aop通知中使用@annotation和形参配合获取此注解

9.总结

zhangfeng

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: