前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring学习(二)bean的循环依赖

Spring学习(二)bean的循环依赖

作者头像
虞大大
发布2020-08-26 17:20:27
6090
发布2020-08-26 17:20:27
举报
文章被收录于专栏:码云大作战码云大作战

一、bean的初始化回顾

二、bean的循环依赖示例

使用spring bean的循环依赖在我们开发中是很普遍的,并且spring容器中也默认支持循环依赖。以下为一个简单的bean的循环依赖示例:

代码语言:javascript
复制
@Component
public class UserService {

    @Autowired
    private TestService testService;
    public UserService() {
        System.out.println("userService初始化完毕");
    }
}
代码语言:javascript
复制
@Component
public class TestService {

    @Autowired
    private UserService userService;
    public TestService() {
        System.out.println("testService创建结束");
    }
}

UserService中注入了testService,TestService中注入了userService,形成了一个循环依赖。

三、testService的bean初始化过程回顾

bean的初始化顺序如上流程图所示,spring容器启动,根据指定包路径扫描路径下所有加了主键的类,将beanName加入到一个list中,对list进行遍历。此时会先对testService的bean进行初始化,因为根据排序值t>u,testService排在userSerivce前面。

testService不是抽象的类,并且是单例,非懒加载的类,因此会在spring容器启动时加载该类。进入核心方法doGetBean()中进行初始化。

· doGetBean()

代码语言:javascript
复制
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
      final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
      throws BeansException {
   //根据testService作为beanName查询缓存池中是否存在bean,此时为null
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      //...
   }      //testService的bean为null,进入else分支   else {
      //...
      try {
         //...testService是否需要加载dependOn依赖的bean
         String[] dependsOn = mbd.getDependsOn();
         if (dependsOn != null) {
            for (String dep : dependsOn) {
               if (isDependent(beanName, dep)) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
               }
               registerDependentBean(dep, beanName);
               try {
                  getBean(dep);
               }
               catch (NoSuchBeanDefinitionException ex) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
               }
            }
         }

         //属于单例,testService的bean加载进入该分支
         if (mbd.isSingleton()) {            //获取bean            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
               @Override
               public Object getObject() throws BeansException {
                  try {                     //lambda——>创建bean
                     return createBean(beanName, mbd, args);
                  }
                  catch (BeansException ex) {
                     destroySingleton(beanName);
                     throw ex;
                  }
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }        //...
}

doGetBean中可以看几个关键的流程,首先是查询缓存池判断是否存在testSerivce的Bean,此时的testService的bean肯定为null,并且testService为单例,所以进入判断单例的分支中进行该bean的创建。

· getSingleton

代码语言:javascript
复制
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "'beanName' must not be null");
   synchronized (this.singletonObjects) {      //判断一级缓存中是否存在bean      Object singletonObject = this.singletonObjects.get(beanName);      if (singletonObject == null) {
         //这里有个细节,会把该bean放入到正在创建的集合中         //testSerivce此时作为正在创建的bean放入到集合中         beforeSingletonCreation(beanName);
         //...
         try {                   //这里会直接从testService bean的工厂取出bean对象            //这里的bean工厂的创建,其实就是在lmd中的createBean中完成            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         //...
}

getSingleton中出现了一段有意思的代码,就是会把正在创建的bean的name放入到集合中。这里的集合其实就是为了给循环依赖时候的判断所使用,说白了就是为了保证在对UserSerivce初始化时,对testSerivce进行注入时候可以知道testService处于正在创建时,无需进行其他创建之前的操作。

· createBean

在上述的getSingleton方法中,会根据singletonFactory.getObject()获取bean对象。其实这里的sigletonFactory是doGetBean中有段lmd表达式的createBean的运行结果。

代码语言:javascript
复制
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
   //...
代码语言:javascript
复制
   // 从beanDefinition中取出该bean的class类型
代码语言:javascript
复制
   Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
      mbdToUse = new RootBeanDefinition(mbd);
      mbdToUse.setBeanClass(resolvedClass);
   }
   //... 开始创建bean
   Object beanInstance = doCreateBean(beanName, mbdToUse, args);
   if (logger.isDebugEnabled()) {
      logger.debug("Finished creating instance of bean '" + beanName + "'");
   }
   return beanInstance;
}

