前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring5.0源码深度解析之Spring是如何利用三级缓存解决循环依赖的问题

Spring5.0源码深度解析之Spring是如何利用三级缓存解决循环依赖的问题

作者头像
黎明大大
发布2021-03-09 16:19:18
1.5K0
发布2021-03-09 16:19:18
举报
文章被收录于专栏:java相关资料java相关资料

前言

Spring已经成为了开发项目的不可缺少的组件了,我们在平常开发项目中难免会遇到以下这些情况,比如说,我有A类和B类,两个业务类都注入到Spring容器里了,且双方都互相注入了,这个时候就会造成循环依赖的问题,相信之前有很多开发者遇到这样的问题吧,不过现在Spring底层已经通过三级缓存来解决了这个循环依赖的问题了。

什么是循环依赖

循环依赖通俗来讲,Bean与Bean之间相互依赖了,或者说多个Bean之间相互依赖,最终形成闭环操作(闭环可以理解为死循环的意思

如下图所演示

A实例依赖B实例,B实例依赖C实例,C实例依赖A实例,就这样他们最终形成了一个循环依赖的关系

需要注意是的是:

上图指的循环依赖不是方法之间的调用,而是对象之间的相互引用

Spring Bean的循环依赖

谈到Spring Bean循环依赖,估计大家伙可能遇到的比较少,毕竟在开发的过程中好像没有循环依赖这个概念,或者换个说法说,在开发的coding阶段,没有关于循环依赖的报错等

Spring Bean循环依赖的几种场景

在Sping环境中,我们的Bean的实例化以及初始化都是交给了Spring容器进行管理,因此为了演示哈,我这里建立了3个类。

1.通过构造器进行注入

代码语言:javascript
复制
@Component
public class InstanceAService {
   public InstanceAService(InstanceBService instanceBService) {

   }
}

@Component
public class InstanceBService {
   public InstanceBService(InstanceCService instanceBService) {

   }
}

@Component
public class InstanceCService {
   public InstanceCService(InstanceAService instanceAService) {

   }
}

结果:======
SpringBoot报错信息如下:
┌─────┐
|  instanceAService defined in file [D:\learn\Learning project\springboot_prometheus\target\classes\com\dream\sunny\InstanceAService.class]
↑     ↓
|  instanceBService defined in file [D:\learn\Learning project\springboot_prometheus\target\classes\com\dream\sunny\InstanceBService.class]
↑     ↓
|  instanceCService defined in file [D:\learn\Learning project\springboot_prometheus\target\classes\com\dream\sunny\InstanceCService.class]
└─────┘

Spring原生报错信息如下:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'instanceAService' defined in file [C:\Users\sunny\Downloads\Compressed\spring-framework-5.2.10.RELEASE\spring-sunny-ioc\build\classes\java\main\com\dream\sunny\customer\xunhuan\InstanceAService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'instanceBService' defined in file [C:\Users\sunny\Downloads\Compressed\spring-framework-5.2.10.RELEASE\spring-sunny-ioc\build\classes\java\main\com\dream\sunny\customer\xunhuan\InstanceBService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'instanceCService' defined in file [C:\Users\sunny\Downloads\Compressed\spring-framework-5.2.10.RELEASE\spring-sunny-ioc\build\classes\java\main\com\dream\sunny\customer\xunhuan\InstanceCService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'instanceAService': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797)
  at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:227)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1416)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1263)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:571)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531)
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:414)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:288)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:411)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:239)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:939)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:946)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:600)
  at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:119)
  at com.dream.sunny.customer.xunhuan.InstanceStart.main(InstanceStart.java:14)

通过如上案例,通过构造器方式注入的Bean,Spring底层是无法解决循环依赖的问题,所以只能抛出异常:BeanCurrentlyInCreationException

1.1通过构造器注入的解决方案

代码语言:javascript
复制
@Component
public class InstanceAService {
   public InstanceAService(@Lazy InstanceBService instanceBService) {

   }
}

@Component
public class InstanceBService {
   public InstanceBService(InstanceCService instanceBService) {

   }
}

@Component
public class InstanceCService {
   public InstanceCService(InstanceAService instanceAService) {

   }
}

结果:======
程序正常启动

如上案例,消除循环依赖的方式是通过延迟加载,只需要在任意一个构造注入中使用@Lazy即可解决

2.通过Setter注入

这种方式注入是我们最常用的一种依赖注入方式(Spring底层通过三级缓存解决循环依赖)

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

   @Autowired
   private InstanceBService instanceBService;
}

