Spring AOP 的实现原理主要基于 动态代理技术,其核心是通过在运行时生成代理对象,将切面逻辑(如日志、事务等)织入到目标方法中。以下是其实现原理的详细解析:
一、动态代理的两种实现方式
Spring AOP 根据目标对象是否实现接口,选择以下两种动态代理方式:
1. JDK 动态代理(接口代理)
-
适用场景:目标类实现了至少一个接口。
-
实现原理:
- 通过
java.lang.reflect.Proxy类动态生成代理对象。 - 代理对象会实现目标接口,并重写接口方法。
- 代理对象内部通过
InvocationHandler接口的invoke()方法拦截目标方法的调用。
- 通过
-
示例代码:
public class JdkProxyDemo { public static void main(String[] args) { TargetInterface target = new TargetImpl(); TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxyObj, method, methodArgs) -> { System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, methodArgs); System.out.println("After method: " + method.getName()); return result; } ); proxy.doSomething(); } } -
优点:无需引入第三方库。
-
缺点:只能代理接口方法,无法代理未实现接口的类。
2. CGLIB 动态代理(子类代理)
-
适用场景:目标类未实现接口。
-
实现原理:
- 通过字节码框架 CGLIB 动态生成目标类的子类作为代理。
- 代理子类会重写父类(目标类)的非
final方法。 - 拦截逻辑通过
MethodInterceptor接口的intercept()方法实现。
-
示例代码:
public class CglibProxyDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TargetClass.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("Before method: " + method.getName()); Object result = proxy.invokeSuper(obj, args); System.out.println("After method: " + method.getName()); return result; }); TargetClass proxy = (TargetClass) enhancer.create(); proxy.doSomething(); } } -
优点:可代理未实现接口的类。
-
缺点:
- 无法代理
final类或final方法。 - 需要引入 CGLIB 依赖(Spring 已内置)。
- 无法代理
二、Spring AOP 的代理创建流程
当 Spring 容器初始化 Bean 时,会按以下步骤决定是否生成代理对象:
-
Bean 的创建与初始化 Spring 容器创建目标 Bean 的实例(如
UserService)。在这之前,容器初始化的时候会通过@EnableAspectJAutoProxy 注册AOP的beanpostprocessor
AnnotationAwareAspectJAutoProxyCreator -
切面匹配检查 遍历所有切面(
@Aspect类),检查当前 Bean 是否有方法匹配切点表达式(Pointcut)。并得到advisor(包含pointcut与advice) -
代理决策
- 若目标 Bean 实现了接口 → 使用 JDK 动态代理。
- 若未实现接口 → 使用 CGLIB 代理。
- 可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用 CGLIB。
-
代理对象的生成
- 动态生成代理类,并替换原始 Bean。
- 代理对象会包含所有匹配的切面逻辑(Advice)。
-
方法调用拦截 当调用代理对象的方法时:
- 代理对象根据方法签名匹配对应的切面。
- 按顺序执行通知链(Advice Chain)中的逻辑(如
@Before→@Around→ 目标方法 →@After等)。
三、代理对象的核心工作流程
以 @Around 环绕通知为例,代理对象的方法调用过程如下:
public class ProxyExample {
// 伪代码:代理对象的拦截逻辑
public Object interceptedMethod(Method method, Object[] args) {
// 1. 执行 @Before 通知
for (BeforeAdvice advice : beforeAdvices) {
advice.before();
}
Object result = null;
try {
// 2. 执行 @Around 的 proceed() 前的逻辑
for (AroundAdvice advice : aroundAdvices) {
// 调用 proceed() 会进入下一个 Around 或执行目标方法
result = advice.around(() -> {
return method.invoke(targetObject, args); // 最终调用原始方法
});
}
// 3. 执行 @AfterReturning 通知
for (AfterReturningAdvice advice : afterReturningAdvices) {
advice.afterReturning(result);
}
} catch (Exception e) {
// 4. 执行 @AfterThrowing 通知
for (AfterThrowingAdvice advice : afterThrowingAdvices) {
advice.afterThrowing(e);
}
throw e;
} finally {
// 5. 执行 @After 通知
for (AfterAdvice advice : afterAdvices) {
advice.after();
}
}
return result;
}
}
四、关键问题解析
1. 为什么同类内的方法调用不会触发 AOP?
-
原因: Spring AOP 的代理对象是外部调用的入口,而同类内的方法调用(如
this.methodB())直接使用的是原始对象(this),而非代理对象,因此无法被拦截。 -
解决方案:
-
通过 AopContext 获取当前代理对象:
((UserService) AopContext.currentProxy()).methodB(); -
使用 AspectJ 的编译时织入(需配置
@EnableLoadTimeWeaving)。
-
2. Spring 如何优化代理对象的创建?
- 代理对象缓存: Spring 会缓存已生成的代理对象,避免重复创建。
- 延迟初始化: 代理对象在首次方法调用时生成(结合
@Lazy注解使用)。
3. 动态代理的性能开销
- JDK 动态代理:基于反射调用,性能略低。
- CGLIB:生成子类直接调用方法(通过 FastClass 机制),性能接近原生代码。
- 优化建议: 避免在频繁调用的方法上使用复杂的切面逻辑。
五、Spring AOP 与 AspectJ 的本质区别
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时动态代理 | 编译时或类加载时(LTW) |
| 粒度 | 仅方法级别 | 方法、构造器、字段、静态代码块等 |
| 性能 | 动态代理有轻微性能损耗 | 直接修改字节码,性能更高 |
| 依赖 | 无需额外配置 | 需 AspectJ 编译器或 Load-Time Weaving |
| 使用场景 | 适合简单横切逻辑(如日志、事务) | 复杂切面需求(如修改字段、监控系统级行为) |
六、总结
Spring AOP 的实现原理核心是 动态代理,通过运行时生成代理对象拦截方法调用,将横切逻辑与业务代码解耦。其关键点包括:
- 两种代理方式:JDK 动态代理(接口)和 CGLIB(子类)。
- 代理创建流程:Bean 初始化时匹配切面,动态生成代理对象。
- 拦截逻辑链:按顺序执行多个通知(Advice),实现环绕、前置、后置等逻辑。
- 局限性:同类调用不触发代理、仅支持方法级别拦截。
对于高性能或复杂切面需求,可结合 AspectJ 使用。理解其原理有助于合理设计切面,避免因代理机制导致的隐蔽问题(如事务失效)。