· doCreateBean

代码语言:javascript
复制
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
      throws BeanCreationException {
   //...
if (instanceWrapper == null) {//实例化对象instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   //...}

在createBeanInstance中创建出了testService早期的对象,因为通过debug可以看到wrapper中testService的bean中依赖的userSerivce的bean还没有被注入。

运行到createBeanInstance后,没有执行bean的初始化方法。

继续执行代码:

代码语言:javascript
复制
//判断是否允许循环依赖//条件一:单例//条件二:私有属性默认为true,不过可以修改,私有属性在beanFactory中//条件三:是否处于正在创建队列中。在调用createBean方法之前,已经放在了创建队列中。boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isDebugEnabled()) {
      logger.debug("Eagerly caching bean '" + beanName +
            "' to allow for resolving potential circular references");
   }   //将bean生成的bean工厂放到二级缓存中   addSingletonFactory(beanName, new ObjectFactory<Object>() {
      @Override
      public Object getObject() throws BeansException {         //这里主要判断是否存在aop,在后面的spring aop中会讲到,这里不做阐述
         return getEarlyBeanReference(beanName, mbd, bean);
      }
   });
}
//...

到目前为止,都和非循环依赖的bean初始化过程是一致的,并且testService对应的bean工厂已经被放到了二级缓存中。

四、通过populateBean开始循环依赖注入

testService在对属性注入时,发现需要注入userSerivce的bean,就会根据userService的注解@Autowired选择对应的策略模式postProcessPropertyValues开始注入userService,直到底层仍调用了beanFactory.getBean(),开始重复执行bean初始化的过程。

userService bean初始化过程:getBean()—>doGetBean()—>getSingleton(beanName)校验userSerivce的bean是否存在一级缓存中,如果存在则返回,不存在继续初始化—>getSingleton(beanName,beanFactory)—>将userSerivce放入正在初始化的集合中—>createBean()—>doCreateBean()—>createBeanInstance()创建userService的早期对象—>将userSerivce的bean工厂放到二级缓存中—>populateBean—>开始依赖注入testService的bean。

此时又回到了对于testService的bean的初始化,getBean()—>doGetBean()—>getSingleton(beanName)。

· 通过getSingleton(testService)查找testService的bean

此时testSerivce的bean没有创建出来,我们观察下getSingleton中是如何找到testService的bean的。

代码语言:javascript
复制
protected Object getSingleton(String beanName, boolean allowEarlyReference) {   //查找一级缓存中是否存在testSerivce的bean   Object singletonObject = this.singletonObjects.get(beanName);   //一级缓存中不存在,并且正在初始化的集合中存在testService
代码语言:javascript
复制
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {         //去三级缓存中查找是否存在         singletonObject = this.earlySingletonObjects.get(beanName);
         //三级缓存中不存在,并且allowEarlyReference为true         //allowEarlyReference因为上游传来就是一个写死的true         if (singletonObject == null && allowEarlyReference) {            //二级缓存中是否存在,存在就放到三级缓存中            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

(1)首先会从一级缓存中查找testService的bean,由于testService的bean进入了循环依赖中并没有完成创建出来所以这里肯定为null。

(2)一级缓存不存在bean并且在初始化集合中存在需要查询的bean,才会继续查找。通过之前的初始化过程我们也可以testService或者userSerivce已经被放入到了集合中。所以通过校验。

(3)去三级缓存中查找是否存在testService的早期bean,发现仍不存在为null,因为可以通过之前的初始化过程知道testService在doCreateBean时,被放入到了二级缓存中。

(4)去二级缓存中查询是否存在testService的工厂bean,发现存在二级缓存中,此时会把二级缓存放到三级缓存中,从二级缓存中删除这个工厂bean(为了方便gc所以需要删除)。

由此可以得出,正在初始化过程中的早期bean会存在二级缓存或三级缓存中,并且在二级缓存中的beanFactory,会被移到三级缓存中。为什么要这么处理,其实是和优化了一部分的性能。

当我们看下sigletonFactory.get(beanName)时,可以看到调用的底层方法是如下图所示的:

通过该bean工厂获取bean时,工厂会对bean所有的后置处理器进行遍历与操作该bean,或者如果实现了aop还会新生成一个代理对象。而在getSingleton方法中也可以看到是先取一级缓存,一级缓存不存在再取三级缓存,不直接取调用过程更复杂的二级缓存中的beanFactory。而且当三级缓存不存在,二级缓存存在时,会获取二级缓存中beanFactory中的bean放到三级缓存中,保证重复初始化bean的时候不再重复执行bean的后置处理器来提高性能。

到此为止,在初始化userService的bean时,所有的属性都注入完毕(即testService的bean被获取到),userService也初始化成功,并被放入到了一级缓存,从正在创建的集合中被移除。

代码语言:javascript
复制
//...finally {   if (recordSuppressedExceptions) {
      this.suppressedExceptions = null;
   }   //从集合中移除userService   afterSingletonCreation(beanName);
}
if (newSingleton) {   //真正的userService的bean被创建出来了,放到一级缓存中   addSingleton(beanName, singletonObject);
}

userService的bean真正创建出来后,又回到最开始的testService的bean初始化中,testService内部属性也可以被注入成功(即userService的bean已经创建成功了)。循环依赖结束。

五、循环依赖的bean初始化过程-图

与普通bean的注入相比,会发现循环依赖的bean的注入,会直接从二级缓存或三级缓存中获取早期的bean,来提高bean注入的性能。

testService、userSerivce的bean依赖注入顺序:

(1)初始化testSerivce,生成了早期的testSerivce bean factory放到了二级缓存,通过populateBean注入userSerivce。

(2)开始初始化userSerivce,生成了早期的userService bean factory放到了二级缓存,通过populateBean需要注入testService。

(3)底层调用了getSingleton,从二级缓存池中获取testService的bean factory并根据工程获得bean实例,将testSerivce的bean放到了三级缓存,从二级缓存中删除bean factory(方便gc)。

(4)userService初始化完毕,放到一级缓存中,从三级缓存中删除bean。

(5)testService初始化完毕,放到一级缓存中,从三级缓存中删除bean。

六、如何关闭循环依赖

spring容器中是默认开启循环依赖的。

我们只需要修改他为false即可。修改方式:

代码语言:javascript
复制
public static void main(String[] args) {
AnnotationConfigApplicationContext context                        = new AnnotationConfigApplicationContext();
    AbstractAutowireCapableBeanFactory beanFactory             = (AbstractAutowireCapableBeanFactory) context.getBeanFactory();
    beanFactory.setAllowCircularReferences(false);
    
    context.register(AppConfig.class);
    context.refresh();
}

循环依赖开启的参数是在AbstractAutowireCapableBeanFactory类中,所以我们通过获取spring容器中的beanFactory对象,通过他的子类来setAlloCircularRefereces(false)即可。

然后将spring容器手动进行启动。

运行结果,无法循环依赖注入:

七、为什么构造函数注入无法进行循环依赖

修改testSerivce类中注入UserService中的注入方式为构造函数注入,修改后代码如下:

代码语言:javascript
复制
@Component
public class TestService {

    private UserService userService;
    @Autowired
    public TestService(UserService userService) {
        this.userService = userService;
    }

    public TestService() {
        System.out.println("testService创建结束");
    }
}

运行结果:

根据报错信息可以追溯到底层仍调用了该方法,并且autowiredBeanName为userService:

代码语言:javascript
复制
return (instanceCandidate instanceof Class ?
      descriptor.resolveCandidate(autowiredBeanName, type, this) : instanceCandidate);

resolveCandidate进去后,会发现一段熟悉的代码:

代码语言:javascript
复制
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
      throws BeansException {
   //开始初始化userSerivce的bean
   return beanFactory.getBean(beanName, requiredType);
}

此时testService由于构造函数没有执行完毕,所以testService的早期对象并没有被创建出来,并且在二级缓存或三级缓存中也没有testService的bean工厂和bean示例。因此在对userSerivce对象进行属性注入testService,由于找不到testService的bean而报错。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档