java面试题Spring部分(四)

一、什么事spring的三级缓存

典型回答

在Spring的BeanFactory体系中,BeanFactory是Spring IOC容器的基础接口,其DefaultSingletonBeanRegistry类实现了BeanFactory接口,并维护了三级缓存:

java 复制代码
public class DefaultSingletonBeanRegistyr extends SimpleAliasRegistry implements SingletonBeanRegistry {
  //一级缓存,保存完成的Bean对象
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  //三级缓存,保存单例Bean的创建工厂
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  //二级缓存,存储"半成品"的Bean对象
  private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}

**singletonObjects是一级缓存,存储的是完整创建好的单例bean对象。**在创建一个单例bean时,会先从singletonObjects中尝试获取该bean的实例,如果能够获取到,则直接返回该实例,否则继续创建该bean。

**earlySingletonObjects是二级缓存,存储的是尚未完全创建好的单例bean对象。**在创建单例bean时,如果发现该bean存在循环依赖,则会先创建该bean的"半成品"对象,并将"半成品"对象存储到earlySingletonObjects中。当循环依赖的bean创建完成后,Spring会将完整的bean实例对象存储到singletonObjects中,并将earlySingletonObjects中存储的代理对象替换为完整的bean实例对象。这样可以保证单例bean的创建过程不会出现循环依赖问题。

**singletonFactories是三级缓存,存储的是单例bean的创建工厂。**当一个单例bean被创建时,Spring会先将该bean的创建工厂存储到singletonFactories中,然后再执行创建工厂的getObject()方法,生成该bean的实例对象。在该bean被其他bean引用时,Spring会从singletonFactories中获取该bean的创建工厂,创建出该bean的实例对象,并将该bean的实例对象存储到singletonObjects中。

二、Spring解决循环依赖一定需要三级缓存吗

典型回答

使用二级缓存也能解决循环依赖问题,但是如果完全依赖二级缓存解决循环依赖,意味着当我们依赖了一个代理类的时候,就需要在Bean实例化之后完成AOP代理,而在Spring的设计中,为了了解Bean的初始化和代理,是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理的。

但是,在Spring的初始化过程中,他是不知道哪些Bean可能有循环依赖的,那么,这时候Spring面临两个选择:

  1. 不管有没有循环依赖,都提前把代理对象创建出来,并将代理对象缓存起来,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
  2. 不提前创建代理对象,在出现循环依赖时,再生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。

第一个方案看上去比较简单,只需要二级缓存就可以了。但是他也意味着,Spring需要在所有的bean的创建过程中就要先成代理对象再初始化;那么这就和spring的aop的设计原则(前文提到的:在Spring的设计中,为了解耦Bean的初始化和代理,是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理的)是相悖的。

而Spring为了不破坏AOP的代理设计原则,则引入第三级缓存,在三级缓存中保存对象工厂,因为通过对象工厂我们可以在想要创建对象的时候直接获取对象。有了它,在后续发生循环依赖时,如果依赖的Bean被AOP代理,那么通过这个工厂获取到的就是代理后的对象,如果没有被AOP代理,那么这个工厂获取到的就是实例化的真实对象。

三、三级缓存是如何解决循环依赖问题的

典型回答

**Spring中Bean的创建过程其实可以分为两步:第一步实例化,第二步初始化

实例化的过程只需要调用构造函数把对象创建出来并给他分配空间,而初始化则是给对象的属性进行赋值。

而Spring之所以可以解决循环依赖就是因为对象的初始化是可以延后的,也就是说,当我创建一个Bean Service的时候,会先把这个对象实例化出来,然后再初始化其中的serviceB属性。

java 复制代码
@Service
public class Service{

	@Autowired
	private ServiceB serviceB;
}

@Service
public class ServiceB{

	@Autowired
	private ServiceA serviceA;
}

而 当一个对象只进行了实例化,但是还没有进行初始化时,我们称之为半成品对象。所以,所谓半成品对象,其实就是bean对象的空壳子,还没有进行属性注入和初始化。

当两个Bean在初始化过程中互相依赖的时候,如初始化A发现依赖了B,继续去初始化B,发现又依赖了A。大致流程如下图:

以上方法,就是通过引入三级缓存,解决了循环依赖的问题,在上述流程执行完成之后,ServiceA和ServiceB都被成功的完成了实例化和初始化。

以下是DefaultSingletonBeanRegistry#getSingleton方法,代码中,包括一级缓存、二级缓存、三级缓存的处理逻辑,该方法是获取bean的单例实例对象的核心方法:

java 复制代码
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先从一级缓存中获取bean实例对象,如果已经存在,则直接返回
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 如果一级缓存中不存在bean实例对象,而且当前bean正在创建中,则从二级缓存中获取bean实例对象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 如果二级缓存中也不存在bean实例对象,并且允许提前引用,则需要在锁定一级缓存之前,
            // 先锁定二级缓存,然后再进行一系列处理
            synchronized (this.singletonObjects) {
                // 进行一系列安全检查后,再次从一级缓存和二级缓存中获取bean实例对象
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 如果二级缓存中也不存在bean实例对象,则从三级缓存中获取bean的ObjectFactory,并创建bean实例对象
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // 将创建好的bean实例对象存储到二级缓存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从三级缓存中移除bean的ObjectFactory
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}
相关推荐
IT毕设梦工厂12 分钟前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius1 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe1 小时前
分布式系统实战经验
java·分布式
是梦终空1 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss2 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸2 小时前
flink doris批量sink
java·前端·flink
Monodye2 小时前
【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
java·网络·数据结构·算法·系统架构
一丝晨光2 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
无名指的等待7123 小时前
SpringBoot中使用ElasticSearch
java·spring boot·后端
Tatakai253 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis