Spring AOP 核心原理:从思想到源码的深度剖析
AOP(面向切面编程)是 Spring 框架三大核心思想之一,与 IoC 相辅相成,共同支撑起 Spring 灵活、低耦合的架构设计。AOP 通过 "横切" 的方式分离业务逻辑与通用功能(如日志、事务、权限),彻底解决了代码冗余和耦合问题。
本次学习将从 AOP 设计思想出发,通过手写简易 AOP 理解核心逻辑,再深入 Spring AOP 源码,以掌握AOP其实现原理。
一、AOP 设计思想
1. Spring 的三大核心:IoC、DI 与 AOP
Spring 框架的强大源于三大核心思想的协同作用:
- IoC(控制反转):将对象创建权交给容器,解耦对象依赖;
- DI(依赖注入): 容器自动为对象注入依赖,简化对象组装;
- AOP(面向切面编程): 分离通用逻辑与业务逻辑,实现代码复用与集中管理。
三者关系:IoC 是基础,DI 是 IoC 的实现方式,AOP 基于 IoC 容器实现横切逻辑的织入,共同目标是降低耦合度。
2. AOP 是思想,Spring AOP 是实现
AOP(Aspect-Oriented Programming)是一种编程思想 ,核心是 "将通用逻辑从业务逻辑中抽离,通过横切技术织入到目标方法中"。
思想层面: AOP 不依赖具体框架,任何语言都可实现(如 AspectJ、JBoss AOP)。
实现层面: Spring AOP 是 AOP 思想的具体实现,基于动态代理技术,与 Spring IoC 容器深度集成,简化了 AOP 的使用。
3. 核心价值:隔离业务逻辑,降低耦合
传统开发中,通用功能(如日志、事务)会嵌入到业务代码中,导致:
- 代码冗余: 同一逻辑在多个业务方法中重复编写;
- 耦合严重: 修改通用逻辑需改动所有业务代码;
- 维护困难: 业务逻辑与通用逻辑混杂,可读性差。
AOP 通过 "切面" 将通用逻辑集中管理,业务代码只关注核心逻辑,实现 "高内聚、低耦合"。
4. AOP 本质:对重复代码的集中处理
AOP 的本质是将多处重复的代码(横切逻辑)抽离为 "切面",在不修改原有业务代码的前提下,自动织入到目标方法的指定位置(如方法前、方法后) 。
举例:用户模块的 "新增用户""删除用户" 方法都需要日志记录和权限校验,AOP 可将这两个逻辑抽离为切面,自动织入到目标方法中,业务方法只需保留核心逻辑。
二、手写实现 AOP 的核心逻辑
为理解 AOP 核心原理,我们手动实现一个简易 AOP 框架,聚焦 "如何识别目标方法、如何织入增强逻辑、如何生成代理对象" 三大核心问题。
1. 整体流程划分
AOP 运行可分为两个阶段:
启动阶段: 解析配置(如注解),确定需要增强的目标类、目标方法,以及要织入的增强逻辑(通知),最终生成代理对象。
调用阶段: 当代理对象的方法被调用时,触发增强逻辑与目标方法的执行(按顺序织入)。
2. AOP 启动流程
核心问题
问题 1:哪些类需要生成代理?哪些方法需要织入增强逻辑?
答:通过注解(如 @Aspect 标记切面类,@Before 标记前置通知)指定目标类和方法。
问题 2:方法前后需要织入什么样的逻辑?
答:切面类中被 @Before @After 等注解标记的方法,即为增强逻辑(通知)。
问题 3:如何绑定目标方法与增强逻辑?
答:通过 "切入点表达式"(如 execution(* com.example.service.. (...)))匹配目标方法,建立 "目标方法 → 增强逻辑链" 的映射关系。
问题 4:如何将目标对象转为代理对象?
答:使用 JDK 动态代理(针对接口)或 CGLIB 代理(针对类),生成代理对象替换原始对象。
启动流程实现步骤
步骤 1:定义核心注解(模拟 Spring AOP 注解)
java
// 标记切面类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
}
// 标记前置通知(目标方法执行前)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
String value(); // 切入点表达式,如 "com.example.service.UserService.add()"
}
// 标记后置通知(目标方法执行后)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
String value();
}
步骤 2:定义通知接口(封装增强逻辑)
java
// 通知接口,不同类型的通知实现此接口
public interface Advice {
void execute(Method method, Object[] args, Object target);
}
// 前置通知实现
public class BeforeAdvice implements Advice {
private Object aspect; // 切面实例
private Method adviceMethod; // 增强方法(如日志逻辑)
public BeforeAdvice(Object aspect, Method adviceMethod) {
this.aspect = aspect;
this.adviceMethod = adviceMethod;
}
@Override
public void execute(Method method, Object[] args, Object target) {
try {
// 调用切面中的增强方法(无参数简化版)
adviceMethod.invoke(aspect);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 后置通知实现(类似)
public class AfterAdvice implements Advice {
// 实现与 BeforeAdvice 类似,略
}
步骤 3:解析切面类,绑定目标方法与通知
java
public class AopConfig {
// 存储目标方法与通知链的映射:key为"类名.方法名",value为通知列表
private Map<String, List<Advice>> adviceMap = new HashMap<>();
public void parseAspect(Object aspect) throws Exception {
Class<?> aspectClass = aspect.getClass();
if (!aspectClass.isAnnotationPresent(Aspect.class)) {
return; // 非切面类,跳过
}
// 遍历切面类的所有方法,解析 @Before @After 注解
for (Method method : aspectClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
Before before = method.getAnnotation(Before.class);
String pointcut = before.value(); // 切入点表达式(如 "UserService.add")
// 将通知添加到映射中
addAdvice(pointcut, new BeforeAdvice(aspect, method));
} else if (method.isAnnotationPresent(After.class)) {
After after = method.getAnnotation(After.class);
String pointcut = after.value();
addAdvice(pointcut, new AfterAdvice(aspect, method));
}
}
}
private void addAdvice(String pointcut, Advice advice) {
// 简化处理:直接用切入点字符串作为key(实际应解析表达式匹配方法)
adviceMap.computeIfAbsent(pointcut, k -> new ArrayList<>()).add(advice);
}
// 根据目标方法获取通知链
public List<Advice> getAdvices(Method method) {
String key = method.getDeclaringClass().getSimpleName() + "." + method.getName();
return adviceMap.getOrDefault(key, new ArrayList<>());
}
}
步骤 4:生成代理对象
使用 JDK 动态代理生成代理对象,代理对象调用方法时会触发通知链:
java
public class ProxyFactory {
private AopConfig config;
public ProxyFactory(AopConfig config) {
this.config = config;
}
// 为目标对象创建代理
public Object createProxy(Object target) {
Class<?> targetClass = target.getClass();
// JDK 动态代理:需要目标类实现接口
return Proxy.newProxyInstance(
targetClass.getClassLoader(),
targetClass.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取当前方法的通知链
List<Advice> advices = config.getAdvices(method);
// 2. 执行通知链和目标方法
return new AdviceChain().proceed(method, args, target, advices);
}
}
);
}
}
启动流程总结
获取配置: 扫描并解析 @Aspect @Before 等注解,提取切面信息;
解析切面: 将切面中的增强方法封装为 Advice(前置 / 后置通知);
绑定关系: 通过切入点表达式建立 "目标方法 → 通知链" 的映射;
生成代理: 为目标对象创建代理对象,代理对象持有通知链信息。
3. AOP 调用流程
核心问题
问题 1:如何将增强逻辑织入目标方法?
答:代理对象的 invoke 方法拦截目标方法调用,先执行增强逻辑,再执行目标方法。
问题 2:多个增强方法的执行顺序如何保证?
答:通过 "索引 + 递归" 遍历通知链,按顺序执行前置通知 → 目标方法 → 后置通知。
调用流程实现:通知链执行
java
public class AdviceChain {
// 递归执行通知链
public Object proceed(Method method, Object[] args, Object target, List<Advice> advices) throws Throwable {
return proceed(0, method, args, target, advices);
}
private Object proceed(int index, Method method, Object[] args, Object target, List<Advice> advices) throws Throwable {
// 若所有通知执行完毕,调用目标方法
if (index == advices.size()) {
return method.invoke(target, args);
}
// 执行当前通知,递归执行下一个通知
Advice advice = advices.get(index);
advice.execute(method, args, target);
return proceed(index + 1, method, args, target, advices);
}
}
执行顺序:
前置通知按注册顺序执行 → 目标方法执行 → 后置通知按注册顺序执行(简化版,实际 Spring 有更复杂的排序逻辑)。
调用流程总结
拦截调用: 请求调用代理对象的方法,触发 InvocationHandler.invoke();
获取通知链: 根据目标方法从映射中获取对应的通知链;
执行链织入: 通过递归按顺序执行所有通知和目标方法。
4. 手写实现整体流程
启动阶段: 解析切面注解,绑定 "目标方法 - 通知链" 关系,生成代理对象替换原始对象;
调用阶段: 代理对象拦截方法调用,通过 "索引 + 递归" 执行通知链和目标方法;
核心映射: 启动时建立的 "方法 - 通知" 映射是调用时快速获取增强逻辑的关键;
简化说明: 手写实现未支持多切面和复杂切入点表达式,Spring AOP 对此做了完善。
三、Spring AOP 源码分析
Spring AOP 基于 IoC 容器,通过动态代理实现增强逻辑的织入,核心类包括 @EnableAspectJAutoProxy、AnnotationAwareAspectJAutoProxyCreator、ProxyFactory 等。
1. AOP 在 Spring 源码中的位置
Spring AOP 的核心动作发生在Bean 初始化后:当 IoC 容器创建 Bean 时,若该 Bean 被切面匹配,会自动为其生成代理对象,最终存入容器的是代理对象而非原始对象。
2. @EnableAspectJAutoProxy 注解的作用
@EnableAspectJAutoProxy 是开启 Spring AOP 的开关,通过导入 AspectJAutoProxyRegistrar 注册 AOP 核心组件:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AspectJAutoProxyRegistrar.class) // 导入注册器
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false; // 是否强制使用CGLIB代理(默认JDK动态代理)
boolean exposeProxy() default false; // 是否暴露代理对象(解决自调用问题)
}
AspectJAutoProxyRegistrar 会向容器注册 AnnotationAwareAspectJAutoProxyCreator(核心处理器):
java
public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注册 AnnotationAwareAspectJAutoProxyCreator
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 处理 proxyTargetClass 和 exposeProxy 属性
// ...
}
}
3. AnnotationAwareAspectJAutoProxyCreator 的执行时机
AnnotationAwareAspectJAutoProxyCreator 是 Spring AOP 的核心处理器,实现了 BeanPostProcessor 接口,在Bean 初始化后(postProcessAfterInitialization 方法)为符合条件的 Bean 生成代理对象。
执行流程:
- 扫描切面: 容器启动时,扫描所有 @Aspect 注解的切面类;
- 解析通知: 将切面中的 @Before @After 等注解方法解析为 Advisor(通知器,包含切入点和通知);
- 匹配目标: 对每个初始化后的 Bean,检查是否被任何 Advisor 的切入点匹配;
- 生成代理: 若匹配,通过 ProxyFactory 生成代理对象。
4. Spring AOP 如何生成代理对象
代理对象的生成由 ProxyFactory 负责,核心逻辑在 getProxy() 方法:
java
public class ProxyFactory extends ProxyCreatorSupport {
public Object getProxy() {
return createAopProxy().getProxy();
}
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 根据目标类和配置选择代理方式
return getAopProxyFactory().createAopProxy(this);
}
}
AopProxyFactory 选择代理方式的逻辑:
- 若目标类实现接口,默认使用 JdkDynamicAopProxy(JDK 动态代理);
- 若目标类未实现接口或 proxyTargetClass = true,使用 CglibAopProxy(CGLIB 代理)。
5. 代理对象的调用流程
当代理对象的方法被调用时,JDK 代理会触发 JdkDynamicAopProxy.invoke() 方法,CGLIB 代理会触发 CglibAopProxy.intercept() 方法,核心逻辑一致:
java
// JdkDynamicAopProxy 的 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 1. 检查是否需要暴露代理对象(解决自调用问题)
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// 2. 获取目标对象
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 3. 获取当前方法的拦截器链(通知链)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 4. 若没有拦截器,直接调用目标方法
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
// 5. 创建 MethodInvocation,执行拦截器链
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed(); // 执行链
}
// 6. 处理返回值
// ...
return retVal;
} finally {
// 清理资源
// ...
}
}
拦截器链执行: ReflectiveMethodInvocation.proceed() 采用 "索引 + 递归" 模式,与手写实现的 AdviceChain 逻辑类似,按顺序执行所有通知和目标方法。
四、Spring 如何实现多层代理
当一个 Bean 被多个切面 增强时,Spring 会生成多层代理 (代理的代理),每层代理对应一个切面的增强逻辑。
原理
多层代理生成: 第一个切面为原始对象生成代理 1,第二个切面为代理 1 生成代理 2,以此类推,最终容器中存储的是最外层代理;
调用顺序: 外层代理先执行自身切面的通知,再调用内层代理,直到原始对象的方法执行,然后按相反顺序执行后置通知。
举例:A 被切面 1 和切面 2 增强,生成代理 2(外层,切面 2)→ 代理 1(内层,切面 1)→ 原始对象 A。
调用流程:
代理2.invoke() → 切面 2 前置通知 → 代理1.invoke() → 切面 1 前置通知 → A.方法() → 切面 1 后置通知 → 切面 2 后置通知。
源码支撑
多层代理由 AnnotationAwareAspectJAutoProxyCreator 循环处理实现:
- 每次为 Bean 生成代理后,会再次检查代理对象是否被其他切面匹配;
- 若匹配,以当前代理对象为目标,生成新的代理对象,直到没有匹配的切面。
核心逻辑在 AbstractAutoProxyCreator.postProcessAfterInitialization():
java
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 若需要代理,生成代理对象(可能是多层代理)
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 检查是否需要代理
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 获取匹配的通知器(Advisor)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 生成代理对象(若已有代理,会基于现有代理再生成新代理)
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
每次调用 createProxy 都会基于当前对象(可能是原始对象或已有代理)生成新的代理,从而实现多层代理。
总结
Spring AOP 的核心是通过动态代理 和切面织入,实现通用逻辑与业务逻辑的分离。从思想到实现,其核心流程可归纳为:
- 设计思想: AOP 作为横切编程思想,解决代码冗余和耦合问题,Spring AOP 是其成熟实现;
- 手写实现: 通过解析配置、绑定 "方法 - 通知" 关系、生成代理对象,模拟 AOP 核心逻辑,关键是 "代理拦截 + 通知链执行";
- Spring 源码: @EnableAspectJAutoProxy 开启AOP,AnnotationAwareAspectJAutoProxyCreator 负责代理生成,ProxyFactory选择代理方式,MethodInvocation 执行通知链;
- 多层代理: 通过循环生成代理,实现多切面增强,调用时从外层到内层依次执行。
理解 AOP 原理不仅能帮助我们灵活使用 @Aspect 等注解,更能在复杂场景下(如自定义切面、解决自调用问题)写出更优雅的代码。AOP 与 IoC 的结合,正是 Spring 框架强大灵活性的根源。