@Component
public class InstanceBService {
   @Autowired
   private InstanceCService instanceCService;
}

@Component
public class InstanceCService {

   @Autowired
   private InstanceAService instanceAService;
}

结果:======
程序正常启动

Spring容器三级缓存

三级缓存其实更像Spring容器的术语,采用三级缓存来解决循环依赖问题,三级缓存的大概作用如下:

名称

描述

singletonObjects

一级缓存,存放完整的Bean

earlySingletonObjects

二级缓存,存放提前暴露的Bean,且该Bean是不完整的

singletonFactories

三级缓存,存放的是Bean工厂,主要生产Bean的

从源码方面来分析Spring的循环依赖

提示:本文的分析重点主要在于Spring的循环依赖部分,其他的部分本文则会一笔带过,如对其他部分感兴趣的伙伴们,可参考作者该系列其他文章。

本文的循环依赖源码分析的开始前提是基于setter注入方式分析哦

老规矩,我们还是通过注解配置类的方式作为入口,我们进入

AnnotationConfigApplicationContext的构造方法

代码语言:javascript
复制
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(InstanceConfig.class);

进入后,我们看到构造方法里面的有三个方法,这个方法我们不着重讲解

1.this():该方法会初始化BeanFactory容器,完成Spring内部的后置处理器解析成BeanDefinition,读取和扫描我们的配置类等

2.register();该方法会将扫描到的配置类注册成Bean定义

3.refresh();Spring的生命周期的所有操作都在该方法中,也是Spring的核心所在

代码语言:javascript
复制
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
      //调用构造函数,this():调用本身构造函数,但是如果有父类的话,会先走父类的构造函数,再走子类的构造函数
      this();

      //注册配置类(将配置类注册到Bean定义中)
      register(componentClasses);

      //IOC容器刷新接口
      refresh();
   }

好,大概了解完后,我们直接进入refresh()方法,该方法内部有很多方法,我这里忽略掉了部分Spring源码

代码语言:javascript
复制
@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
       ...
         // 调用我们的bean工厂的后置处理器. 1. 会在此将class扫描成beanDefinition  2.bean工厂的后置处理器调用
         //BeanFactoryPostProcessors (解析配置类)
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册我们bean的后置处理器
         registerBeanPostProcessors(beanFactory);

         // 初始化国际化资源处理器.
         initMessageSource();

         // 创建事件多播器
         initApplicationEventMulticaster();

         // 这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.
         onRefresh();

         //把我们的事件监听器注册到多播器上
         registerListeners();

         // 实例化剩余的单实例Bean
         finishBeanFactoryInitialization(beanFactory);
         ...
   }
}

我们前面讲到过,通过AnnotationConfigApplicationContext()的构造方法,会将Spring内部的类注册成Bean定义和扫描到的配置类注册到Bean定义中,从下图中我们可以看到,beanDefinitionNames集合中存在多个内容,其中一个则是我们的配置类哈,其他的部分则是Spring内部的后置处理器

接着我们看到invokeBeanFactoryPostProcessors()方法,该方法会将扫描到类注册成Bean定义,这里指的扫描的类是在该类中加了@Component或者@Bean等注解

我们看到下图,执行了该方法后,会将加了@Component的类解析的Bean定义进行存储

该方法很深,不是本文的重点,我们就不继续往下跟了。我们只需要知道该方法的主要作用就是将我们的类解析成Bean定义

回到上面,然后看到我们finishBeanFactoryInitialization()方法,该方法会去实例化单例Bean

我们跟入进去,该方法里的内部方法都有注释,这里就不做分析,我们只关注重点

看到beanFactory.preInstantiateSingletons();该方法就是去实例化所有的单例Bean,而且是非懒加载的Bean

代码语言:javascript
复制
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // 为我们的bean工厂创建类型转化器  Convert
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

   //如果beanFactory之前没有注册嵌入值解析器,则注册默认的嵌入值解析器:主要用于注解属性值的解析。
   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
   }

   // 初始化LoadTimeWeaverAware Bean实例对象
   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   // Stop using the temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(null);

   //允许缓存所有bean定义元数据,而不期望进一步的更改
   //冻结所有的Bean定义,说明注册的Bean定义将不被修改或任何进一步处理(可以理解为:Spring要进行生产Bean了,所以不允许外部再进行修改Bean)
   beanFactory.freezeConfiguration();

   //实例化所有的单例Bean,且非Lazy加载的Bean
   beanFactory.preInstantiateSingletons();
}

我们跟进去beanFactory.preInstantiateSingletons();方法

代码语言:javascript
复制
@Override
public void preInstantiateSingletons() throws BeansException {
   ...
   //获取我们容器中所有bean定义的名称
   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

   // Trigger initialization of all non-lazy singleton beans...
   //循环我们所有的bean定义名称
   for (String beanName : beanNames) {
      //合并我们的bean定义,转换为统一的RootBeanDefinition类型(在), 方便后续处理
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

      /**
       * 根据bean定义判断是不是抽象的&& 不是单例的 &&不是懒加载的
       */
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

         //是否为工厂Bean
         if (isFactoryBean(beanName)) {
            // 是factoryBean会先生成实际的bean  &beanName 是用来获取实际bean的
            Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            if (bean instanceof FactoryBean) {
               FactoryBean<?> factory = (FactoryBean<?>) bean;
               boolean isEagerInit;
               if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                  isEagerInit = AccessController.doPrivileged(
                        (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                        getAccessControlContext());
               } else {
                  isEagerInit = (factory instanceof SmartFactoryBean &&
                        ((SmartFactoryBean<?>) factory).isEagerInit());
               }
               //调用真正的getBean的流程
               if (isEagerInit) {
                  getBean(beanName);
               }
            }
         } else {
            //非工厂Bean,就是普通的Bean对象
            getBean(beanName);
         }
      }
   }
   ...
}

通过图加代码更好理解

我们进入方法,首先会获取到所有Bean定义放入到集合中,并且会逐个去遍历它,我们这里为了方便,直接条件断点,直接进入条件断点

然后看到我Debug的第一行,我们的Bean会有以不同的类型注册的,这里为了方便统一类型,Spring通过getMergedLocalBeanDefinition(beanName); 方法,将我们注册的Bean统一成RootBeanDefinition

接着会进行判断当前遍历的Bean定义,如果该Bean不是抽象且是单例且非懒加载才会进入,当然我这里都是满足的

然后又会接着判断,当前Bean是否是工厂Bean,当然我们的Bean并没有实现FactoryBean接口,并不是工厂Bean,所以会进行else,去实例化Bean对象

关于工厂Bena的详细介绍,伙伴们可以自行百度哈,这里不做多解释

好,那我们进入getBean()方法,在该方法内又调用了doGetBean()方法,众所周知在Spring源码里面,带了do的方法才是真正干活的方法

代码语言:javascript
复制
@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

我们再接着进入到doGetBean()方法,好,重头戏就从这里开始了

代码语言:javascript
复制
protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {

   /**
    * 在这里 传入进来的name 可能是别名, 也有可能是工厂bean的name,所以在这里需要转换,获取最真实的Bean名称
    */
   String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   //尝试去缓存中获取对象(从一级缓存获取数据)
   Object sharedInstance = getSingleton(beanName);

   if (sharedInstance != null && args == null) {
      if (logger.isTraceEnabled()) {
         if (isSingletonCurrentlyInCreation(beanName)) {
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                  "' that is not fully initialized yet - a consequence of a circular reference");
         } else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
         }
      }
      /**
       * /*
       *
       * 如果 sharedInstance 是普通的单例 bean,下面的方法会直接返回。但如果
       * sharedInstance 是 FactoryBean 类型的,则需调用 getObject 工厂方法获取真正的
       * bean 实例。如果用户想获取 FactoryBean 本身,这里也不会做特别的处理,直接返回
       * 即可。毕竟 FactoryBean 的实现类本身也是一种 bean,只不过具有一点特殊的功能而已。
       */
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }

   else {
      /**
       * spring 只能解决单例对象的setter 注入的循环依赖,不能解决构造器注入
       */
      if (isPrototypeCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(beanName);
      }

      /**
       * 判断AbstractBeanFacotry工厂是否有父工厂(一般情况下是没有父工厂因为abstractBeanFactory直接是抽象类,不存在父工厂)
       * 一般情况下,只有Spring 和SpringMvc整合的时才会有父子容器的概念,
       * 比如我们的Controller中注入Service的时候,发现我们依赖的是一个引用对象,那么他就会调用getBean去把service找出来
       * 但是当前所在的容器是web子容器,那么就会在这里的 先去父容器找
       */
      BeanFactory parentBeanFactory = getParentBeanFactory();
      //若存在父工厂,切当前的bean工厂不存在当前的bean定义,那么bean定义是存在于父beanFacotry中
      if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         //获取bean的原始名称
         String nameToLookup = originalBeanName(name);
         //若为 AbstractBeanFactory 类型,委托父类处理
         if (parentBeanFactory instanceof AbstractBeanFactory) {
            return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                  nameToLookup, requiredType, args, typeCheckOnly);
         }
         else if (args != null) {
            //  委托给构造函数 getBean() 处理
            return (T) parentBeanFactory.getBean(nameToLookup, args);
         }
         else if (requiredType != null) {
            return parentBeanFactory.getBean(nameToLookup, requiredType);
         }
         else {
            // 没有 args,委托给标准的 getBean() 处理
            return (T) parentBeanFactory.getBean(nameToLookup);
         }
      }

      /**
       * 方法参数 typeCheckOnly ,是用来判断调用 #getBean(...) 方法时,表示是否为仅仅进行类型检查获取 Bean 对象
       * 如果不是仅仅做类型检查,而是创建 Bean 对象,则需要调用 #markBeanAsCreated(String beanName) 方法,进行记录
       */
      if (!typeCheckOnly) {
         markBeanAsCreated(beanName);
      }

      try {
         /**
          * 从容器中获取 beanName 相应的 GenericBeanDefinition 对象,并将其转换为 RootBeanDefinition 对象
          *   <bean id="tulingParentCompent" class="com.tuling.testparentsonbean.TulingParentCompent" abstract="true">
          <property name="tulingCompent" ref="tulingCompent"></property>
          </bean>
          <bean id="tulingSonCompent" class="com.tuling.testparentsonbean.TulingSonCompent" parent="tulingParentCompent"></bean>
          */
         RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
         //检查当前创建的bean定义是不是抽象的bean定义
         checkMergedBeanDefinition(mbd, beanName, args);

         /**
          *
          * @Bean
         public DependsA dependsA() {
         return new DependsA();
         }

          @Bean
          @DependsOn(value = {"dependsA"})
          public DependsB dependsB() {
          return new DependsB();
          }
           * 处理dependsOn的依赖(这个不是我们所谓的循环依赖 而是bean创建前后的依赖)
          */
         //依赖bean的名称
         String[] dependsOn = mbd.getDependsOn();
         if (dependsOn != null) {
            // <1> 若给定的依赖 bean 已经注册为依赖给定的 bean
            // 即循环依赖的情况,抛出 BeanCreationException 异常
            for (String dep : dependsOn) {
               //beanName是当前正在创建的bean,dep是正在创建的bean的依赖的bean的名称
               if (isDependent(beanName, dep)) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
               }
               //保存的是依赖 beanName 之间的映射关系:依赖 beanName - > beanName 的集合
               registerDependentBean(dep, beanName);
               try {
                  //获取depentceOn的bean
                  getBean(dep);
               } catch (NoSuchBeanDefinitionException ex) {
                  throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
               }
            }
         }

         //创建单例bean
         if (mbd.isSingleton()) {
            //把beanName 和一个singletonFactory 并且传入一个回调对象用于回调
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  //进入创建bean的逻辑
                  return createBean(beanName, mbd, args);
               } catch (BeansException ex) {
                  //创建bean的过程中发生异常,需要销毁关于当前bean的所有信息
                  destroySingleton(beanName);
                  throw ex;
               }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         } else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
               beforePrototypeCreation(beanName);
               prototypeInstance = createBean(beanName, mbd, args);
            } finally {
               afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
         } else {
            String scopeName = mbd.getScope();
            if (!StringUtils.hasLength(scopeName)) {
               throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
            }
            Scope scope = this.scopes.get(scopeName);
            if (scope == null) {
               throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
            }
            try {
               Object scopedInstance = scope.get(beanName, () -> {
                  beforePrototypeCreation(beanName);
                  try {
                     return createBean(beanName, mbd, args);
                  } finally {
                     afterPrototypeCreation(beanName);
                  }
               });
               bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            } catch (IllegalStateException ex) {
               throw new BeanCreationException(beanName,
                     "Scope '" + scopeName + "' is not active for the current thread; consider " +
                           "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                     ex);
            }
         }
      } catch (BeansException ex) {
         cleanupAfterBeanCreationFailure(beanName);
         throw ex;
      }
   }
   ...
   return (T) bean;
}

还是老套路,我们配合图加如上代码一起分析,会更加容易理解一点

看到 transformedBeanName();该方法主要作用是获取最真实的Bean的名称,为什么要这么说呢?

因为我们在将类注册成Bean定义的时候,是可以设置它的别名的,如果不设置Spring默认会以类名的驼峰命名来注册Bean

如果是设置了别名,通过反射是无法去实例化对象的,所以这里是需要获取到它最真实的类名称啦

