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

手撕Spring底层系列之:IOC、AOP

人们眼中的天才之所以卓越非凡,并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆·格拉德威尔


目录

一、开篇:为什么必须理解Spring底层?

二、IOC容器:控制反转的本质解析

2.1 核心机制:工厂模式 + 反射

2.2 容器启动六步流程

2.3 循环依赖的破解之道

三、AOP设计:动态代理的两种武器

3.1 AOP 概念

3.2 AOP 实现思想

3.3 JDK动态代理 vs CGLIB字节码增强

3.4 应用场景

四、总结


🌟 嗨,我是Xxtaoaooo!

本系列将用源码解剖+拆分核心轮子的方式,带你暴力破解Spring底层逻辑。

警告:阅读后可能导致看Spring源码时产生「庖丁解牛」般的快感!

话不多说,直接开干!


一、开篇:为什么必须理解Spring底层?

        作为数年Java程序猿,我曾陷入“会用Spring却不懂Spring”的尴尬境地。当@Autowired突然失效、事务注解不回滚时,盲目搜索解决方案的挫败感让我决心撕开Spring的优雅封装。本系列文章正是我个人深入了解源码后提炼的核心设计精华,你将看到:

  • IoC容器如何用三级缓存破解循环依赖的魔鬼细节
  • AOP动态代理的选择逻辑(JDK vs CGLIB)
  • Bean生命周期中9个关键扩展点的实战应用
  • 如何手写简化版Spring核心模块


二、IOC容器:控制反转的本质解析

2.1 核心机制:工厂模式 + 反射

  • 工厂模式ApplicationContext作为中央工厂,替代传统的new对象方式
  • 反射机制:运行时动态解析类信息并实例化对象
// 反射实现Bean实例化简化代码
public class SimpleBeanFactory {private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();public Object getBean(String beanName) throws Exception {Object bean = singletonObjects.get(beanName);if (bean != null) return bean;// 反射创建对象(简化版)Class<?> clazz = Class.forName(beanName);bean = clazz.getDeclaredConstructor().newInstance();singletonObjects.put(beanName, bean);return bean;}
}

2.2 容器启动六步流程

1. A[启动 refresh() 方法]:

    • 这是整个容器初始化的入口。无论是通过new ClassPathXmlApplicationContext(...)还是new AnnotationConfigApplicationContext(...),最终都会调用refresh()方法来启动容器。

2. B[创建并准备 BeanFactory]:

    • BeanFactory是Spring IoC容器的核心。在此阶段,会创建一个DefaultListableBeanFactory实例,并对其进行一些基础设置,比如设置类加载器。

3. C[扫描并加载 BeanDefinitions]:

    • Spring会扫描指定的配置源(如XML文件、注解@Component/@Service等),将找到的Bean解析成BeanDefinition对象。
    • BeanDefinition是Bean的“蓝图”或“配方”,包含了Bean的类名、作用域、依赖关系、属性等元数据。这些BeanDefinition会被注册到BeanFactory中。

4. D[注册 BeanPostProcessors]:

    • BeanPostProcessor是Spring提供的一个强大扩展点,它允许在Bean实例化、配置和初始化前后进行自定义处理(例如,AOP就是通过它实现的)。
    • 这一步非常关键,它会找出所有实现了BeanPostProcessor接口的Bean,并将它们注册到BeanFactory中,以便在后续的Bean创建过程中使用。

5. E[初始化所有非懒加载的单例Bean]:

    • 这是容器初始化的核心工作。Spring会遍历BeanFactory中所有的BeanDefinition,并根据定义创建Bean实例(对于非懒加载的单例Bean)。
    • 这个过程包括了:实例化(调用构造函数)、填充属性(依赖注入)、执行初始化回调(如@PostConstruct方法或afterPropertiesSet方法),以及应用BeanPostProcessor

6. F[发布上下文就绪事件]:

    • 当所有单例Bean都初始化完成后,Spring会发布一个ContextRefreshedEvent事件。
    • 这标志着容器已经完全准备就绪,可以对外提供服务。其他应用程序组件可以监听这个事件来执行一些启动后的逻辑(例如,启动一个定时任务)。

总结为:

  1. 配置解析:读取XML/注解的Bean定义
  2. BeanFactory构建:初始化DefaultListableBeanFactory
  3. 后置处理器注册:处理@Autowired等注解逻辑
  4. 预实例化单例:非懒加载Bean的初始化
  5. 依赖注入:通过populateBean()填充属性
  6. 生命周期回调:执行init-method@PostConstruct

2.3 循环依赖的破解之道

三级缓存是解决循环依赖的核心数据结构:

public class DefaultSingletonBeanRegistry {// 一级缓存:完整Beanprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:早期引用(未填充属性)private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存:Bean工厂(可生成代理对象)private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

单向对象注入的代码验证

<bean id="userDao" class="org.xt.dao.impl.UserDaoImpl"></bean><bean id="userService" class="org.xt.service.impl.UserServiceImpl"><property name="name" value="123"></property><property name="userDao" ref="userDao"></property>
</bean>

容器中有userDao Bean对象的执行流程:

userDao创建 → userService创建

→ userService执行注入userDao的操作: setUserDao方法执行

<bean id="userService" class="org.xt.service.impl.UserServiceImpl"><property name="name" value="123"></property><property name="userDao" ref="userDao"></property>
</bean><bean id="userDao" class="org.xt.dao.impl.UserDaoImpl"></bean>

容器中没有userDao Bean对象的执行流程:

userService创建 → userDao创建

→ userService执行注入userDao的操作: setUserDao方法执行

双向对象引用

循环引用(依赖):多个实体之间相互依赖并形成闭环的情况。

循环引用的解决方法:

Spring提供了三级缓存存储完整Bean实例和半成品Bean实例,用于解决循环引用问题。

三级缓存存储未被引用的对象,如果找到未被引用的对象,那么将从三级缓存删除,放入二级缓存,一级缓存是存完成的bean对象。

结合dao和service层循环引用结合三级缓存进行解析:


三、AOP设计:动态代理的两种武器

3.1 AOP 概念

什么是AOP?

        AOP Aspect Oriented Programming 顾名思义就是面向切面编程,是对面向对象编程OOP的一个升华,面向对象编程是纵向对一个事物的抽象,一个对象包括静态属性信息,动态方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、属性与方法、方法与方法、都可以组成一个切面,这种思维去设计编程的方式被称为面向切面编程。

3.2 AOP 实现思想

