clearMetadataCache方法

81 阅读11分钟

关于clearMetadataCache方法的总结

首先我们还是进入到ConfigurableListableBeanFactory接口中找到clearMetadataCache()方法。

/**
 * Clear the merged bean definition cache, removing entries for beans
 * which are not considered eligible for full metadata caching yet.
 * <p>Typically triggered after changes to the original bean definitions,
 * e.g. after applying a {@link BeanFactoryPostProcessor}. Note that metadata
 * for beans which have already been created at this point will be kept around.
 * @since 4.2
 * @see #getBeanDefinition
 * @see #getMergedBeanDefinition
 * 会去清除beanDefinition的缓存,缓存--填充属性的时候缓存的beanDefinition
 */
void clearMetadataCache();

然后再进入到DefaultListableBeanFactory类中的实现方法:

@Override
public void clearMetadataCache() {
    //该方法返回当前是否有beanDefinition是否被冻结,如果被冻结了就不需要进行merge了
    super.clearMetadataCache();
    this.mergedBeanDefinitionHolders.clear();
    clearByTypeCache();
}

但是当前的实现还有一个父类AbstractBeanFactory实现方法:

/**
 * Clear the merged bean definition cache, removing entries for beans
 * which are not considered eligible for full metadata caching yet.
 * <p>Typically triggered after changes to the original bean definitions,
 * e.g. after applying a {@code BeanFactoryPostProcessor}. Note that metadata
 * for beans which have already been created at this point will be kept around.
 * @since 4.2
 */
public void clearMetadataCache() {
    this.mergedBeanDefinitions.forEach((beanName, bd) -> {
        if (!isBeanEligibleForMetadataCaching(beanName)) {
            bd.stale = true;
        }
    });
}

在父类AbstractBeanFactory中的clearMetadataCache方法里,我们看到他在遍历当前的mergedBeanDefinitions集合,并且每次都会调用isBeanEligibleForMetadataCaching方法来判断当前啊的beanDefinition是否被冻结。

但是他这里的判断是否被冻结的方法在子类DefaultListableBeanFacrtoy中也有实现:

/**
 * Considers all beans as eligible for metadata caching
 * if the factory's configuration has been marked as frozen.
 * @see #freezeConfiguration()
 */
@Override
protected boolean isBeanEligibleForMetadataCaching(String beanName) {
    return (this.configurationFrozen || super.isBeanEligibleForMetadataCaching(beanName));
}
/**
 * Determine whether the specified bean is eligible for having
 * its bean definition metadata cached.
 *
 * @param beanName the name of the bean
 * @return {@code true} if the bean's metadata may be cached
 * at this point already
 */
protected boolean isBeanEligibleForMetadataCaching(String beanName) {
    return this.alreadyCreated.contains(beanName);
}

那么他在执行的时候会将父类的判断一起执行。

总结上述的代码流程为:

  1. 调用父类的方法的clearMetadataCache把所有存在mergedBeanDefinitionMap当中的beanDefinition的stale标记为true(标识需要合并)。
  2. 前提条件是这个容器没有被冻结或者这个beanDefinition所对应的bean没有被创建。
  3. 清除被缓存的依赖项。

这里涉及到mergedBeanDefinitionMap我们下面展开讲解:

  1. 这里需要首先解释什么是mergedBeanDefinitionMap(下面简称mbdmap);spring把扫描出来的beanDefinition或者程序员手动添加的beanDefinition,通过registerBeanDefinition这个方法都存到beanFactory的一个map属性当中——beanDefinitionMap;过程如下图:

  1. 但是需要说明的是,当spring实例化bean的时候并不会直接从这个这个map当中获取beanDefinition来实例化bean;spring容器实例化的时候会在一个mergedBeanDefinitionMap当中获取beanDefinition来实例化bean;

  1. 需要再次说明的是,上图spring容器去实例化一个bean的时候并没有从beanDefinitionMap当中去获取beanDefinition;而不管扫描出来的还是程序员手动添加的beanDefinition都在beanDefinitionMap中;那么这个mergedBeanDefinitionMap当中的数据哪里来的?
  2. 下面讲一下Spring是如何获取一个BeanDefinition的流程:
  3. 其实spring在去获取一个beanDefinition的时候不是单纯的从mergedBeanDefinitionMap当中获取;
  4. 而是首先从mergedBeanDefinitionMap根据名字去获取,如果获取不到则需要进行合并(beanDefinitionMap合并mergedBeanDefinitionMap)当然如果获取到了也不会直接返回;
  5. 还需要进行标识判断;如果标识了需要合并还是会合并的;获取beanDefinition的流程如下图:

  1. 现在讲道理你应该理解了mergedBeanDefinitionMap和beanDefinitionMap之间的区别和联系了;说白了就是git和svn当中的versionmergedBeanDefinitionMap相当于服务器的版本;beanDefinitionMap相当于本地版本;在获取的时候需要判断两个版本是否一样;
  2. spring判断是否最新版本的逻辑主要有两个:
    1. 假设一个beanDefinition在mergedBeanDefinitionMap获取不到,他会尝试去合并;看看合并后最新的版本当中能否获取到;
    2. 假设一个beanDefinition能够获取到需要判断这个beanDefinition当中的标识是否表明了已经过期;
    3. 如果过期了则需要合并;如果不一样则需要合并成最新的版本,合并完最新的版本存入的是mergedBeanDefinitionMap;
  1. 看看spring源码当中的实现:

  1. 至于为什么需要mergedBeanDefinitionMap这个属性;参考视频吧;我讲了有些beanDefinition需要合并;

