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"); } }
- @Aspect声明AOP切入面
- @Component声明bean
- @Pointcut声明切入点,使用表达式标明拦截点
- @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?)
- 除了返回类型模式,命名模式和参数模式以外的所有部分都是可选的
- modifiers-pattern访问修饰符public,private等
- ret-type-pattern返回类型,
*
是最常用的返回类型 - declaring-type-pattern声明类型模式(包加类),如果有声明类型模式,需要在声明模式和命名模式之间添加一个
.
进行链接 - name-pattern命名模式(方法名),
*
代表包含所有方法 - (param-pattern)参数匹配,
()
无参数,(..)
任何参数,(*)
任何单参数,(*,String)
第一个参数为任何参数,第二个为String类型 - 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使用范例
-
service包下的任何方法
within(com.xyz.service.*)
-
service及其子包下的任何方法
within(com.xyz.service..*)
-
- target使用范例
-
实现AccountService接口的所有方法
target(com.xyz.service.AccountService)
-
- @annotation使用范例
- 方法上包含Transactional注解,将被拦截
@annotation(org.springframework.transaction.annotation.Transactional)
- 方法上包含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中方法详解如下:- getArgs(): 返回请求参数
- getThis(): 返回代理对象
- getTarget(): 返回被代理对象
- getSignature(): 返回被拦截方法的签名描述
- 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()); }
- 定义Auditable注解在方法上生效,执行时机是运行时
- annotationParameter方法上使用此注解
- 在aop通知中使用@annotation和形参配合获取此注解
9.总结
- 在SpringBoot中定义AOP处理十分简便,①使用@Pointcut定义切入点,并指定切入点表达式;②使用@Before,@Around等注解定义通知类型;③使用通知中的形参JoinPoint 等的方式处理参数。
- 此文章的github范例,点击获取
- 参考: