当前位置: 首页 > news >正文

Spring AOP核心原理分析

Spring AOP 核心原理:从思想到源码的深度剖析

AOP(面向切面编程)是 Spring 框架三大核心思想之一,与 IoC 相辅相成,共同支撑起 Spring 灵活、低耦合的架构设计。AOP 通过 “横切” 的方式分离业务逻辑与通用功能(如日志、事务、权限),彻底解决了代码冗余和耦合问题。
本次学习将从 AOP 设计思想出发,通过手写简易 AOP 理解核心逻辑,再深入 Spring AOP 源码,以掌握AOP其实现原理。

一、AOP 设计思想

1. Spring 的三大核心:IoC、DI 与 AOP

Spring 框架的强大源于三大核心思想的协同作用:

  1. IoC(控制反转):将对象创建权交给容器,解耦对象依赖;
  2. DI(依赖注入): 容器自动为对象注入依赖,简化对象组装;
  3. 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. 核心价值:隔离业务逻辑,降低耦合

传统开发中,通用功能(如日志、事务)会嵌入到业务代码中,导致:

  1. 代码冗余: 同一逻辑在多个业务方法中重复编写;
  2. 耦合严重: 修改通用逻辑需改动所有业务代码;
  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 注解)

// 标记切面类
@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:定义通知接口(封装增强逻辑)

// 通知接口,不同类型的通知实现此接口
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;}@Overridepublic void execute(Method method, Object[] args, Object target) {try {// 调用切面中的增强方法(无参数简化版)adviceMethod.invoke(aspect);} catch (Exception e) {e.printStackTrace();}}
}// 后置通知实现(类似)
public class AfterAdvice implements Advice {// 实现与 BeforeAdvice 类似,略
}

步骤 3:解析切面类,绑定目标方法与通知

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 动态代理生成代理对象,代理对象调用方法时会触发通知链:

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() {@Overridepublic 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:多个增强方法的执行顺序如何保证?
答:通过 “索引 + 递归” 遍历通知链,按顺序执行前置通知 → 目标方法 → 后置通知。

调用流程实现:通知链执行

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 核心组件:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AspectJAutoProxyRegistrar.class) // 导入注册器
public @interface EnableAspectJAutoProxy {boolean proxyTargetClass() default false; // 是否强制使用CGLIB代理(默认JDK动态代理)boolean exposeProxy() default false; // 是否暴露代理对象(解决自调用问题)
}

AspectJAutoProxyRegistrar 会向容器注册 AnnotationAwareAspectJAutoProxyCreator(核心处理器):

public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 注册 AnnotationAwareAspectJAutoProxyCreatorAopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);// 处理 proxyTargetClass 和 exposeProxy 属性// ...}
}

3. AnnotationAwareAspectJAutoProxyCreator 的执行时机

AnnotationAwareAspectJAutoProxyCreator 是 Spring AOP 的核心处理器,实现了 BeanPostProcessor 接口,在Bean 初始化后(postProcessAfterInitialization 方法)为符合条件的 Bean 生成代理对象。

执行流程:

  1. 扫描切面: 容器启动时,扫描所有 @Aspect 注解的切面类;
  2. 解析通知: 将切面中的 @Before @After 等注解方法解析为 Advisor(通知器,包含切入点和通知);
  3. 匹配目标: 对每个初始化后的 Bean,检查是否被任何 Advisor 的切入点匹配;
  4. 生成代理: 若匹配,通过 ProxyFactory 生成代理对象。

4. Spring AOP 如何生成代理对象

代理对象的生成由 ProxyFactory 负责,核心逻辑在 getProxy() 方法:

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 选择代理方式的逻辑:

  1. 若目标类实现接口,默认使用 JdkDynamicAopProxy(JDK 动态代理);
  2. 若目标类未实现接口或 proxyTargetClass = true,使用 CglibAopProxy(CGLIB 代理)。

5. 代理对象的调用流程

当代理对象的方法被调用时,JDK 代理会触发 JdkDynamicAopProxy.invoke() 方法,CGLIB 代理会触发 CglibAopProxy.intercept() 方法,核心逻辑一致:

// 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 循环处理实现:

  1. 每次为 Bean 生成代理后,会再次检查代理对象是否被其他切面匹配;
  2. 若匹配,以当前代理对象为目标,生成新的代理对象,直到没有匹配的切面。

核心逻辑在 AbstractAutoProxyCreator.postProcessAfterInitialization():

@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 的核心是通过动态代理切面织入,实现通用逻辑与业务逻辑的分离。从思想到实现,其核心流程可归纳为:

  1. 设计思想: AOP 作为横切编程思想,解决代码冗余和耦合问题,Spring AOP 是其成熟实现;
  2. 手写实现: 通过解析配置、绑定 “方法 - 通知” 关系、生成代理对象,模拟 AOP 核心逻辑,关键是 “代理拦截 + 通知链执行”;
  3. Spring 源码: @EnableAspectJAutoProxy 开启AOP,AnnotationAwareAspectJAutoProxyCreator 负责代理生成,ProxyFactory选择代理方式,MethodInvocation 执行通知链;
  4. 多层代理: 通过循环生成代理,实现多切面增强,调用时从外层到内层依次执行。

理解 AOP 原理不仅能帮助我们灵活使用 @Aspect 等注解,更能在复杂场景下(如自定义切面、解决自调用问题)写出更优雅的代码。AOP 与 IoC 的结合,正是 Spring 框架强大灵活性的根源。

http://www.dtcms.com/a/539991.html

相关文章:

  • HTTPS 错误排查实战,从握手到应用层的工程化流程
  • 基于ASP身份认证服务器实现远程办公VPN双因素认证的架构与实践
  • 服务器会遭受到哪些网络攻击
  • 网站设计制作软件江门自助建站模板
  • 滨州做网站推广h5制作方法
  • KTM5800——30bit 绝对角度细分器支持最多 4096 对极与一键非线性自校准集成双 16bit 2M SAR ADC,可替代TW29
  • 局域网网站制作保定哪有做网站的
  • 基于LPJ模型的植被NPP模拟、驱动力分析及其气候变化响应预测
  • 人脸识别4-Windows下基于MSVC编译SeetaFace6
  • 【AES加密】AES加密算法流程全解析
  • 5.1.4 大数据方法论与实践指南-主流湖仓一体商业化解决方案
  • 【数据库】异构多活+双轨并行:浙人医基于金仓KFS实现数据库信创平滑升级
  • Python实用装饰器提升开发效率
  • 【JAVA 进阶】Mybatis-Plus 实战使用与最佳实践
  • LangGraph 官方教程:聊天机器人之五
  • 天硕工业SSD揭秘无DRAM缓存SSD的性能差距
  • C# 内存是绝对自动清理吗?
  • 在 CentOS 系统上实现定时执行 Python 邮件发送任务完整指南
  • C#操作Excel
  • 放置在网站根目录下中国做外贸最好的网站有哪些
  • 二叉搜索树,咕咕咕
  • 可用 Docker (DockerHub) 国内镜像源加速列表 - 长期维护(截至 2025 年 06 月 15 日)
  • QtQuick3D入门(5):实例化渲染
  • 浙人医基于金仓 KFS 工具信创落地:多数据库协同难题解决方案详讲
  • [C++STL] :list的简介和使用
  • Nacos配置中心实战进阶:多场景动态刷新全解析
  • Linux写sh开机启动脚本-bash报错的两种解决方法
  • 注册协议通知
  • wordpress网站部署百度一下一下你就知道
  • 健康濮阳门户网站建设装企erp管理系统