大纲
AOP 的常用注解
@Before
:前置通知,在目标方法执行之前执行@After
:后置通知,在目标方法执行之后执行(始终会执行)@AfterReturning
:后置返回后通知,在目标方法正常返回后执行(发生异常不会执行)@AfterThrowing
:后置异常通知,在目标方法抛出异常后执行@Around
: 环绕通知,可以在目标方法执行前后执行自定义逻辑@Pointcut
:定义切入点,指定在哪些连接点上应用切面的逻辑@Aspect
:定义切面,通常与 @Component
结合使用,将其标记为 Spring 容器中的一个 Bean
Spring 中的 AOP 注解使用
AOP 案例代码
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j @Service public class MyService {
public int div(int x, int y) { int result = x / y; log.info("result = {}", result); return result; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Slf4j @Aspect @Component public class MyAspect {
@Before("execution(public int com.java.interview.spring.aop.MyService.*(..))") private void beforeNotify() { log.info("@Before ..."); }
@After("execution(public int com.java.interview.spring.aop.MyService.*(..))") private void afterNotify() { log.info("@After ..."); }
@AfterReturning("execution(public int com.java.interview.spring.aop.MyService.*(..))") private void afterReturningNotify() { log.info("@AfterReturning ..."); }
@AfterThrowing(pointcut = "execution(public int com.java.interview.spring.aop.MyService.*(..))", throwing = "exception") private void afterThrowingNotify(Exception exception) { log.info("@AfterThrowing ..."); }
@Around("execution(public int com.java.interview.spring.aop.MyService.*(..))") private Object afterAroundNotify(ProceedingJoinPoint joinPoint) { Object result = null; log.info("@Around before ..."); try { result = joinPoint.proceed(); } catch (Throwable throwable) { log.error("@Around exception : {}", throwable.getMessage()); throw new RuntimeException(throwable); } log.info("@Around after ..."); return result; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@EnableAspectJAutoProxy @RunWith(SpringRunner.class) @SpringBootTest(classes = {MainTestApplication.class}) public class MainTestApplication {
@Autowired private MyService myService;
@Test public void test() { myService.div(9, 1); }
}
|
AOP 测试结果
特别注意,Spring 4.x 与 Spring 5.x 的 AOP 通知顺序是不一样的。
Spring 4.x 与 SpringBoot 1.x
注意
这里使用的 Spring 版本是 4.3.13.RELEASE
,而且 SpringBoot 的版本是 1.5.9.RELEASE
。
执行上述代码,输出的结果如下:
1 2 3 4 5 6
| 2019-05-13 20:48:17.135 INFO 9045 --- [ main] com.java.interview.spring.aop.MyAspect : @Around before ... 2019-05-13 20:48:17.135 INFO 9045 --- [ main] com.java.interview.spring.aop.MyAspect : @Before ... 2019-05-13 20:48:17.157 INFO 9045 --- [ main] com.java.interview.spring.aop.MyService : result = 9 2019-05-13 20:48:17.159 INFO 9045 --- [ main] com.java.interview.spring.aop.MyAspect : @Around after ... 2019-05-13 20:48:17.160 INFO 9045 --- [ main] com.java.interview.spring.aop.MyAspect : @After ... 2019-05-13 20:48:17.160 INFO 9045 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterReturning ...
|
此时,如果将上述代码中 myService.div(9, 1)
更改为 myService.div(9, 0)
,刻意让 Java 代码抛出异常,输出的结果如下:
1 2 3 4 5
| 2019-05-13 20:59:38.560 INFO 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @Around before ... 2019-05-13 20:59:38.560 INFO 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @Before ... 2019-05-13 20:59:38.578 ERROR 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @Around exception : / by zero 2019-05-13 20:59:38.580 INFO 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @After ... 2019-05-13 20:59:38.580 INFO 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterThrowing ...
|
Spring 4.x 的部分 AOP 通知顺序总结
- 正常执行:@Around(环绕通知 - 前半部分) > @Before(前置通知)> @Around(环绕通知 - 后半部分) > @After(后置通知)> @AfterReturning(后置返回后通知)
- 异常执行:@Around(环绕通知 - 前半部分) > @Before(前置通知)> @After(后置通知)> @AfterThrowing(后置异常通知)。特别注意,当发生异常时,
@Around(环绕通知 - 后半部分)
会不生效。
Spring 5.x 与 SpringBoot 2.x
注意
这里使用的 Spring 版本是 5.2.8.RELEASE
,而且 SpringBoot 的版本是 2.3.3.RELEASE
。
执行上述代码,输出的结果如下:
1 2 3 4 5 6
| 2019-05-13 21:35:33.890 INFO 31351 --- [ main] com.java.interview.spring.aop.MyAspect : @Around before ... 2019-05-13 21:35:33.891 INFO 31351 --- [ main] com.java.interview.spring.aop.MyAspect : @Before ... 2019-05-13 21:35:33.906 INFO 31351 --- [ main] com.java.interview.spring.aop.MyService : result = 9 2019-05-13 21:35:33.907 INFO 31351 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterReturning ... 2019-05-13 21:35:33.907 INFO 31351 --- [ main] com.java.interview.spring.aop.MyAspect : @After ... 2019-05-13 21:35:33.907 INFO 31351 --- [ main] com.java.interview.spring.aop.MyAspect : @Around after ...
|
此时,如果将上述代码中 myService.div(9, 1)
更改为 myService.div(9, 0)
,刻意让 Java 代码抛出异常,输出的结果如下:
1 2 3 4 5
| 2019-05-13 21:36:38.212 INFO 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @Around before ... 2019-05-13 21:36:38.212 INFO 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @Before ... 2019-05-13 21:36:38.231 INFO 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterThrowing ... 2019-05-13 21:36:38.233 INFO 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @After ... 2019-05-13 21:36:38.233 ERROR 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @Around exception : / by zero
|
Spring 5.x 的部分 AOP 通知顺序总结
- 正常执行:@Around(环绕通知 - 前半部分) > @Before(前置通知)> @AfterReturning(后置返回后通知)> @After(后置通知) > @Around(环绕通知 - 后半部分)
- 异常执行:@Around(环绕通知 - 前半部分) > @Before(前置通知)> @AfterThrowing(后置异常通知)> @After(后置通知)。特别注意,当发生异常时,
@Around(环绕通知 - 后半部分)
会不生效。
AOP 通知顺序总结
SpringBoot 1 使用的 Spring 版本是 4.x
,在这个版本中的 AOP 通知顺序为:
- (1) 环绕通知(Around advice)
- (2) 前置通知(Before advice)
- (3) 后置通知(After advice)
- (4) 后置返回通知(After-returning advice)
- (5) 后置异常通知(After-throwing advice)
SpringBoot 2 使用的 Spring 版本是 5.x
,在这个版本中的 AOP 通知顺序为:
- (1) 环绕通知(Around advice)
- (2) 前置通知(Before advice)
- (3) 后置返回通知(After-returning advice)
- (4) 后置异常通知(After-throwing advice)
- (5) 后置通知(After advice)
特别注意
Spring 4 和 Spring 5 的 AOP 通知顺序是不一样的,这一点必须记住。
Spring 中调用内部方法 AOP 不生效
在 Spring 中,类自身调用内部方法时,会出现 AOP 不生效的问题。基于上述案例,更改后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Slf4j @Service public class MyService {
public int div(int x, int y) { int result = x / y; log.info("div result is {}", result);
this.multiply(x, y);
return result; }
public int multiply(int x, int y) { int result = x * y; log.info("multiply result is {}", result); return result; }
}
|
可以发现,在调用 div()
方法时,可以发生代理调用,但是在 div()
方法中调用内部方法 multiply()
时,multiply()
方法并不会发生代理调用,因为 this
并非是代理对象(去掉 this
代理也不会生效)。解决方法是将代理对象暴露出去,然后在代码中通过 AopContext 获取当前的代理对象(底层使用了 ThreadLocal 来实现),最后再使用代理对象调用内部方法。基于上述案例,更改后的代码如下:
1 2 3 4 5 6 7
| @EnableAspectJAutoProxy(exposeProxy = true) public class MainTestApplication {
......
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Slf4j @Service public class MyService {
public int div(int x, int y) { int result = x / y; log.info("div result is {}", result);
MyService proxyInstance = (MyService) AopContext.currentProxy(); proxyInstance.multiply(x, y);
return result; }
public int multiply(int x, int y) { int result = x * y; log.info("multiply result is {}", result); return result; }
}
|
Spring 中 AOP 默认使用哪种代理方式
在 Spring 5.x 中,如果目标对象实现了接口,默认会使用 JDK 动态代理;如果目标对象没有实现接口,则会使用 Cglib 代理。底层源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
|
值得一提的是,从 SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转换异常,默认使用的是 Cglib 代理。如果需要让 SpringBoot 2.x 默认使用 JDK 动态代理,可以通过配置项 spring.aop.proxy-target-class = false
来进行指定。