然后我们往下分析

getSingleton()方法,该方法会去我们的单例池里面寻找,我们的Bean是否已经被创建了,如果被创建了则直接返回

但是我们现在还只是在初始化阶段,想都不用想都知道这里肯定是找不到的啦

当然Spring里的三级缓存也在方法中,我们进去瞧上一波哈

我们可以先看到源码中的三级缓存是啥样子

代码语言:javascript
复制
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    /** 一级缓存 这个就是我们大名鼎鼎的单例缓存池 用于保存我们所有的单实例bean */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** 三级缓存 该map用户缓存 key为 beanName  value 为ObjectFactory(包装为早期对象) */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** 二级缓存 ,用户缓存我们的key为beanName value是我们的早期对象(对象属性还没有来得及进行赋值) */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
}

从如上源码中,我们可以看看三个集合缓存的有什么区别?

我们可以看到他们的缓存集合类型都是Map,且key是String,value是Object类型,只有三级缓存不一样,三级缓存的它的value是ObjectFactory类型

大家伙是否很好奇,为啥三级缓存是value是这个类型呢?

我们现在暂时不深究,待会我们一起看源码就知道有什么作用了,这里我们只需要知道他们的区别即可

获取单例源码如下

代码语言:javascript
复制
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   /**
    * 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
    * IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
    */
   Object singletonObject = this.singletonObjects.get(beanName);
   /**
    * 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
    * IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
    */
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            /**
             * 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
             * 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象(纯净态)
             * 就是早期对象
             */
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  /**
                   * 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
                   */
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     /**
                      * 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
                      * 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
                      */
                     singletonObject = singletonFactory.getObject();
                     //从三级缓存中获取到对象不为空
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     //ObjectFactory 包装对象从三级缓存中删除掉
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

1.首先从一级缓存通过bean的原始名称到singletonObjects集合中去获取,如果获取到直接return

2.如果从一级缓存中获取不到结果或者对象正在创建中(isSingletonCurrentlyInCreation(beanName)),那就从二级缓存通过bean的原始名称到earlySingletonObjects集合中去获取,如果获取到直接return

3.如果一级缓存还是获取不到结果,且allowEarlyReference为true的话,这个时候就会从三级缓存(singletonFactories).getObject()去获取

4.如果三级缓存获取到结果了,就将获取到的结果put到二级缓存(earlySingletonObjects)中,并且从三级缓存(singletonFactories)集合中remove掉

看完获取单例源码后,我们接着回到doGetBean()方法,接着往下分析

我们这里直接看到重点地方,其他的方法我都有写上注释,各位小伙伴知道那些方法的作用即可啦

这里会再一次判断当前Bean是否为单例,我这里是单例Bean,则进入判断

小伙伴们注意了,这里的getSingleton()方法,并不是我上面分析从单例池获取Bean的哦

这里会根据传入的beanName,然后去创建Bean操作了哦,这里使用到了函数编程啦,这里不作多介绍了,不太了解函数编程的小伙伴,可以自行百度一下哦

好,那么我们就直接进入到createBean方法啦,去看看Spring是如何创建Bean的哦

代码语言:javascript
复制
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   if (logger.isTraceEnabled()) {
      logger.trace("Creating instance of bean '" + beanName + "'");
   }
   RootBeanDefinition mbdToUse = mbd;

   // 确保此时的 bean 已经被解析了
   Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
      mbdToUse = new RootBeanDefinition(mbd);
      mbdToUse.setBeanClass(resolvedClass);
   }

   // Prepare method overrides.
   try {
      /**
       * 验证和准备覆盖方法( 仅在XML方式中)
       * lookup-method 和 replace-method
       * 这两个配置存放在 BeanDefinition 中的 methodOverrides( 仅在XML方式中)
       * 在XML方式中 bean 实例化的过程中如果检测到存在 methodOverrides ,
       * 则会动态地位为当前 bean 生成代理并使用对应的拦截器为 bean 做增强处理。
       * 具体的实现我们后续分析,现在先看 mbdToUse.prepareMethodOverrides() 代码块
       */
      mbdToUse.prepareMethodOverrides();
   } catch (BeanDefinitionValidationException ex) {
      throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
            beanName, "Validation of method overrides failed", ex);
   }

   try {
      /**
       * 第一个bean后置处理器
       * 通过bean的后置处理器来进行后置处理生成代理对象,一般情况下在此处不会生成代理对象
       * 为什么不能生成代理对象,不管是我们的jdk代理还是cglib代理都不会在此处进行代理,因为我们的
       * 真实的对象没有生成,所以在这里不会生成代理对象,那么在这一步是我们aop和事务的关键,因为在这里
       * 解析我们的aop切面信息进行缓存
       */
      Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
      if (bean != null) {
         return bean;
      }
   } catch (Throwable ex) {
      throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
            "BeanPostProcessor before instantiation of bean failed", ex);
   }

   try {
      /**
       * 第二个后置处理器
       * 该步骤是我们真正的创建我们的bean的实例对象的过程
       */
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isTraceEnabled()) {
         logger.trace("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
   } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
      // A previously detected exception with proper bean creation context already,
      // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
      throw ex;
   } catch (Throwable ex) {
      throw new BeanCreationException(
            mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
   }
}

老样子,我们直接看到重点部分,其他部分小伙伴们看注释理解即可

我们进入到doCreateBean方法

代码语言:javascript
复制
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   //BeanWrapper 是对 Bean 的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装 bean 的属性描述器
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      //从没有完成的FactoryBean中移除
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
      /**
       * 实例化对象,第二次调用后置处理器
       * 创建bean实例化 使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化 该方法很复杂也很重要
       */
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   //从beanWrapper中获取我们的早期对象(从getWrappedInstance就能看出获取构造方法,也就是获取它的实例,相当于new出来的对象)
   final Object bean = instanceWrapper.getWrappedInstance();
   Class<?> beanType = instanceWrapper.getWrappedClass();
   if (beanType != NullBean.class) {
      mbd.resolvedTargetType = beanType;
   }

   // Allow post-processors to modify the merged bean definition.
   synchronized (mbd.postProcessingLock) {
      if (!mbd.postProcessed) {
         try {
            /**
             * 第三次调用后置处理器
             * 进行后置处理 @AutoWired @Value的注解的预解析
             */
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
         } catch (Throwable ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Post-processing of merged bean definition failed", ex);
         }
         mbd.postProcessed = true;
      }
   }

   /**
    * 缓存单例到三级缓存中,以防循环依赖
    * 判断是否早期引用的Bean,如果是,则允许提前暴露引用
    * 判断是否能够暴露早期对象的条件:
    * 是否单例
    * 是否允许循环依赖(重点,Spring默认为true)
    * 是否正在创建的Bean
    */
   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");
      }

      /**
       * 第四次调用后置处理器,判断是否需要AOP
       * 把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,该方法内部调用getEarlyBeanReference方法
       */
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
      //属性赋值 给我们的属性进行赋值(调用set方法进行赋值)  --》 这里就是自动注入
      //方法里面实现了第五次和第六次调用后置处理器
      populateBean(beanName, mbd, instanceWrapper);
      //进行对象初始化操作(在这里可能生成代理对象)
      //方法里面实现了第七次和第八次调用后置处理器
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   } catch (Throwable ex) {
      if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
         throw (BeanCreationException) ex;
      } else {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
      }
   }

   // 是早期对象暴露
   if (earlySingletonExposure) {
      /**
       * 去缓存中获取到我们的对象 由于传递的allowEarlyReference 是false 要求只能在一级二级缓存中去获取
       * 正常普通的bean(不存在循环依赖的bean) 创建的过程中,压根不会把三级缓存提升到二级缓存中
       */
      Object earlySingletonReference = getSingleton(beanName, false);
      //能够获取到
      if (earlySingletonReference != null) {
         //经过后置处理的bean和早期的bean引用还相等的话(表示当前的bean没有被代理过)
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         //处理依赖的bean
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                           StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                           "] 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.");
            }
         }
      }
   }

   // Register bean as disposable.
   try {
      //注册销毁的bean的销毁接口
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   } catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

配合图,我们直接看到重点部分,createBeanInstance()该方法的主要作用在于,其底层方法会推断出一个合适的构造器,通过反射创建实例化我们的对象,该底层方法我就不跟进去了

我们通过debug调试,看到instanceWrapper是我们注入的类的类型,然后看到wrappedObject里面的属性还是为null,所以我们暂时可以证明Spring还没有给我们的属性进行属性注入

也可以看到我刚执行完instanceWrapper = createBeanInstance(beanName, mbd, args); 我的控制台立马输出无参构造方法的信息,这也就证明了,该方法就是去new出我们的对象的啦

我们继续往下走,看到下图标记的这一行代码,这个地方就是判断Spring是否开启循环依赖的地方,当然啦,我们看到最主要的一个地方,就是this.allowCircularReferences这个属性,该属性的值默认是为true的,所以Spring是默认开启循环依赖的

且主要看到addSingletonFactory()方法,它的底层会提前将当前的Bean放入的三级缓存中,现阶段可以理解将Bean提前暴露出来了

注意哦,这个三级缓存 存储的value是个工厂哦,为什么是存储工厂呢?

首先if判断是需要支持循环依赖,才会进入到addSingletonFactory()方法的,换个思路吧,我上面有说到,我们虽说创建了对象,但是到当前这一步,还没有进行属性赋值,如果没有当前这一步(不将值插入到三级缓存中),走到下面要进行属性赋值的时候,会一直循环的赋值(因为我们的属性是需要注入的,当发现需要注入,又会重新回到前面的getBean()方法那里,重新走一遍),如果我们不放入到缓存中,他会一直创建,而有了缓存直接从缓存读取,节省了很多时间,且我们要知道我们存储的工厂还是能够继续对Bean进行修改的,因为我们的对象还没有成为Bean

代码语言:javascript
复制
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

关闭Spring循环依赖

当然既然Spring默认支持循环依赖,那我想关闭怎么办呢?在这里有以下几种方法关闭

1.通过修改源码的方式,达到关闭Spring循环依赖的效果

2.通过AbstractAutowireCapableBeanFactory里面的API的形式,来继续关闭Spring循环依赖

3.通过Spring的扩展点,来达到关闭Spring循环依赖的效果

好了,我们在接着回到上面开启循环依赖的代码处,继续往下分析,看到populateBean(beanName, mbd, instanceWrapper);这行代码

这行代码就是我们常说的依赖注入啦,给我们的属性注入值,但是可以看到我的debug调试的控制台可以看到

我的属性注入已经开始发生了循环注入

好,我们知道了populateBean(beanName, mbd, instanceWrapper)该方法的是属性注入,我们跟进去

代码语言:javascript
复制
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
   //若bw为null的话,则说明对象没有实例化
   if (bw == null) {
      //进入if 说明对象有属性,bw为空,不能为他设置属性,那就在下面就执行抛出异常
      if (mbd.hasPropertyValues()) {
         throw new BeanCreationException(
               mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
      }
      else {
         // Skip property population phase for null instance.
         return;
      }
   }

   /**
    * 在属性被填充前,给 InstantiationAwareBeanPostProcessor 类型的后置处理器一个修改
    * bean 状态的机会。官方的解释是:让用户可以自定义属性注入。比如用户实现一
    * 个 InstantiationAwareBeanPostProcessor 类型的后置处理器,并通过
    * postProcessAfterInstantiation 方法向 bean 的成员变量注入自定义的信息。
    *当时我们发现系统中的的InstantiationAwareBeanPostProcessor.postProcessAfterInstantiationM没有进行任何处理,
    *若我们自己实现了这个接口 可以自定义处理.....spring 留给我们自己扩展接口的
    *特殊需求,直接使用配置中的信息注入即可。
    */
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      //是否持有 InstantiationAwareBeanPostProcessor
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         //判断我们的后置处理器是不是InstantiationAwareBeanPostProcessor
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            //进行强制转化
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            //若存在后置处理器给我们属性赋值了,那么返回false 可以来修改我们的开关变量,就不会走下面的逻辑了
            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
               return;
            }
         }
      }
   }

   //获取bean定义的属性
   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

   /**
    * 判断我们的bean的属性注入模型
    * AUTOWIRE_BY_NAME 根据名称注入
    * AUTOWIRE_BY_TYPE 根据类型注入
    */
   int resolvedAutowireMode = mbd.getResolvedAutowireMode();
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      //把PropertyValues封装成为MutablePropertyValues
      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
      //根据bean的属性名称注入
      if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
         autowireByName(beanName, mbd, bw, newPvs);
      }
      //根据bean的类型进行注入
      if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
         autowireByType(beanName, mbd, bw, newPvs);
      }
      //把处理过的 属性覆盖原来的
      pvs = newPvs;
   }

   /**
    * 这里又是一种后置处理,用于在 Spring 填充属性到 bean 对象前,对属性的值进行相应的处理,
    * 比如可以修改某些属性的值。这时注入到 bean 中的值就不是配置文件中的内容了,
    * 而是经过后置处理器修改后的内容
    */
   boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
   //判断是否需要检查依赖
   boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

   //提出当前正在创建的beanWrapper 依赖的对象
   PropertyDescriptor[] filteredPds = null;
   if (hasInstAwareBpps) {
      if (pvs == null) {
         pvs = mbd.getPropertyValues();
      }
      //获取所有的后置处理器
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
               if (filteredPds == null) {
                  filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
               }
               pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
               if (pvsToUse == null) {
                  return;
               }
            }
            //对依赖对象进行后置处理
            pvs = pvsToUse;
         }
      }
   }
   //判断是否检查依赖
   if (needsDepCheck) {
      if (filteredPds == null) {
         filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
      checkDependencies(beanName, mbd, filteredPds, pvs);
   }

   /**
    * 其实,上面只是完成了所有注入属性的获取,将获取的属性封装在 PropertyValues 的实例对象 pvs 中,
    * 并没有应用到已经实例化的 bean 中。而 #applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) 方法,
    * 则是完成这一步骤的
    */
   if (pvs != null) {
      applyPropertyValues(beanName, mbd, bw, pvs);
   }
}