        其思想实现方案依靠动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法。动态代理技术主要依靠Java的反射机制来实现。

Java提供了两种动态代理实现的方式:基于接口的动态代理(JDK动态代理)和基于类的动态代理(CGLIB动态代理)。JDK动态代理只能代理实现了接口的类,而CGLIB动态代理可以代理任意的类,但其原理是通过生成目标类的子类来实现代理。

A对象是目标对象,B对象是增强对象

A对象内部的方法被称为目标方法

B对象内部的方法被称为增强方法

模拟AOP实现的基础代码

public interface UserService {void show1();void show2();
}//目标对象
public class UserServiceImpl implements UserService {@Overridepublic void show1() {System.out.println("show1...");}@Overridepublic void show2() {System.out.println("show2...");}
}//增强类,内部提供增强方法
public class MyAdvice {public void beforeAdvice(){System.out.println("前置的增强...");}public void afterAdvice(){System.out.println("后置的增强...");}
}public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {ApplicationContext applicationContext;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//  目的:对UserServiceImpl中的show1和show2方法进行增强,增强方法存在MyAdvice中//  问题1:筛选UserServiceImpl或者service.impl包下的所有方法都可以进行增强 解决方案:添加判断//  问题2:MyAdvice怎么获取到 解决方法:从spring容器中获取if(bean.getClass().getPackage().getName().equals("org.xt.service.impl")){//生成当前Bean的Proxy对象Object beanProxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(proxy, method, args) -> {//执行增强对象的Before方法MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);myAdvice.beforeAdvice();//执行目标对象的目标方法Object result = method.invoke(bean, args);//执行增强对象的After方法myAdvice.afterAdvice();return result;});return beanProxy;}return bean;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

3.3 JDK动态代理 vs CGLIB字节码增强

对比维度

JDK动态代理

CGLIB

代理对象要求

必须实现接口

可代理任意类

性能

调用快,创建慢

创建快,调用稍慢

底层技术

Java反射机制

ASM字节码操作

方法拦截器

InvocationHandler

MethodInterceptor

适用场景

接口-based代理

类继承-based代理

代理创建核心源码解析

// DefaultAopProxyFactory选择代理方式
public AopProxy createAopProxy(AdvisedSupport config) {if (config.isOptimize() || config.isProxyTargetClass()) {return new CglibAopProxy(config); // 使用CGLIB}return new JdkDynamicAopProxy(config); // 使用JDK代理
}// JDK代理调用流程
public Object invoke(Object proxy, Method method, Object[] args) {// 1. 获取目标对象Object target = getTarget();// 2. 获取拦截器链List<Object> chain = getInterceptorsAndDynamicInterceptionAdvice(method);// 3. 执行拦截器链return new ReflectiveMethodInvocation(target, method, args, chain).proceed();
}

通知(Advice)执行时序

3.4 应用场景

IOC容器的典型应用

AOP在微服务中的实战

// 分布式事务切面示例
@Aspect
public class GlobalTransactionAspect {@Around("@annotation(com.distributed.tx)")public Object handleTransaction(ProceedingJoinPoint joinPoint) {try {// 1. 创建全局事务IDString txId = UUID.randomUUID().toString();// 2. 通知参与者注册事务TransactionManager.register(txId);// 3. 执行目标方法Object result = joinPoint.proceed();// 4. 提交事务TransactionManager.commit(txId);return result;} catch (Throwable e) {// 5. 回滚事务TransactionManager.rollback(txId);throw new RuntimeException(e);}}
}

四、总结

优秀的框架设计,往往是把复杂留给自己,把简单留给使用者。
Spring的成功正是这一理念的完美实践——通过IoC容器标准化对象生命周期,通过AOP解耦横切关注点,最终实现开发效率的质的飞跃。

回顾本系列的核心收获:

  1. IoC的本质是管理权转移:将对象的控制权从程序员手中移交至容器,通过三级缓存等精妙设计解决工程难题
  2. AOP的根基是代理模式:动态代理技术使非侵入式功能扩展成为可能,JDK与CGLIB的互补成就了Spring的灵活性
  3. 手写轮子的终极意义:当完成手搓AOP实现时,那些曾令人困惑的面试题(如Bean生命周期、循环依赖)将自然内化为你的设计能力

致每一位不甘于CRUD的开发者:撕开框架底层的过程如同攀登技术高原,虽充满挑战,但站在设计思想的顶峰时,你看到的将不再是零散的API,而是一片可自由创造的新大陆。


🌟 嗨,我是Xxtaoaooo!
⚙️ 【点赞】让更多同行看见深度干货
🚀 【关注】持续获取行业前沿技术与经验
🧩 【评论】分享你的实战经验或技术困惑

作为一名技术实践者,我始终相信:

每一次技术探讨都是认知升级的契机,期待在评论区与你碰撞灵感火花🔥

http://www.xdnf.cn/news/15609.html

相关文章:

  • java操作Excel两种方式EasyExcel 和POI
  • 跟着Carl学算法--回溯【2】
  • React Hooks 数据请求库——SWR使用详解
  • Spring AI 系列之十四 - RAG-ETL之一
  • Vue3+Ts实现父子组件间传值的两种方式
  • Unity Android Logcat插件 输出日志中文乱码解决
  • 小白成长之路-Elasticsearch 7.0 配置
  • BNN 技术详解:当神经网络只剩下 +1 和 -1
  • 基于redis的分布式锁 lua脚本解决原子性
  • 免杀学习篇(1)—— 工具使用
  • 网页源码保护助手 海洋网页在线加密:HTML 源码防复制篡改,密文安全如铜墙铁壁
  • 基于华为欧拉系统安装FileGator文件管理器
  • 【Android】日志的使用
  • 深度学习中的激活函数:从原理到 PyTorch 实战
  • python 基于 httpx 的流式请求
  • 场景设计题+智力题
  • [Science]论文 视黄素与细胞修复
  • C++回顾 Day7
  • PyCharm 高效入门指南:从安装到效率倍增
  • [面试] 手写题-对象数组根据某个字段进行分组
  • 学习嵌入式的第二十八天-数据结构-(2025.7.15)进程和线程
  • P3842 [TJOI2007] 线段
  • Web攻防-PHP反序列化字符逃逸增多减少成员变量属性解析不敏感Wakeup绕过
  • 高等数学强化——导学
  • Android中Launcher简介
  • deepseekAI对接大模型的网页PHP源码带管理后台(可实现上传分析文件)
  • ASP .NET Core 8结合JWT轻松实现身份验证和授权
  • SpringBoot 实现 Redis读写分离
  • “C21988-谷物烘干机(2D+3D+说明书+运动仿真)8张cad+设计说明书
  • pytorch学习笔记(四)-- TorchVision 物体检测微调教程