Spring AOP基本原理

Spring AOP 是基于动态代理机制实现的,通过动态代理机制生成目标对象的代理对象,当外部调用目标对象的相关方法时,Spring注入的其实是代理对象Proxy,通过调用代理对象的方法执行AOP增强处理,然后回调目标对象的方法。

失效原因

AOP使用的是JDK动态代理或者cglib代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。

而当类内部调用增强方法时,使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效

只有引用的是被 动态代理 所创对象,才能被Spring增强,实现期望的AOP功能。

复现

创建了 Spring Boot 项目,依赖信息

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义注解和切面类

1
2
3
4
5
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
}

切面类

1
2
3
4
5
6
7
8
9
10
11
@Aspect
@Component
public class LogAspect {

@Around("@annotation(com.example.demo.aop.Log)")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = proceedingJoinPoint.proceed();
System.out.println("进入切面方法");
return result;
}
}

内部调用

定义了两个方法 login()status()

login() 方法上使用了自定义注解,同时在同一个类中的 status() 方法里调用了 login() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class User {

@Log
public void login() {
System.out.println("用户已登录");
}

public void status() {
System.out.println("用户登录状态");
this.login();
}
}

写一个测试方法

1
2
3
4
5
6
7
8
9
10
@SpringBootTest
public class AopTest {
@Resource
private User user;

@Test
public void test() {
user.status();
}
}

输出结果

1
2
用户登录状态
用户已登录

可以发现增强方法失效

解决方法

通过 ApplicationContext 引入 bean 对象

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
@Component
public class User {

@Resource
private ApplicationContext applicationContext;

/**
* 获取当前类的代理类对象
*
* @return
*/
private User getProxyClassObject() {
return applicationContext.getBean(this.getClass());
}

@Log
public void login() {
System.out.println("用户已登录");
}

public void status() {
// 获取代理类对象
User user = getProxyClassObject();
System.out.println("用户登录状态");
// 调用代理类对象的login()方法
user.login();
}
}

引入自身bean对象

不推荐,有循环依赖问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class User {

@Resource
/**
* 延迟加载,尝试解决循环依赖问题
*/
@Lazy
private User user;

@Log
public void login() {
System.out.println("用户已登录");
}

public void status() {
System.out.println("用户登录状态");
user.login();
}
}

引入AopContext获取当前类的代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
/**
* 启用AspectJ自动代理的注解
* 将当前代理对象暴露出去
*/
@EnableAspectJAutoProxy(exposeProxy=true)
public class User {

@Log
public void login() {
System.out.println("用户已登录");
}

public void status() {
System.out.println("用户登录状态");
// 获取当前代理对象,并调用 login() 方法
((User)AopContext.currentProxy()).login();
}
}