Spring 之 AOP 基础使用

AOP 的前置知识

AOP 的常用注解

  • @Before:前置通知,在目标方法执行之前执行
  • @After:后置通知,在目标方法执行之后执行(始终会执行)
  • @AfterReturning:后置返回后通知,在目标方法正常返回后执行(发生异常不会执行)
  • @AfterThrowing:后置异常通知,在目标方法抛出异常后执行
  • @Around: 环绕通知,可以在目标方法执行前后执行自定义逻辑
  • @Pointcut:定义切入点,指定在哪些连接点上应用切面的逻辑
  • @Aspect:定义切面,通常与 @Component 结合使用,将其标记为 Spring 容器中的一个 Bean

Spring 中的 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
43
@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 ...");
log.error("@AfterThrowing exception : {}", exception.getMessage());
}

@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());
// 抛出异常,否则不会触发 @AfterThrowing
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
/**
* 这里直接基于 SpringBootTest 进行单元测试
*/
@EnableAspectJAutoProxy
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MainTestApplication.class})
public class MainTestApplication {

@Autowired
private MyService myService;

@Test
public void test() {
myService.div(9, 1);
}

}

测试结果

特别注意,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
6
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 ...
2019-05-13 20:59:38.580 ERROR 12634 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterThrowing exception : java.lang.ArithmeticException: / by zero

Spring 4.x 的部分 AOP 通知顺序总结

  • 正常执行:@Before(前置通知)> @After(后置通知)> @AfterReturning(正常返回)
  • 异常执行:@Before(前置通知)> @After(后置通知)> @AfterThrowing(方法异常)

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
6
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.231 ERROR 31712 --- [ main] com.java.interview.spring.aop.MyAspect : @AfterThrowing exception : / by zero
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 通知顺序总结

  • 正常执行:@Before(前置通知)> @AfterReturning(正常返回)> @After(后置通知)
  • 异常执行:@Before(前置通知)> @AfterThrowing(方法异常)> @After(后置通知)

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);

// 当 @EnableAspectJAutoProxy 注解的 exposeProxy 属性为 false 时(默认),这里直接调用 multiply() 内部方法,并不会发生代理调用
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 属性将代理对象暴露出去
@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);

// 当 @EnableAspectJAutoProxy 注解的 exposeProxy 属性为 true 时,可以通过 AopContext 获取当前的代理对象,然后再调用内部方法
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 事务不能回滚的深层次原因