目录
一、Spring 三级缓存解决循环依赖原理分析
1、Spring容器的三级缓存说明
2、Spring创建管理Bean的基本流程
3、循环引用的死循环场景
4、二级缓存解决死循环方案(无法解决AOP代理问题)
5、三级缓存解决死循环+AOP代理问题
一、Spring 三级缓存解决循环依赖原理分析
1、Spring容器的三级缓存说明
三级缓存产生的原因:
- 解决循环依赖可能导致的死循环问题
- 循环依赖下的AOP代理问题
Spring解决循环依赖的核心就是提前暴露对象,而提前暴露对象就是放置于第二级缓存中。缓存的底层都是Map,至于它们属于第几层是由Spring回去数据顺序及其作用来表现的。
| 名称 | 作用 |
|---|---|
| singletonObjects | 一级缓存:存放完整的Bean |
| earlySingletonObjects | 二级缓存:存放提前暴露的Bean,Bean不是完整的,为完成属性注入和执行初始化方法 |
| singletonFactories | 三级缓存:存放的是Bean工厂,主要生产Bean,存放到二级缓存中 |
在DefaultSingletonBeanRegistry类中:
private final MapsingletonObjects = new ConcurrentHashMap<>(256); private final Map earlySingletonObjects = new HashMap<>(16); private final Map > singletonFactories = new HashMap<>(16);
2、Spring创建管理Bean的基本流程
获取bean时通过bean工厂先从单例池中获取,如果没有则创建并添加至单例池,最终返回需要的bean对象。
创建Bean的流程:
getBean --》 实例化 --》 填充属性 --》 初始化 --》放入单例池
对Bean的创建最为核心三个方法解释如下:
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
- initializeBean:初始化,回到一些形如initMethod、InitializingBean等方法
3、循环引用的死循环场景
从对单例Bean的初始化流程可以看出,循环依赖主要发生在第三步(populateBean),也就是field属性注入的处理。 如果继续遵循一个单例池的原则,会出现下列死循环引用问题。
- 两对象相互引用一直在填充属性中进行循环(死循环)
- 因为引用间没有任何一个可以进入下一阶段 “初始化” 并 “放入单例池”
- 所以单例池中需要的对象永远找不到(getBean)
4、二级缓存解决死循环方案(无法解决AOP代理问题)
- 增加一个【半成品池】结解决循环依赖创建问题:
- 创建时:对象一旦实例化完成【createBeanInstance("a")】,会先将对象放入半成品池中
- 对象引用时:会先找单例池,如果不存在则会去半成品池中寻找
- 增加一个【半成品池】结解决循环依赖创建问题:
- 创建时:对象一旦实例化完成【createBeanInstance("a")】,会先将对象放入半成品池中
- 对象引用时:会先找单例池,如果不存在则会去半成品池中寻找
流程:
1、当 A对象 执行【第2行】实例化完成【createBeanInstance("a")】后,将实例化的 A对象 放入半成品池中
2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 从 单例池 或 半成品池 获取 B对象 进行填充)
3、此时 单例池 和 半成品池 中都没有 B对象 因而执行B的创建流程
(1)执行到【第7行】为 B对象 填充属性时需要 A对象
(2)首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【找到了】
(3)将 半成品池中的 A对象 填充进 B的属性中
(4)B对象 属性填充完成 --》初始化完成 --》放入单例池
4、A对象 填充 B属性存在【getBean:单例池中找到了】
5、A对象 属性填充完成 --》初始化完成 --》放入单例池
6、将半成品池中的A对象 清除
5、三级缓存解决死循环+AOP代理问题
我们通常情况下调用的是代理对象,而非对象本身(回忆一下事务处理的场景)
增加一个【工厂池】结解决循环依赖创建+AOP代理问题:
所谓的三级缓存就是在 单例池、半成品池 的基础上,增加了一个工厂池,里面存储的都是每个对象绑定的ObjectFactory(),此时的 单例池、半成品池 中 存储的 已经 不是对象本身,而是代理对象
ObjectFactory()作用特点:
- 为了调用提前处理【getEarlyBeanReference】来执行创建动态代理【createProxy】
- 对象 实例化完成【createBeanInstance】,就会在工厂池中创建一个factory()
- factory()不一定会被执行,只有该对象在创建过程中,又被其他对象所引用才会被调用,从而执行createProxy
AOP处理器实现了Bean处理器接口
AOP处理器创建代理对象会通过两个入口来调用创建动态代理(createProxy):
- postProcessAfterInitialization:【后置处理】在对象 初始化完成 后调用后置处理来创建代理对象
- getEarlyBeanReference:【提前处理】在对象 实例化完成 后在工厂池中创建一个工厂,这个工厂会通过调用提前引用来创建代理对象
流程:
1、当 A对象 执行【第2行】实例化完成【createBeanInstance("a")】后,会在 工厂池 中放入一个【factory(a)】
2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 从 单例池 或 半成品池 获取 B对象 进行填充)
3、此时 单例池 和 半成品池 中都没有 B代理对象 因而执行B的创建流程(同样会在工厂池中放入factory(b)):
- 执行到【第7行】为 B对象 填充属性时需要 A代理对象
- 首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【没有】
- 从而进入 工厂池 中执行调用 【factory(a)】--》执行调用 提前引用【getEarlyBeanReference】--》执行调用 创建动态代理【createProxy】
- 将创建的 A代理对象 放入 半成品池 ,可以被获取到并进行填充
- B对象 属性填充完成 --》初始化完成 --》执行调用 后置处理【postProcessAfterInitialization】--》执行调用 创建动态代理【createProxy】
- 将创建的 B代理对象 放入 单例池 ,可以被获取到并进行填充
4、A对象 填充 B属性存在【getBean:单例池中找到了B代理对象】
5、A对象 属性填充完成 --》初始化完成 --》执行调用 后置处理【postProcessAfterInitialization】
6、此时 半成品池 中 已经存在 了 A代理对象 所以不需要再执行 createProxy,会将 A代理对象 从 半成品池中 移入 单例池
7、一旦对象进入单例池,则意味着创建流程已经全部完成,此时会将工厂池的factory()进行清理
问:为什么不在创建实例化后直接调用提前引用,而是要通过工厂方法调用?
答:所谓的提前引用指的是在创建的过程当中被引用。A在创建过程中,如果有人要去引用这个A,那么才会执行这个提前引用流程,否则这个factory(a)是不会被执行的,重点取决于是否被引用决定。
二、Spring的核心模块容器模块( Spring Core):
这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。它使用IoC将应用配置和依赖从实际的应用代码中分离出来
应用上下文模块(spring context):
- 核心模块的BeanFactory使Spring成为一个容器,而上下文模块使它成为一个框架。这个模块扩展了BeanFactory的概念,增加了对国际化(I18N)消息、事件传播、验证的支持。
- 这个模块提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持
AOP模块(spring aop):
Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作
JDBC抽象和DAO模块(spring dao):
提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。并且,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs(plain old Java objects)都适用。
对象/关系映射集成模块(spring orm):
Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构
Web模块(spring web):
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。还提供了一些面向服务支持,例如:实现文件上传的multipart请求
MVC模块(spring mvc):
三、BeanFactory、ApplicationContext和FactoryBean 1、BeanFactory 和 ApplicationContext 的区别Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。
Spring提供了两种不同的 IOC 容器,一个是BeanFactory,另外一个是ApplicationContext,它们都是Java interface。BeanFactory 和 ApplicationContext 都提供了一种方式,使用getBean("bean name")获取bean
ApplicationContext 继承于 BeanFactory【ApplicationContext继承ListableBeanFactory;它们都可以用来配置XML属性,也支持属性的自动注入。而ListableBeanFactory继承BeanFactory】
- BeanFactory:是 IoC 容器的顶级接口,是IoC容器的最基础实现,也是访问Spring容器的根接口,负责对bean的创建,访问等工作。实现类功能比较单一,BeanFactory接口实现的容器,特点是在每次获取对象时才会创建对象。
- ApplicationContext:继承了BeanFactory接口,拥有BeanFactory的全部功能,并且扩展了很多高级特性,每次容器启动时就会创建所有的对象
两者装载bean的区别:
BeanFactory:BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
延迟实例化的优点:
- 应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
ApplicationContext:ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
不延迟实例化的优点:
- 所有的Bean在启动的时候都加载,系统运行的速度快;
- 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
- 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)
首先它是一个Bean,但又不仅仅是一个Bean。它是一个能 生产 或 修饰 对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例
- 负责生产和管理bean的一个工厂。
- 事IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象的依赖。
- 多种实现:如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。
整体流程图:
1、Bean信息扫描加载进Bean定义信息注册表(BeanDefiniton) 2、Bean的生命周期核心流程就几个:getBean --> 实例化 --> 填充属性 --> 初始化 --> 加入IOC容器
- 实例化:在内存开辟空间,实例化对象的属性都是默认值
- 填充属性:三级缓存循环依赖问题的流程
- 初始化:一系列的 Aware--》一系列的 PostProcessor
如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口,Spring将调用Bean的setApplicationContext()方法,将应用上下文的引用传入到bean中
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用



