3.SpringBoot中AOP使用

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.*)
      
    2. 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.总结

  • 在SpringBoot中定义AOP处理十分简便,①使用@Pointcut定义切入点,并指定切入点表达式;②使用@Before,@Around等注解定义通知类型;③使用通知中的形参JoinPoint 等的方式处理参数。
  • 此文章的github范例,点击获取
  • 参考:
    • SpringBoot官方文档
    • SpringFramework官方AOP文档
    • aspectj官方文档

你可能感兴趣的:(Spring,#,Spring,Boot,#,SpringFramework,spring,springboot,aop,代理)