Spring三大缓存介绍
一级缓存:
一级缓存保存的是已经完全初始化和实例化的 Bean 对象,在程序中使用的 Bean 通常就是从这个缓存中获取的;这个缓存的目的是确保 Bean 只初始化一次(是单例的),避免多次实例化相同的Bean对象,提高性能。
二级缓存:
二级缓存用来解决 Bean 创建过程中的循环依赖问题,它存储的是尚未完成属性注入和初始化的“半成品”Bean 对象;当 Spring容器发现两个或多个 Bean 之间存在循环依赖时,也就是当一个 Bean 创建过程中需要引用另一个正在创建的 Bean,Spring 将创建需要的这些未完全初始化的对象提前暴露在二级缓存中,以便其他 Bean 进行引用,确保它们之间的依赖能够被满足。
三级缓存:
三级缓存中存储的是 ObjectFactory<?> 类型的代理工厂对象,主要用于处理存在 AOP 时的循环依赖问题;每个 Bean 都对应一个 ObjectFactory 对象,通过调用该对象的 getObject 方法,可以获取到早期暴露出去的 Bean;在该 Bean 要被其他 Bean 引用时,Spring 就会用工厂对象创建出该 Bean 的实例对象,最终当该 Bean 完成构造的所有步骤后就会将该 Bean 放入到一级缓存中。
1.遍历待创建的所有 beanName:在容器启动时,Spring 会遍历所有需要创建的 Bean 名称,在第一次遍历到 A 时,就开始获取 Bean A;如果 Bean A 的实例不在一级、二级缓存中(缓存中没有值),Spring 会开始正常的 Bean 创建流程。
2.Bean A 的创建:Bean A 的创建过程开始,然后 Spring 会检查是否 A 是单例(Singleton),同时 A 是否已经创建完成;如果 A 是单例且尚未创建完成,将 A 的 BeanFactory 存入三级缓存。
3.处理依赖注入:A 开始处理 @Autowired 注解,尝试注入 B 属性;Spring 会在一级、二级缓存中来查找 Bean B,如果找不到,则开始正常创建 Bean B 的流程。
4.Bean B 的创建:Bean B 的创建过程类似于 A,首先判断B是否是单例,且是否已创建完成,如果否,B 的BeanFactory 也会被存入三级缓存。
5.B 注入 A 属性:B 开始注入 A 属性,尝试从一级、二级缓存中查找 A;如果在缓存中找不到 A,B 会尝试从三级缓存获取 A 的 BeanFactory,并通过 BeanFactory的getObject()方法获取 A 的属性,此时,A 被存入二级缓存(此时已经确定是放入是否需要代理了),同时清除三级缓存;因此,B 能够成功注入 A 属性,B 接着执行初始化,处于实例化和初始化都已完成的完全状态。
6.B 存入一级缓存:B执行addSingleton(),将完全状态的 B 存入一级缓存,并清空二级,三级缓存。
7.A 继续注入 B 属性:A 继续注入 B 属性,调用beanFactory.getBean()方法获取 B,由于第六步已经将 B 存入一级缓存,A 可以直接获取 8.B,成功注入 B 属性, A 接着执行初始化,得到一个完全状态的 A。
A 存入一级缓存:A 执行addSingleton(),将完全状态的 A 存入一级缓存,并清空二级缓存。
Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 ==> 二级缓存 ==> 三级缓存。
1、一级缓存:Map<String, Object> singletonObjects:
(1)第一级缓存的作用:
用于存储单例模式下创建的Bean实例(已经创建完毕)。
该缓存是对外使用的,指的就是使用Spring框架的程序员。
2、第二级缓存:Map<String, Object> earlySingletonObjects:
(1)第二级缓存的作用:
用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
为了解决第一个classA引用最终如何替换为代理对象的问题(如果有代理对象)
3、第三级缓存:Map<String, ObjectFactory<?>> singletonFactories:
(1)第三级缓存的作用:
通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)。
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
此缓存是解决循环依赖最大的功臣
(2)存储什么数据?
K:bean的名称
V:ObjectFactory,该对象持有提前暴露的bean的引用
为什么第三级缓存要使用ObjectFactory?
如果仅仅是解决循环依赖问题,使用二级缓存就可以了,但是如果对象实现了AOP,那么注入到其他bean的时候,并不是最终的代理对象,而是原始的。这时就需要通过三级缓存的ObjectFactory才能提前产生最终的需要代理的对象。
解决构造函数相互注入造成的循环依赖
对于类A和类B都是通过构造器注入的情况,可以在A或者B的构造函数的形参上加个@Lazy注解实现延迟加载。@Lazy实现原理是,当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。
比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Async
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
这两段代码中定义了两个Service类:TestService1和TestService2,在TestService1中注入了TestService2的实例,同时在TestService2中注入了TestService1的实例,这里构成了循环依赖。
只不过,这不是普通的循环依赖,因为TestService1的test1方法上加了一个@Async注解。
大家猜猜程序启动后运行结果会怎样?
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘testService1’: Bean with name ‘testService1’ has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.
报错了。。。原因是出现了循环依赖。
把TestService1的test1方法上的@Async注解去掉,TestService1和TestService2都需要注入对方的实例,同样构成了循环依赖。
但是重新启动项目,发现它能够正常运行。这又是为什么?
带着这两个问题,让我们一起开始spring循环依赖的探秘之旅。
2.什么是循环依赖?
循环依赖:说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。
spring中出现循环依赖主要有以下场景:
单例的setter注入
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了。
spring内部有三级缓存:
singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
下面用一张图告诉你,spring是如何解决循环依赖的:
细心的朋友可能会发现在这种场景中第二级缓存作用不大。
那么问题来了,为什么要用第二级缓存呢?
试想一下,如果出现以下这种情况,我们要如何处理?
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}
TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。
按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
假设不用第二级缓存,TestService1注入到TestService3的流程如图:
TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
这样不是有问题?
为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?
答:不行,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。
针对这种场景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
多例的setter注入
这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
很多人说这种情况spring容器启动会报错,其实是不对的,我非常负责任的告诉你程序能够正常启动。
为什么呢?
其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法
标红的地方明显能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。
而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。
如何让他提前初始化bean呢?
只需要再定义一个单例的类,在它里面注入TestService1
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
}
重新启动程序,执行结果
Requested bean is currently in creation: Is there an unresolvable circular reference?
果然出现了循环依赖。
注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
构造器注入
这种注入方式现在其实用的已经非常少了,但是我们还是有必要了解一下,看看如下代码:
@Service
public class TestService1 {
public TestService1(TestService2 testService2) {
}
}
@Service
public class TestService2 {
public TestService2(TestService1 testService1) {
}
}
运行结果:
Requested bean is currently in creation: Is there an unresolvable circular reference?
出现了循环依赖,为什么呢?
从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
单例的代理对象setter注入
这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
我那位同事的问题也是这种情况。
。。。看原文吧,,复制不动了
4.出现循环依赖如何解决?
项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:
生成代理对象产生的循环依赖
这类循环依赖问题解决方法很多,主要有:
使用@Lazy注解,延迟加载
使用@DependsOn注解,指定加载先后关系
修改文件名称,改变循环依赖类的加载顺序
使用@DependsOn产生的循环依赖
这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
多例循环依赖
这类循环依赖问题可以通过把bean改成单例的解决。
构造器循环依赖
这类循环依赖问题可以通过使用@Lazy注解解决。