manualSingletonNames

  • Set manualSingletonNames;
  • 他在spring源码当中定义的就是一个set集合;
  • 用来存放手工注册的bean的名字;
  • 所谓手工注册的bean大体可以理解就是我们通过registerSingleton这种api直接注册的bean;
  • 我们知道正常情况下注册bean(扫描--bd--bean)他们的名字是beanDefinitionMap的key;
  • 存在一个list集合当中List beanDefinitionNames;
/** List of bean definition names, in registration order. */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);

/** List of names of manually registered singletons, in registration order. */
private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
  1. 这两个集合有什么意义呢?只是单纯的为了记录这些bean的名字嘛?还有其他意义嘛?
  • spring查找bean的方式有很多;
  • 比如spring可以根据一个类型去查找一个bean;
  • 假设根据一个B类型去查找;如果B的类型有N个;
  • 那么spring的做法是先根据类型找到所有符合B类型的名字;然后再根据名字取单例池当中获取bean对象;假设我们手工注册的bean没有记录名字;则无法找出来;
  1. 假设没有这两个集合;那么spring怎么找到B的所有bean呢?有人会觉得直接遍历单例池然后根据遍历出来的对象判断类型也可以实现;
  2. 确实如此;但是如果有的bean还没有实例化;则不存在单例池当中;那么就要继续从mergedBeanDefinitionMap当中去遍历了;(查找一个bean需要遍历这两个map)
  3. 上面这两个集合便可以简化这个流程;直接依次遍历这两个集合得到bean的名字;根据名字从mergedBeanDefinitionMap当中或者单例池当中获取;
  4. 上面这两个集合便可以简化这个流程;直接依次遍历这两个集合得到bean的名字;根据名字从mergedBeanDefinitionMap当中或者单例池当中获取;

freezeConfiguration

@Override
public void freezeConfiguration() {
    this.configurationFrozen = true;
    this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
}
/**
 * Whether bean definition metadata may be cached for all beans.
 */
private volatile boolean configurationFrozen;
  1. 不关心最后那行代码其实他就是一个标识;所以要理解这个方法就要看spring当中在哪里使用了这个方法,结合上下文才能对这个方法的所有意义理解透彻;
  2. 根据前面讲的源码分析(我指的视频,不是本文)或者直接看clearMetadataCache方法当中便可以知道,所谓的冻结就是spring不会标识一个bd已经过期
  3. 也就是你如果对一个bd更改了属性(在冻结之后);不会被合并了;
  4. 因为你修改了bd,但是spring不会觉得这个bd已经过期(判断已经冻结,不会合并);故而修改的不会生效;
  5. 但并不是所有的修改不会生效;因为spring在实例化bean的时候会在markBeanAsCreated这个方法当中把mergedBeanDefintionMap清空;
  6. 这样在markBeanAsCreated方法之后再去获取的bd就是最新的(合并--->因为mergedBeanDefintionMap清空了,只能合并);

markBeanAsCreated

/**
 * Mark the specified bean as already created (or about to be created).
 * <p>This allows the bean factory to optimize its caching for repeated
 * creation of the specified bean.
 * 
 * 将指定的 Bean 标记为已创建(或即将创建)。这允许 Bean 工厂优化其缓存以重复创建指定的 Bean。
 *
 * @param beanName the name of the bean
 */
protected void markBeanAsCreated(String beanName) {
    if (!this.alreadyCreated.contains(beanName)) {
        synchronized (this.mergedBeanDefinitions) {
            if (!this.alreadyCreated.contains(beanName)) {
                // Let the bean definition get re-merged now that we're actually creating
                // the bean... just in case some of its metadata changed in the meantime.
                // 现在我们实际上正在创建 bean,让 bean 定义重新合并......以防万一在此期间它的某些元数据发生了变化。
                clearMergedBeanDefinition(beanName);
                //放到alreadyCreated这个set集合中开始创建
                this.alreadyCreated.add(beanName);
            }
        }
    }
}

isConfigurationFrozen()

@Override
public boolean isConfigurationFrozen() {
    return this.configurationFrozen;
}

就是判断整个beanFactory是否被冻结了;这个方法有什么意义呢?这里我们从spring源码当中三处对这个方法的调用来分析他的意义(当然spring当中可能不止三处对这个方法的调用;更多的需要大家自己去找,我这只是抛砖引玉)

  1. 在clearMetadataCache的父类方法方法当中的isBeanEligibleForMetadataCaching方法里面其实就对是否冻结做了判断,只不过用的不是这个方法,是直接对this.configurationFrozen的值进行判断;当然咯等同于调用这个方法;意思就是如果被冻结了则不会合并;这个好理解,前面也说过,你既然冻结了,肯定不建议你在修改beanDefinititon了;所以不会合并,也就是不会过期;导致修改失效(需要说明的是不是所有都不生效;原因参考上文说的markBeanAsCreated,或者参考视频里面我做的测试)。
  2. registerBeanDefinition方法当中;如果注册了一个不存在的(新的)beanDefintion那么spring会要做判断,是否被冻结;如果被冻结了,则需要调用clearMetadataCache当中的clearByTypeCache();清除allBeanNameByType<Class,String[]>这个map(这个map参考下文);因为spring觉得当beanFactory被冻结了表示spring容器已经开始对bean进行实例化了有可能存在缓存,而这个时候如果你注册了新的beanDefinition则这个缓存不具备时效性; 当然如果你注册了一个已经存在的beanDefintion;spring也会在resetBeanDefinition进行clearByTypeCache,只不过不需要判断是否冻结了;
  3. 根据类型查找bean的时候;前面讲过spring查找一个bean的方式很多;其中有一种就是根据类型来查找bean,流程是先根据类型找到该类型所匹配的所有名字;在查找的时候如果被冻结了;会先从缓存种查找;因为原因和上面一样;如果容器没有被冻结,表示spring可能都没有开始实例化,缓存不可能存在;如果被冻结了则有可能存在缓存当中;

clearMetadataCache意义

这个时候你再来看clearMetadataCache的第一个意义:

  1. 调用父类的方法的clearMetadataCache把所有存在mergedBeanDefinitionMap当中的beanDefinition的stale标记为true;标识需要合并;现在应该已经理解了;
  2. 那么问题是基于他的这个意义;什么时候会有必要调用这个clearMetadataCache?
  3. 很显然当我们往容器里面注册了一个新的beanDefinition的时候,或者我们修改了beanDefinition的时候都需要去调用这个方法;
  4. 有的人会问我添加新的beanDefinition没有显示调用这个方法啊;为什么没有任何异常?
  5. 这是因为绝大部分情况下我们添加或者修改beanDefinition都是通过spring当中的bean工厂的后置处理器回调postProcessBeanFactory方法来完成的——beanFactoryPostProcessor#postProcessBeanFactory;
  6. 而beanFactoryPostProcessor的回调是在invokeBeanFactoryPostProcesor这个方法当中完成了;在这个方法最后spring帮我们显示调用了clearMetadataCache这个方法;
  7. 另外需要说明的是如果只是单纯为了合并;那么添加新的beanDefinition不是很有必要调用clearMetadataCache;
  8. 因为添加新的总是会合并——获取不到spring会自动进行一次合并动作只有修改了已经存在的beanDefinition是一定要调用clearMetadataCache的
  9. 尽管如此当你往容器当中添加了新的beanDefinition我们依然还是可以调用clearMetadataCache;
  10. 因为clearMetadataCache的第二个意义——清除被缓存的依赖项;(需要说明的是如果你清除缓存依赖项可以单纯只调用clearByTypeCache,没必要调用clearMetadataCache);
  11. spring为了提高效率在完成依赖注入的过程中会去把已经找到的依赖的类型所对应的所有名字缓存起来;
  12. 比如A里面依赖了B;C里面也依赖了B,D里面也依赖了B;那么在实例化这些bean的时候会完成这些bean的自动注入;比如实例化A的时候去找到B(找到B这个过程很复杂)并且依赖上;
  13. 之后便把B这个类型所对应的名字存放到一个map当中(allBeanNameByType<Class,String[]>);
  14. 这个map当中的key是B的类型;value是这个类型所对应的所有名字;
  15. 这样下次D再依赖B的时候,spring拿到B的类型从这个缓存当中去找到B类型所有的bean的名字(如果有多个名字后面会处理,这个是依赖注入的逻辑后面再分析);
  16. 根据名字从单例池获取一个对象直接依赖上(如果没有这个缓存,每次spring再注入的时候都需要根据这个类型找到这个类型再容器当中到底有几个名字,说白了就是几个对象;这个过程很复杂);
  17. 所以当我们添加了一个新的beanDefinition的时候,那么这个缓存map就不具备时效性了;故而需要清除;
  18. 最后当我们直接往容器当中注册一个bean的时候也需要调用这个方法(clearMetadataCache);原理也是因为那个缓存不具备时效性了;