我们知道,前面创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值,所以该方法的作用就是为创建好的对象给它的属性赋值,并且调用实现了InstantiationAwareBeanPostProcessors的后置处理器,可以给我们最后一次修改Bean的属性的机会,其他的不多说了,看到我标记的这两行代码,就是属性注入的核心代码,在populateBean里面他会解析你的属性,并且赋值,当发现InstanceA对象里面依赖instanceB,此时又会走getBean()方法,但这个时候你去缓存中去拿是可以拿到的,因为我们在对 createBeanInstance 对象创建完成以后已经放入了缓存当中,所以创建 InstanceB 的时候发现依赖 instanceA,直接就从缓存中去拿,那么此时instanceA和instanceB都创建好了,到此Bean也创建完成,最后将创建好的Bean放入到一级缓存中,也就是我们常说的单例池中

我们可以看到上面有一个for循环,会遍历所有的后置处理器,当然哦,我这里会遍历三次,每一次遍历都是不同的后置处理器,可以结合我下图查看

其中第二张图和第三张图,它们的后置处理器分别为CommonAnnotationBeanPostProcessor和AutowiredAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor的后置处理器主要处理@Resource注解的属性注入

AutowiredAnnotationBeanPostProcessor的后置处理器主要处理@Autowired注解的属性注入

所以我们看到三张图,因为我们的属性注入使用的@Autowired注解,所以会将其他的属性全部实例化掉,也就在控制台输出了信息

扩展延申

1.一级缓存解决循环依赖的问题(一级缓存读取肯定完整的Bean)

详细描述:加入将类A和类B都注入到Bean对象,然后在各自的类中,都有对方的对象,这样去注入的Bean,就会产生循环依赖的问题

解决方式:当去创建Bean A的时候,将BeanA加入到一级缓存,再去创建Bean B的时候,去检查一级缓存是否有该实例,如果有该实例,则不再去创建,是否就已经解决的循环依赖的问题呢

2.二级缓存解决防止多线程下会读取到不成熟的Bean(分隔成熟Bean和不成熟的Bean)

详细描述:就上面的问题描述延申,如果在多线程情况下,我一个线程刚创建Bean对象A,但是还没有实例化属性B,这个时候Bean已经加入到一级缓存中去了,我另外一个线程恰好,也去创建Bean A,发现已经创建好,直接去读取,那么这个时候去读取的Bean里的属性肯定为NULL,那这个时候读取到的Bean就是一个不完整的Bean

解决方式:通过添加二级缓存,去解决不成熟Bean(多线程下存储的可能是不完整的Bean),然后一级缓存是最后存储的Bean,也就是说,一级缓存存储的是完整的Bean

3.三级缓存是解决

详细描述:防止Bean对象重复创建,我们知道三级缓存的value存放的是一个工厂,假如我两个类都注入了相同的属性,那我去实例化一个工厂的时候大概需要10s,有了三级缓存则在缓存中读取,无需重复创建,也节省创建的时间

解决方案:

严谨的说法:

1.在实例化Bean的时候去创建AOP(Spring希望正常的Bean,也就是没有循环依赖的Bean 将AOP放在初始化之后去创建)

2.在实例化(初始化)Bean之后去创建AOP(Spring希望循环依赖的Bean 将AOP放在实例化之后去创建)

3.其实AOP也可以在二级缓存就解决了,为什么要三级缓存中实现呢, Spring每个方法只干一件事情,可能因为单一原则或者是解耦的原因吧,所以才放到三级缓存中

至此,Spring循环依赖源码解析这篇文章就到此结束啦。

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

本文分享自 黎明大大 微信公众号,前往查看

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

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

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