Appearance
Spring AOP
本文介绍有关Spring AOP的知识。
1. 什么是AOP
AOP,全称Aspect-Oriented Programming,面向切面编程,是对OOP(Object-Oriented Programming,面向对象编程)的补充。
说人话,看不懂。
举个例子,假设现在有个用户服务类,其中有三个方法:用户注册、用户登录和用户退出登录。在每个方法调用时,都需要写日志,便于运维。如果我们在开发中,每次都需要手动加上写日志的逻辑,那么代码不仅变得重复冗余,而且不利于修改。
java
@Service
@Slf4j
public class UserService {
/**
* 用户注册
*/
public void registerUser(String username, String password){
log.info("registerUser {} {}", username, password);
// 注册逻辑
}
/**
* 用户登录
*/
public void userLogin(String username, String password){
log.info("userLogin {} {}", username, password);
// 登录逻辑
}
/**
* 用户退出登录
*/
public void userLogout(String userId){
log.info("userLogout {}", userId);
// 退出登录逻辑
}
/**
* 这是一个工具方法,不应该记录日志
*/
private void generateUserId(){
}
}那有没有一种机制,能让我们在调用方法前,先执行一段代码去写日志,这样就只需要指定哪些方法需要写日志了,而不用每次手动添加写日志的逻辑。也就是说,我们只需要在程序中声明:当程序运行UserService.registerUser()、UserService.userLogin()、UserService.userLogout()前,先写日志log()。这就是AOP机制。
最终执行代码可能会如下:
java
LogUtil.log();
UserService.registerUser()2. AOP术语
在AOP领域中,存在一些关键术语:
目标对象(Target):也就是说哪些对象中的某方法被执行时,需要添加其他额外逻辑,如上面的
UserService对象;连接点(Join Point):在程序执行中的某个时间点,或者说某个事件发生时,需要添加其他额外逻辑,例如上面的调用
registerUser()、userLogin()和userLogout()时,需要执行写日志操作;常见的连接点包括方法执行、字段访问、构造函数调用或异常处理 。
切入点(Pointcut):匹配特定连接点的表达式。只有特定的连接点才会触发AOP,也就是说,目标对象中的哪些方法被调用时,才会触发AOP,在上面的例子中,调用
registerUser()、userLogin()和userLogout()时,需要执行写日志,调用generateUserId()时,不需要写日志。通知(Advice):当匹配上连接点后,需要执行什么操作,通知是实际执行的代码块,并且需要和切入点一起使用。如上面的
log()方法;切面(Aspect):切面是一个类,包含多个通知,如上面的
LogUtil.log()中的LogUtil类;织入(Weaving):在应用程序声明周期的什么时候实现AOP,编译期、类加载期还是运行期;
如果连接点是方法执行,那么可以细分为以下时间点:
- 方法执行前
- 方法正常执行后
- 方法报错后
- 方法最终返回前
在Java中,可以使用try-catch-finally块来理解:
java
try{
方法执行前,这里可以添加操作(方法执行前)
目标对象目标方法执行
方法执行后,这里可以添加操作(方法正常执行后)
}catch(){
方法报错后,这里可以添加操作(方法报错后)
}finally{
(方法最终返回前)
}3. AspectJ 与 Spring AOP
3.1 AspectJ介绍
AspectJ是一个完整的 AOP 解决方案,提供灵活且强大的织入机制,直接修改字节码 。官方地址:https://eclipse.dev/aspectj/
实现方式分为两种:
编译时实现:在编译时,AspectJ 编译器(
ajc)将目标对象类和切面类的源代码作为输入,并生成织入后的类文件作为输出 。 例如,最终生成的UserService.class中registerUser()方法如下:javapublic void registerUser(String username, String password){ LogUtil.log(); // 注册逻辑 }类加载时实现:正常编译,但是将字节码文件文件加载进JVM内存时,修改字节码,添加通知。
例如,在
UserService.class文件中,registerUser()方法正常:javapublic void registerUser(String username, String password){ // 注册逻辑 }但是,将
UserService.class文件加载进内存时,修改UserService代码实现,在内存中的代码变为如下:javapublic void registerUser(String username, String password){ LogUtil.log(); // 注册逻辑 }
AspectJ 5 引入了 @AspectJ 样式,允许开发者使用带有注解的常规 Java 类来定义切面:
- 关键注解包括用于声明切面类的
@Aspect、用于定义切入点表达式的@Pointcut,以及用于通知类型的特定注解(@Before、@AfterReturning、@AfterThrowing、@After、@Around)。 - 这些注解定义在
org.aspectj.lang.annotation包中 。
3.2 Spring AOP介绍
Spring AOP是另一种AOP实现,它不直接修改目标类的字节码,而是创建包装目标对象的代理对象。代理分为:
- JDK代理:由Java自带的代理机制,原理是实现目标对象相同的接口,聚合目标对象;
- CGLIB代理:需要CGLIB支持,原理是继承目标对象类;
3.3 AspectJ和Spring AOP的关系
Spring AOP 的核心实现基于 JDK 动态代理和 CGLIB,是一种纯 Java 解决方案,其实现机制不依赖于AspectJ。
但是,Spring AOP 默认利用 AspectJ 切入点表达式语言来定义切入点,它使得 Spring AOP 能够受益于 AspectJ 强大且富有表现力的语法来匹配连接点,并且,Spring AOP也会实现AspectJ 5中引入的@AspectJ样式。
可以理解为,Spring AOP使用了AspectJ的一些工具方法、注解,所以Spring AOP依赖于AspectJ,但核心实现机制不同。
3.4 Spring AOP与AspectJ的对比
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 基于代理(JDK动态代理或CGLIB) | 字节码操作 |
| 织入机制 | 运行时织入 | 编译时、编译后、加载时织入 |
| 支持的连接点 | 仅限于方法执行连接点 | 支持所有连接点(包括方法执行、字段访问、构造函数调用等) |
| 性能 | 较慢(由于运行时代理开销) | 较快(字节码直接修改,没有运行时代理开销) |
| 复杂性 | 设置和使用更简单 | 设置更复杂(需要AspectJ编译器或LTW配置) |
| 适用范围 | 仅限Spring管理的Bean | 可适用所有领域对象(包括非Spring管理的对象) |
| 构建过程 | 无需额外编译器或织入器,可以与常规Java构建集成 | 需要AspectJ编译器(ajc)或LTW代理 |
| 自调用问题 | 存在(内部方法调用会绕过代理) | 不存在(字节码直接织入) |
| 调试与可维护性 | 配置简单,易于启用/禁用;堆栈跟踪可能比较长 | 更改需要重新编译;织入点追踪可能更复杂 |
重点关注实现方式、织入机制和支持的连接点这三项。
4. AOP初体验
4.1 引入依赖
首先引入Spring AOP依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>查看spring-boot-starter-aop项目依赖,会发现自动引入了aspectj:
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.5.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.2.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.24</version>
<scope>compile</scope>
</dependency>
</dependencies>4.2 启用AspectJ
使用注解@EnableAspectJAutoProxy在任意配置类上,以启用@AspectJ样式:
java
@Configuration
@ComponentScan(basePackages = {"com.lee.aop.t1"})
@EnableAspectJAutoProxy
public class Config {
}@EnableAspectJAutoProxy注解有两个参数:
boolean proxyTargetClass() default false:用于控制 Spring AOP 创建代理的方式proxyTargetClass = false(默认值):Spring AOP 优先使用 JDK 动态代理。如果目标类没有实现任何接口,Spring 会自动退回到使用 CGLIB 代理。
使用 JDK 动态代理时,代理类和目标类是兄弟关系,代理类实现了目标类所实现的接口。
proxyTargetClass = true:- Spring AOP 强制使用 CGLIB 代理。CGLIB 代理不需要目标类实现接口,它通过继承目标类来创建代理类。
- 使用 CGLIB 代理时,代理类是目标类的子类。
boolean exposeProxy() default false:当将其设置为true时,它的作用是:暴露当前代理对象到
AopContext。解决代理对象内部方法调用无法被 AOP 拦截的问题。
如果一个 bean 的某个方法 内部调用了自身 的另一个方法,那么代理不会生效:
java@Service public class MyService { @Transactional // 外部调用 methodA 时会走事务 public void methodA() { // ... 一些业务逻辑 methodB(); // 内部调用 methodB } @Transactional // 希望 methodB 也能有事务 public void methodB() { // ... 另一些业务逻辑 } }在这种情况下,当
methodA()被外部调用时,会通过代理对象进入 AOP 拦截,事务会生效。但是,在methodA()内部调用methodB()时,这个调用是直接通过this指针进行的,而不是通过代理对象。这意味着methodB()上的@Transactional注解(或其他 AOP 切面)将不会生效,因为它绕过了代理。为了解决这个问题,可以将
exposeProxy设置为true。当exposeProxy为true时,Spring 会将当前线程中的代理对象暴露到AopContext中。这样,在methodA()内部,就可以通过AopContext.currentProxy()获取到当前的代理对象,然后通过代理对象来调用methodB(),从而让 AOP 切面生效:java@Service public class MyService { @Transactional public void methodA() { // ... 一些业务逻辑 ((MyService) AopContext.currentProxy()).methodB(); // 通过代理调用 methodB } @Transactional public void methodB() { // ... 另一些业务逻辑 } }
4.3 编写切面类
编写切面类如下:
java
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Aspect1 {
@Before("execution(* study())")
public void before(){
System.out.println("before");
}
@After("execution(* study())")
public void after(){
System.out.println("after");
}
@AfterThrowing("execution(* study())")
public void afterThrowing(){
System.out.println("afterThrowing");
}
@AfterReturning("execution(* study())")
public void afterReturning(){
System.out.println("afterReturning");
}
}@Aspect:表明该类是切面类;@Before、@After、@AfterThrowing、@AfterReturning这些注解表明该方法是通知;执行顺序如下:
javatry{ @Before method() @AfterReturning }catch(){ @AfterThrowing }finally{ @After }即存在如下两种执行流程:
txt正常执行: @Before ---> @AfterReturning ---> @After 异常执行: @Before ---> @AfterThrowing ---> @Afterexecution(* study()):是切入点表达式,表明该通知匹配哪些方法;
4.4 编写业务类
java
@Component
public class Student {
public int study() {
System.out.println("正在学习...");
return 1;
}
}4.5 编写测试类
java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(Config.class);
applicationContext.refresh();
for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
Student bean = applicationContext.getBean(Student.class);
bean.study();
}
}结果如下:
java
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
config
aspect1
student
org.springframework.aop.config.internalAutoProxyCreator
>>>>>>>>>>>>>>>>>>>>>>>
before
正在学习...
afterReturning
after可以看到,AOP正常发挥作用,并且,容器中多了一个名为org.springframework.aop.config.internalAutoProxyCreator的Bean。
4.6 环绕通知
环绕通知通过@Around实现,主要为了手动控制连接点的执行,通过ProceedingJoinPoint来获取连接点(方法)并执行:
java
@Component
@Aspect
public class Aspect2 {
@Around("execution(* study())")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("before...");
Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
System.out.println("after returning...");
return proceed;
} catch (Throwable e) {
System.out.println("after throwing...");
throw new RuntimeException(e);
} finally {
System.out.println("after");
}
}
}5. Spring AOP APIs
本小节介绍Spring AOP中比较底层的API。
5.1 PointCut
PointCut接口代表切入点的实现,在Spring中,由于连接点只有方法调用,所以切入点只关注两点:
- 哪些类可以被切入;
- 类中的哪些方法可以被切入;
所以接口定义如下:
java
public interface Pointcut {
// ClassFilter 决定哪些类可以被切入
ClassFilter getClassFilter();
// MethodMatcher 决定类中的哪些方法可以被切入
MethodMatcher getMethodMatcher();
}java
public interface ClassFilter {
boolean matches(Class clazz);
}java
public interface MethodMatcher {
// 暂时只关注这个方法,用于静态切点
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}在实际中,用得比较多的是PointCut实现是AspectJExpressionPointcut,它可以设置AspectJ表达式,来判断是否匹配方法:
java
public class Main {
public static void main(String[] args) throws NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 设置AspectJ表达式
pointcut.setExpression("execution(* study())");
// 判断是否匹配方法
boolean match1 = pointcut.matches(Student.class.getDeclaredMethod("study"), Student.class);
boolean match2 = pointcut.matches(Student.class.getDeclaredMethod("play"), Student.class);
System.out.println(match1);
System.out.println(match2);
}
}
class Student{
public void study(){}
public void play(){}
}
// 结果
// true
// false5.2 Advice与Interceptor
Advice定义了在特定的连接点(Joinpoint)上执行的横切行为(Cross-cutting Concern)。简单来说,它就是你想要插入到程序中特定位置的代码。
根据插入时机的不同,Advice 又分为多种类型,例如:
- Before Advice (前置通知): 在方法执行前执行。
- After Returning Advice (返回后通知): 在方法成功执行并返回后执行。
- After Throwing Advice (抛出异常后通知): 在方法抛出异常后执行。
- After Advice (后置通知/最终通知): 无论方法是否成功执行或抛出异常,都会在方法执行后执行。
- Around Advice (环绕通知): 围绕方法执行,可以在方法执行前和执行后都进行操作,甚至可以控制方法是否执行。
Interceptor是 Advice 的一种特定类型,特别是环绕通知(Around Advice)的一种实现。它用于拦截运行时事件,例如方法调用、构造函数调用、字段访问等。
最常见的Interceptor是 org.aopalliance.intercept.MethodInterceptor。它提供了一个 invoke(MethodInvocation invocation) 方法,允许在目标方法执行前、执行后以及捕获异常进行操作,MethodInvocation是被拦截的方法,可以通过 invocation.proceed() 来控制是否继续执行目标方法。
这三者的关系如图:

java
package org.aopalliance.aop;
public interface Advice {
}java
package org.aopalliance.intercept;
import org.aopalliance.aop.Advice;
public interface Interceptor extends Advice {
}java
package org.aopalliance.intercept;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}Advice 奠定了 AOP 的基础,是一个高层抽象,定义了横切关注点的各种“执行时机”,也就是说,如果仅仅在方法执行前打印日志,BeforeAdvice就够了;如果仅仅需要在方法执行后打印日志,AfterAdvice就足够了,从而让我们的程序知道这些Advice是在什么时候执行的。
Interceptor 强大在它能“拦截”整个调用过程,在方法执行前、执行后、甚至决定是否执行方法,或者修改方法的参数和返回值。这是其他简单 Advice (例如BeforeAdvice)类型无法做到的。
在很多时候,仅仅需要简单Advice就足够了,并不总是需要Interceptor。
**但是,在Spring中,总是会把Advice转换为Interceptor**执行!!!
在Spring中,针对5种Advice,定义了如下5种实现类:

其中,AspectJMethodBeforeAdvice和AspectJAfterReturningAdvice没有实现MethodInterceptor接口,是简单的Advice,所以需要转换器将其转换为MethodInterceptor,分别为MethodBeforeAdviceAdapter和AfterReturningAdviceAdapter:
java
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}java
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof AfterReturningAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
return new AfterReturningAdviceInterceptor(advice);
}
}因此,我们可以得出结论,在Spring中,五种Advice的实现都是MethodInterceptor实现,通过invoke()方法控制方法调用流程。
5.3 Advice和PointCut
在前一小节中,Spring中Advice的5种实现都有一个共同父类:AbstractAspectJAdvice,其中包含属性pointcut:
java
public AbstractAspectJAdvice(
Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
this.methodName = aspectJAdviceMethod.getName();
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
this.aspectJAdviceMethod = aspectJAdviceMethod;
this.pointcut = pointcut;
this.aspectInstanceFactory = aspectInstanceFactory;
}所以,Advice可以聚合PointCut(在某些场景下)。
5.4 Advisor与Aspect
在Spring中,Advisor就是包含一个通知(Advice)的切面,这称为低级切面;在我们程序中,通过@Aspect标注的类中,可以有多个通知(Advice),这称为高级切面。
在Spring中,一个高级切换最终会转换为多个低级切面,也就是说,标注了@Before等注解的方法,会转换为一个Advisor。
Advisor接口如下:
java
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
default boolean isPerInstance() {
return true;
}
}可以看到,Advisor中需要包含Advice。
Advisor的子接口PointCutAdvisor定义如下:
java
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}可以看到,PointcutAdvisor中需要包含PointCut。
可以理解为,Advisor同时包含Advice和PointCut。
5.5 ProxyFactory
ProxyFactory是Spring通过的代理工厂,它会根据情况提供JDK动态代理或CGLIB代理。
它的使用很简单,只需要提供被代理对象即可:
java
MyService target = new MyService();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target); // 设置目标对象(被代理对象)
// 得到代理对象
MyService proxy = (MyService)proxyFactory.getProxy();得到的代理对象,是通过JDK动态代理生成的,还是通过CGLIB生成的,主要判断逻辑如下:
首先,判断目标对象是否有接口,如果没有接口,那么使用CGLIB生成代理;否则,转到第2步继续判断。关于目标对象是否实现了接口,可以通过以下方法设置:
javaproxyFactory.setInterfaces()判断属性
proxyTargetClass的值:- 如果值为
true,那么将会使用CGLIB代理; - 如果值为
false,那么将会使用JDK代理,默认值为false;
可以通过以下方法设置:
javaproxyFactory.setProxyTargetClass(true);- 如果值为
通过以上方式创建出来的代理,只是普通的代理对象,没有任何的功能增强,我们可以使用如下方法,为被代理的对象增强方法:
java
proxyFactory.addAdvisor();
// 或者添加多个
// proxyFactory.addAdvisors()5.6 编程式AOP
下面以编程的方式使用AOP。
首先准备目标类接口和目标类:
java
public interface IService {
void doSomething(String param);
String getResult();
}
public class MyService implements IService{
@Override
public void doSomething(String param) {
System.out.println("执行目标方法: doSomething, 参数: " + param);
}
@Override
public String getResult() {
System.out.println("执行目标方法: getResult");
return "结果";
}
}然后使用AOP:
java
public class Main2 {
public static void main(String[] args) throws NoSuchMethodException {
// 1. 创建目标对象
MyService target = new MyService();
// 2. 获取Advisor
Advisor advisor = getAdvisor();
// 3. 创建代理工厂并配置
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target); // 设置目标对象
proxyFactory.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
proxyFactory.addAdvisors(advisor); // 添加 Advisor
//proxyFactory.setProxyTargetClass(true);
proxyFactory.setInterfaces(MyService.class.getInterfaces());
// 4. 获得代理对象并使用
IService proxy = (IService)proxyFactory.getProxy();
proxy.doSomething("test");
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(proxy.getResult());
}
/**
* 获得Advisor
*/
private static Advisor getAdvisor() throws NoSuchMethodException {
// 1. 创建PointCut
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* getResult())");
// 2. 创建Advice, 这里使用Spring定义好的AspectJMethodBeforeAdvice,也可以自己实现接口
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(
Advice.class.getDeclaredMethod("before"),
pointcut,
new SingletonAspectInstanceFactory(new Advice())
);
// 3. 创建Advisor
DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(
pointcut, new MethodBeforeAdviceInterceptor(advice));
return defaultPointcutAdvisor;
}
static class Advice{
public void before(){
System.out.println("before...");
}
}
}在第12行中,我们在第一个位置添加了
ExposeInvocationInterceptor.ADVISOR,这是因为AspectJMethodBeforeAdvice在调用通知方法时,会从ThreadLocal中获取MethodInvocation,而ExposeInvocationInterceptor.ADVISOR放在Advisor调用链第一个,就是将MethodInvocation设置进ThreadLocal中:javaprotected JoinPointMatch getJoinPointMatch() { MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); if (!(mi instanceof ProxyMethodInvocation pmi)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } return getJoinPointMatch(pmi); }在33-36行中,设置
advice时需要传入第一个参数Method和第三个参数AspectInstanceFactory,这是为了获取通知方法,以及在哪个对象上调用通知方法;
5.7 MethodInvocation
MethodInvocation表示方法调用,在Spring中,其唯一的实现类是ReflectiveMethodInvocation,通过其代理方法,我们可以得到其中的重要属性:
java
protected ReflectiveMethodInvocation(
Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
this.proxy = proxy; // 代理类
this.target = target; // 目标对象
this.targetClass = targetClass; // 目标对象类
this.method = BridgeMethodResolver.findBridgedMethod(method); // 要执行的目标方法
this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments); // 目标方法参数
// Advisor链,即通知方法链,用于增强目标方法
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}在其中的proceed()方法,主要逻辑如下(简化版本,只看主要逻辑):
java
@Override
@Nullable
public Object proceed() throws Throwable {
// currentInterceptorIndex 表示当前调用的通知方法索引
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 如果当前调用的通知方法索引已经到了最后,那么调用目标方法
return invokeJoinpoint();
}
// 获取下一个通知方法
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 调用下一个通知方法
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}我们以MethodBeforeAdviceInterceptor通知方法中的invoke()为例子:
java
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}首先调用通知方法this.advice.before(),然后调用MethodInvocation.proceed(),这就形成了递归调用。
5.8 代理对象
我们以JDK代理对象为例子,讲解调用代理对象时,是如何增强目标方法的,查看JdkDynamicAopProxy(这就是代理工厂返回的代理对象),查看其invoke()方法,有以下逻辑:
java
// 获取当前类当前方法符合条件的Advisor链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 如果调用链为空,则直接反射调用目标对象方法
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 如果调用链不为空,创建MethodInvocation对象,沿着调用链递归调用
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}6. Spring AOP 源码分析
本小节以查看源码的方式,讲解Spring是如何解析、配置AOP功能的。
6.1 @EnableAspectJAutoProxy
首先查看@EnableAspectJAutoProxy注解,该注解需要加到任意配置类上,以启用AspectJ AOP功能,定义如下:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}可以看到,这个注解注入了AspectJAutoProxyRegistrar,这是一个实现了ImportBeanDefinitionRegistrar接口的类,由ConfigurationClassPostProcessor这个工厂后处理器处理,最终调用registerBeanDefinitions()方法:
java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}最终会在容器注入以下类:
java
AnnotationAwareAspectJAutoProxyCreator这个类是Bean后处理器,继承结构如下:

6.2 AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator这是一个Bean后处理器,主要关注初始化Bean后,查看其父类AbstractAutoProxyCreator的postProcessAfterInitialization方法:
java
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
// bean 就是创建完成后的对象
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyBeanReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}在第6行wrapIfNecessary()方法中,会判断是否需要为Bean创建代理:
java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略部分代码...
// 查看当前bean是否有满足条件的Advisor
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
// 如果有Advisor,那么需要创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
return proxy;
}
// 没有Advisor,直接返回原始Bean
return bean;
}6.3 查找Advisors
通过上面的getAdvicesAndAdvisorsForBean()方法,最终跟踪到findEligibleAdvisors()方法(AbstractAdvisorAutoProxyCreator类中的)
java
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 查找候选的Advisor
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 查找有资格的Advisor,也就是能切入该Bean的Advisor
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
// 扩充Advisors链
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 如果合格的Advisor链不为空,排序
try {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
catch (BeanCreationException ex) {
throw new AopConfigException("Advisor sorting failed with unexpected bean creation, probably due " +
"to custom use of the Ordered interface. Consider using the @Order annotation instead.", ex);
}
}
return eligibleAdvisors;
}findCandidateAdvisors()需要查看AnnotationAwareAspectJAutoProxyCreator子类中的实现,主要分为两步:
- 调用父类中的方法,查找容器中类型为
Advisor的Bean,这是直接注入的低级切面; - 调用如下方法,解析高级切面(有
@Aspect注解的Bean),将其转换为低级切面;
txt
org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisorsfindAdvisorsThatCanApply()的主要逻辑如下:
获取
Bean Class中的每个Method;对于每个
Method,用每个Advisor判断是否满足匹配条件,如果满足,则加入到有资格的Advisor列表中;类似逻辑为代码如下:
javaList<Advisor> eligibleAdvisors = new ArrayList<>(); for (Advisor candidate : candidateAdvisors) { MethodMatcher methodMatcher = candidate.getPointCut().getMethodMatcher(); Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if (methodMatcher.matches(method, targetClass)) { eligibleAdvisors.add(candidate); } } }
extendAdvisors()方法主要是判断Advisor调用链中是否有AspectJ通知,如果有,则需要在调用链第一个位置加入ExposeInvocationInterceptor.ADVISOR:
java
public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
// Don't add advisors to an empty list; may indicate that proxying is just not required
if (!advisors.isEmpty()) {
boolean foundAspectJAdvice = false;
for (Advisor advisor : advisors) {
// Be careful not to get the Advice without a guard, as this might eagerly
// instantiate a non-singleton AspectJ aspect...
if (isAspectJAdvice(advisor)) {
foundAspectJAdvice = true;
break;
}
}
if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
return true;
}
}
return false;
}6.4 创建代理对象
当上面查找到有符合条件的Advisor后,就需要创建代理对象,最终进入buildProxy()方法,主要逻辑就是创建ProxyFactory,设置代理工厂相关属性(主要是addAdvisors()),最后调用getProxy()方法获得代理对象:
java
private Object buildProxy(Class<?> beanClass,
@Nullable String beanName,
@Nullable Object[] specificInterceptors,
TargetSource targetSource,
boolean classOnly) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
}
}
}
else {
// No proxyTargetClass flag enforced, let's apply our default checks...
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = smartClassLoader.getOriginalClassLoader();
}
return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}6.5 总结
- 通过注解
@EnableAspectJAutoProxy引入AnnotationAwareAspectJAutoProxyCreator,这是一个Bean后处理器,主要关注创建对象后; - 容器创建Bean后,查找是否有符合条件的
Advisor,如果有,则需要创建代理对象;- 查找符合条件的
Advisor,涉及解析@Aspect注解,将高级切面转换为低级切面; - 低级切面又包括
Advice和PointCut;
- 查找符合条件的
- 创建代理对象,直接使用
ProxyFactory进行创建;
7. 其他
除了上面的Spring AOP 知识,还可以关注以下知识:
7.1 AspectJ表达式
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
7.2 代理原理
JDK动态代理原理和CGLIB代理原理
7.3 AspectJ
使用AspectJ实现AOP,案例。
7.4 自己实现AOP
结合以上原理,自己实现一个AOP。