Spring核心机制

张开发
2026/4/10 14:39:53 15 分钟阅读

分享文章

Spring核心机制
Bean 的生命周期扫描 class 文件Spring Main 方法启动首先就是扫描用户写的 class 文件基于ComponentScan 注解获取路径信息创建 BeanDefinition扫描 class 文件创建 BeanDefinition 对象存入 BeanDefinitionMapkey 为 beanName通过反射获取该 class 文件的注解看看是否有Component获取注解内的 beanName没有 name 则生成默认 name。BeanDefinition 对象属性主要有 bean 的 class 以及其模式 scope是否为单例依旧是反射获取 class 的注解看是否有Scope获取其值初始化单例 Bean通过遍历 BeanDefinitionMap 获取 BeanDefinition根据 scope 字段判断是否为 singleton是就 createBean将 Bean 放入 singletonObject-单例池单例 map中key 依旧为 beanNamegetBeanbeanName根据 name 从 BeanDefinitionMap 中获取 BeanDefinition 对象查看 scope 属性是否为 singleton是则就从单例池 singletonObject 中获取 bean为 prototype 代表为多例模式则调用 createBean创建 beancreateBeanname通过 name 获取 BeanDefinition再获取其 class 对象通过反射获取构造器并实例化对象将实例化 bean 返回依赖注入注入时机当一个 Bean 创建的时候就应该初始化 Bean将其依赖的 Bean 注入进去所以是在 createBean 中实例化 bean 后进行依赖注入依赖注入获取类的所有公开字段 Fields遍历字段查看是否有注解Autowired用反射赋值需要先将该字段 setAccessibeltrue然后根据该字段的 namegetBeanname去 Spring 容器中找对应 bean 并 set完成依赖注入Aware 回调如何获取 beanName我们在开发时有时候需要获取到当前类的 beanNameSpring 实现了一个接口 BeanNameAware如果我们需要在这个类获取 beanName就实现这个接口实现其 setBeanNamebeanName方法获得时机在该类的依赖注入后会检查该类是否实现了 BeanNameAware 接口如果实现了就调用该实例 bean 的 setBeanNamebeanName方法beanName 即为所需值由 Spring 传入给用户用户只需 this.beanName beanName 即可获得初始化机制使用场景我们希望 Bean 加载后就自动调用某个方法初始化某些属性或打印一些信息实现与 Aware 回调类似Spring 定义接口用户类实现接口实现初始化方法自定义初始化方法逻辑初始化时机与 Aware 回调相邻如果该类实现了 InitializingBean 接口就调用该实例的 init方法实现初始化与 Aware 回调不同的是初始化不传参AOP 机制BeanPostProcessor 机制Spring 定义了一个接口 BeanPostProcessor以及两个方法 postProcessBeforeInitialization 与 postProcessAfterInitialization用户类只需要实现这个接口重写这两个方法逻辑即可实现在 bean 初始化前后统一添加处理处理时机在类扫描阶段扫描出类有Component 后创建 BeanDefination 前会判断这个类是否实现了 BeanPostProcessor 接口有则生产该类的一个实例对应接口吸收new 该类方便后续调用不同的实现方法并将其放入到beanPostProcessorList中我们可以实现多个类似接口实现在初始化前后执行多个方法干不同的事儿。在 createBean的逻辑中有 init逻辑只需在执行 init前后分别从beanPostProcessorList中获取对应实例并执行用户实现的的 postProcessBeforeInitialization 与 postProcessAfterInitialization 方法即可存在的问题到这里只是实现了在 bean 初始化前后做了一些事情只是在 Bean 生命周期的特定时刻被 Spring 容器调用它自身并不能改变 Bean 的方法字节码并未实现 AOP 功能// 1. 传统BeanPostProcessor方式只能做简单通知无法修改方法逻辑ComponentpublicclassLoggingBeanPostProcessorimplementsBeanPostProcessor{OverridepublicObjectpostProcessAfterInitialization(Object bean,String beanName){System.out.println(Bean [beanName] 初始化完成);// ❌ 这里无法对bean的方法添加日志逻辑returnbean;// 返回的还是原bean}}真正的包装通过代理实现。代理对象重写原对象的方法在原对象的方法执行前后添加代理逻辑注意代理对象调用的不是 super.test来执行原对象方法这是不行的因为 super 对象并没有被依赖注入调用的是最初的普通对象 target.test。原对象target不会被单独放入单例池它只作为代理对象的内部引用存在// Spring AOP 的核心处理器实现了 BeanPostProcessorpublicclassAnnotationAwareAspectJAutoProxyCreatorextendsAbstractAdvisorAutoProxyCreator{// 这是一个后处理器在Bean初始化之后执行OverridepublicObjectpostProcessAfterInitialization(NullableObject bean,String beanName){// 关键它不直接对bean进行处理而是判断是否需要为其创建一个“代理对象”if(bean!null){Object cacheKeygetCacheKey(bean.getClass(),beanName);if(!this.earlyProxyReferences.contains(cacheKey)){// ⭐⭐⭐ 核心方法如果需要增强则创建代理对象并返回returnwrapIfNecessary(bean,beanName,cacheKey);}}returnbean;// 不需要增强返回原对象}protectedObjectwrapIfNecessary(Object bean,String beanName,Object cacheKey){// 1. 检查这个Bean是否已经处理过或是否是基础类等// 2. 获取适用于这个Bean的所有“增强器”(Advisor)Object[]specificInterceptorsgetAdvicesAndAdvisorsForBean(bean.getClass(),beanName,null);// 3. 如果有匹配的增强器if(specificInterceptors!DO_NOT_PROXY){this.advisedBeans.put(cacheKey,Boolean.TRUE);// ⭐⭐⭐ **创建代理对象**Object proxycreateProxy(bean.getClass(),beanName,specificInterceptors,newSingletonTargetSource(bean));// 4. 将代理对象返回替换掉原来的beanreturnproxy;}// ... 否则返回原bean}}BeanPostProcessor 与 AOPBeanPostProcessor 是 AOP 的触发器在postProcessAfterInitialization方法中会查看该对象是否开启了 AOP 增强如果有则创建代理对象实现逻辑增强并将代理对象存入单例池 Map。1.扫描Component等注解类 ↓2.生成BeanDefinition→BeanDefinitionMap↓3.提前初始化所有BeanPostProcessor↓4.遍历BeanDefinition初始化普通Bean ├─4.1推断构造方法实例化对象-普通对象target ├─4.2属性填充依赖注入 ├─4.3调用Aware接口BeanNameAware等 ├─4.4BeanPostProcessor.postProcessBeforeInitialization()├─4.5初始化方法PostConstruct→ afterPropertiesSet → init-method ├─4.6BeanPostProcessor.postProcessAfterInitialization()│ ⭐ 如果Bean需要AOP这里会创建并返回代理对象 └─4.7将最终对象可能是代理放入单例池 普通对象Target--依赖注入--初始化前(BPP)--初始化--初始化后(BPP创建代理)--【代理对象】--放入单例池 ↑|└──────────────────── 代理对象内部持有 target 引用 ──────────────────────┘Transactional底层基于 AOP 实现classUserServiceProxyextendsUserService{UserServicetarget;Overwritepublicvoidtest(){// Srping事务切面逻辑检查这个方法是否有Transactional// 有则开启事务// 1.事务管理器新建一个数据库连接connection// 2.connection.autocommit false关闭自动提交target.test();// 调用普通对象原方法// 这里执行sql是jdbcTemplate拿到我们上面创建的conn执行// 这里判断是否有异常是conn.commit()还是conn。rellback()}}事务失效场景事务失效的核心原因就一个事务是靠 AOP 代理实现的凡是绕过代理或者代理机制本身搞不定的情况事务就会失效。Transactionalpublicvoid test(){ jdbcTemplate.execute(sql:insert into t1 values(1,1,1,1, 1) ; a(); //UserService普通对象.a()a的事务不会生效导致2个sql都插入成功 // 原因代理对象调用test()时是target.test()再调用a时也是target.test() // target普通对象不是代理对象并没有实现如上代码的事务切面逻辑来开启事务 // 解决办法 1.另其一个类UserService2将a()方法放进去注入到UserService中 UserService2在bean的生命周期中最终是生成代理对象放入单例池中的 因为在AOP后处理逻辑中扫描到了有Transactional需要代理增强 2.通过AopContext.currentProxy拿到该target的代理对象UserServiceProxy 去执行代理对象的a()方法自然有事务切面逻辑a()事务生效 3.在UserService中注入自己注入的就是代理bean直接userService.a()执行 这里的循环依赖问题Spring自己解决了 } Transactional(propagation Propagation.NEVER) - 如果在一个事务中就报错 public void a(){ jdbcTemplate.execute( sql: insertintot1values(2,2,2,2,2)}注意在事务的切面逻辑中jdbcTemplate 需要拿到事务管理器创建的 connection 才能实现事务如果拿不到jdbc 会自己创建连接那还是 autocommit trueSQL 运行即提交事务失效。插播一句Configuration、AOP、Lazy 都是基于动态代理的他们是平级的通过 ThreadLocal 拿到事务管理器创建的 connection事务管理器创建连接、jdbc 执行 SQL 都是在一个线程内执行的所以可以通过 ThreadLocal 拿到连接信息但是 jdbc 获取 dataSource 与事务管理器获取 dataSource 时都是创建一个新的对象没法从 ThreadLocal 中获取对应的 connectionThreadLocalMap 存的是{dataSource — connection}这就需要我们在配置类 AppConfig 上加Configuration 来解决这个问题这里的配置类里配置有事务管理器以及 jdbcTemplateAppConfig 加了Configuration 注解在 bean 的周期中就会创建 AppConfigProxy 代理类代理类去执行事务管理器、jdbcTemplate 相关方法当发现需要 dataSource 对象时就去单例池中找没找到就创建一个放到单例池中从而实现事务管理器与 jdbcTemplate 的 dataSource 是同一个对象可以去 ThreadLocal 中查找对应 connection。正常逻辑是两个方法jdbc 连接法、事务管理器连接法里自己 new dataSource是两个不同对象。循环依赖ComponentpublicclassAService{LazyAutowireBServicebservice;Async--后续介绍需要当前不需要 publivvoidtest(){}}ComponentpublicclassBService{AutowireAServiceaservice;}先看 AService 的 Bean 的生命周期实例化 — 普通对象 target依赖注入填充 bService — 单例池 Map — 创建 BServiceBService 的 Bean 的生命周期1.实例化 — target2.依赖注入填充 aService — 单例池 Map — 创建 AService…3.填充其他属性4.做一些其他事情5.添加到单例池填充其他属性做一些其他的事情AOP、Aware 回调、初始化添加到单例池Spring 使用三级缓存singletonFactories的核心目的是在循环依赖场景下延迟生成代理对象确保最终注入的是同一个代理实例同时避免重复 AOP 代理。一级缓存singletonObjects存放完全初始化好的单例 Bean已完成所有生命周期。二级缓存earlySingletonObjects存放提前暴露的 Bean 实例已解决循环依赖但未完成初始化。三级缓存singletonFactories存放对象工厂ObjectFactory用于延迟生成代理对象关键所在。这里用黄色来标记解决思路 先看AService的Bean的生命周期1.实例化---普通对象target---放入myMap2.依赖注入填充bService---单例池Map---创建BService BService的Bean的生命周期1.实例化---target---放入myMap2.依赖注入填充aService---单例池Map---没有就去myMap找---AService普通对象不用创建了打破了循环依赖3.填充其他属性4.做一些其他事情5.添加到单例池3.填充其他属性4.做一些其他的事情AOP、Aware回调、初始化5.添加到单例池这里用红色来标记存在的问题 先看AService的Bean的生命周期1.实例化---普通对象target---放入myMap2.依赖注入填充bService---单例池Map---创建BService BService的Bean的生命周期1.实例化---target---放入myMap2.依赖注入填充aService---单例池Map---没有就去myMap找---AService普通对象 不用创建了打破了循环依赖3.填充其他属性4.做一些其他事情5.添加到单例池3.填充其他属性4.做一些其他的事情AOP、Aware回调、初始化---AOP产生aService代理对象 BService中的aService是普通对象但AService是代理对象5.添加到单例池这里用绿色来标记解决方法 先看AService的Bean的生命周期1.实例化---普通对象target---直接AOP生成a代理对象---放入myMap2.依赖注入填充bService---单例池Map---创建BService BService的Bean的生命周期2.1实例化---target---放入myMap2.2依赖注入填充aService---单例池Map---没有就去myMap找---AService普通对象 不用创建了打破了循环依赖2.3填充其他属性2.4做一些其他事情2.5添加到单例池3.填充其他属性4.做一些其他的事情AOP、Aware回调、初始化---AOP产生aService代理对象不再做1已经生成代理对象 BService中的aService是普通对象但AService是代理对象5.添加到单例池 新的问题一个Bean如果没有出现循环依赖就应该在4进行AOP出现循环依赖再提前进行AOP实例化代理对象 要保证Bean的生命周期Bean都在1进行AOP的话就打破了Bean的生命周期的设计 解决思路在1判断一下A是否出现了循环依赖是则提前AOP不是则继续创建普通target 如何在1判断AService是否出现循环依赖呢 在1并不能确定A是否有循环依赖A依赖BB依赖A这些信息是在接下来的依赖注入阶段才知道的 只有在2.2步才能确定A出现了循环依赖创建B时发现依赖A单例池没有A「分析到这里可以加一个creatingSet 用来存储正在创建的Bean第0步和第6步」 发现AService在Set中判断出AService出现了循环依赖再去创建AOP这里用紫色来标记 先看AService的Bean的生命周期0.creatingSetAService1.实例化--- 普通对象target2.依赖注入填充bService--- 单例池Map --- 创建BServiceBService的Bean的生命周期2.1实例化--- target2.2依赖注入填充aService--- 单例池Map --- creatingSet --- A出现了循环依赖--- 需要AOP代理对象2.3填充其他属性2.4做一些其他事情2.5添加到单例池3.填充其他属性4.做一些其他的事情AOP、Aware回调、初始化5.添加到单例池6.creatingSet.removeAService解决在2.2发现A出现循环依赖后再去创建A的代理对象并赋值给bService 新的问题在2.2生成了A的代理对象什么时候将A代理对象放到单例池呢 直接在2.2后将A代理对象放到单例池。❌不可行因为单例池意义就是存储已经走完完整生命周期的Bean对象 这个时候A代理对象里的普通对象target才刚走完第1步实例化。 解决思路那就再定义一个Map在2.2后存提前AOP出来的代理对象这个Map就是earlySingletonObjects这里用浅黄色来标记 先看AService的Bean的生命周期0.creatingSetAService1.实例化--- 普通对象target2.依赖注入填充bService--- 单例池Map --- 创建BServiceBService的Bean的生命周期2.1实例化--- target2.2依赖注入填充aService--- 单例池Map --- creatingSet --- A出现了循环依赖且需要AOP--- earlySingletonObjects查找A的代理对象--- AOP创建代理对象并加入二级缓存2.3填充其他属性2.4做一些其他事情2.5添加到单例池3.填充其他属性-- 填充的是代理对象里的target4.做一些其他的事情AOP、Aware回调、初始化-- 不会再做AOPearlySingletonObjects.get()放入单例池并remove5.添加到单例池6.creatingSet.removeAService思路2.2中发现出现循环依赖后先去二级缓存中找A的代理对象没有则再创建AOP代理对象并加入二级缓存 在依赖注入完成后从二级缓存拿到提前生成的Bean这时候这个Bean已经是完整的了经过了完整的依赖注入 此时将其放入单例池并从二级缓存中删去 问题2.2创建A的代理对象需要target对象如何拿到target呢 解决前面我们使用myMap在1实例化户存储了target对象这里也可以用myMap实现 从myMap中拿到Atarget再创建代理对象myMap的作用就是打破循环0.creatingSetAService1.实例化--- 普通对象target -- singletonFactories2.依赖注入填充bService--- 单例池Map --- 创建BServiceBService的Bean的生命周期2.1实例化--- target -- singletonFactories2.2依赖注入填充aService--- 单例池Map --- creatingSet --- A出现了循环依赖--- earlySingletonObjects查找A的代理对象--- singletonFactories执行lambda获取对象--- 赋值给aService并放入到earlySingletonObjects--- 从singletonFactories移除AService2.3填充其他属性2.4做一些其他事情2.5添加到单例池3.填充其他属性-- 填充的是代理对象里的target4.做一些其他的事情AOP、Aware回调、初始化-- 不会再做AOP其他初始化前、初始化后正常执行earlySingletonObjects.get()放入单例池并remove5.添加到单例池6.creatingSet.removeAService分析A 实例化对象后存入 myMapB 实例化时从 myMap 中拿到未完全的 Bean问题若 A 需要代理加强的话最终单例池中的 A 是代理对象而 B 中的 a 属性是普通对象提前代理 A在 A 实例化后看 A 是否需要代理增强直接生成代理再放入 myMap这里也是能解决循环依赖问题即只用两级缓存问题这样所有需要代理的对象都在实例化时生成了违背了 Bean 的生命周期退一步只有需要代理增强的对象出现循环依赖时才在 1 实例化时生成代理对象其他正常在 4 生成代理问题在 1 实例化时可以知道是否需要代理增强但如何知道是否发生了循环依赖呢无法知道只有在 2.2 时通过 creatingSet 判断才能知道是否发生了循环依赖那就在 2.2 时进行提前 AOP在 1 实例化时就不用 myMap 了问题2.2 中生成的 A 的代理对象可以赋值给 B 中的 a解决“B 中的 a 属性是普通对象”的问题但无法将该代理对象直接放入单例池中因为单例池存储的是以及走完完整的 Bean 生命周期的 Bean此时的代理对象中的 target 才走完 1 实例化创建一个新 Map 用于存提早创建出来的 Bean就是earlySingletonObjects在 2.2 中如果单例池没有就查二级缓存依旧没有就提早创建 A 的代理对象将代理对象放入这个 map普通对象也一样遵循这个流程只不过在 2.2 中创建的是普通对象放入二级缓存问这里不用二级缓存直接创建并赋值不可以吗不行每个对象都这样创建并赋值的话就无法保证单例了多个对象的相同属性不再是同一个对象即创建一个 bean 就要加入“单例池”以备后用但这里创建的不是完整的 Bean所以设计二级缓存收集提前创建出来的 Bean来保证单例性有了二级缓存“何时将代理对象/对象放入单例池中”的问题序列 8也就解决了在依赖注入后即可放入单例池中并删除二级缓存中的 bean。如果是普通的没有循环依赖依旧是走步骤 4 正常初始化加入单例池有循环依赖则提前到 2.2 生成 Bean 放入了二级缓存代理对象中的 target 走完接下来的生命周期后代理对象就是完整的 Bean 了2.2 中创建 A 的代理对象步骤是需要 A 的 target 对象才能创建的如何获取 A 的 target 呢在 1 中实例化出了 A 的 target可以将其存入一个 map正如前面所用的 myMap用来打破循环在 2.2 创建代理对象时从 myMap 中拿到 targetSpring 三级缓存的实现Spring 在 1 实例化后用三级缓存singletonFactories来打破循环存的是一个 lambda 表达式lambda 表达式执行的结果是创建代理对象并返回或者直接返回 target 对象取决于 A 是否需要代理增强。2.2 中从singletonFactories获取创建的 Bean这个 Bean 是提前创建的放到二级缓存并从三级中移除这里获取单例对象时调用 getSingleton 方法底层就是单例模式创建对象 DCL 双减加锁策略实现中间会从单例池、二级缓存、三级缓存中判断、拿去、创建对象。Async源码在“循环依赖”处场景A 类里有Async 方法A 与 B 循环依赖Async 注解方法类就需要代理增强需要代理对象但Async 不是基于 AOP 实现的而是自己定义了 BeanPostProcessor不是用的 AOP 的Async 需要在第 4 步的时候生成代理对象即 2.2 中从 singletonFactories 中获取的是 target 对象而不是代理对象因为他不是基于 AOP 来的所以 2.2 中给 B 的数据 aService 是 atarget 对象在 A 进行步骤 4 的时候会比较对象是否一样即 2.2 中放入二级缓存中的 target 对象 与 这里产生的代理对象不一样则报错。解决办法Lazy对 A 的 bService 加LazyA 在依赖注入时由于 bService 懒加载就不会去注入这个 Bean自然就不会去创建 B 的 Bean自然就不会出现循环依赖报错问题继续后续属性依赖在真正用到 bService 的时候再去注入 Bean用到 bService 的时候 A 的 Bean 已经创建好了这里的 A 是代理 Bean因为AsyncB 创建 Bean 时自然可以拿到 A 完成创建并注入。注意使用构造方法依赖注入 Bean 时出现循环依赖 Spring 是解决不了的因为是在实例化前进行依赖注入此时没有实例化对象谈不上提前暴露无法走 Spring 的三级缓存策略加Lazy 可以解决。

更多文章