Spring源码阅读 之 bean的注册

在前面我们已经学习了配置的加载,读取,解析。现在我们已经能够将一份配置转变成对应的一个个beandefinition了,我们知道Spring是一个IOC的容器,那么我们如何将这个一个个beandefinition放入我们的容器呢?换而言之,如何进行注册呢?相信看完本文后你会豁然开朗


我们要分析的核心代码就是 org.springframework.beans.factory.support.DefaultListableBeanFactory的registerBeanDefinition方法,代码如下:

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            // 这里会对MethodOverrides做检查
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                                                   "Validation of bean definition failed", ex);
        }
    }
	// 判断是否有同名的bean被注册过了
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 是否允许直接进行负载
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }
        else if (existingDefinition.getRole() < beanDefinition.getRole()) {
		// ....省略部分代码,都是输出日志的
            
        // beanName做key放入map中,完成注册    
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        /**
         *这段代码比较难理解
         *我会在下面进行一一分析
         */
        if (hasBeanCreationStarted()) {
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }
        else {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    // 需要重置BeanDefinition,
    // 当前注册的bean的定义已经在beanDefinitionMap缓存中存在,
    // 或者其实例已经存在于单例bean的缓存中	
    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}
beanDefinitionNames,manualSingletonNamesbeanDefinitionMapfrozenBeanDefinitionNames是做什么的?
  1. beanDefinitionNames:按照注册顺序的beanDefinition名称集合

  2. beanDefinitionMap:beanDefinition的一个缓存,key是beanDefinitionName

  3. manualSingletonNames:手动注册的单例类名称,按照注册顺序排序

  4. frozenBeanDefinitionNames:在完成所以示例的初始化,Spring会将配置冻结起来,代表能缓存所有的bean的元数据,frozenBeanDefinitionNames保存的其实就是beanDefinitionNames,只不过是以数组形式保存的,用数组的原因是因为frozenBeanDefinitionNames在期望情况下不应该发生改变

hasBeanCreationStarted(),这是做什么的?为什么这种情况下需要加锁?

​ 这里主要是判断是不是已经有bean被创建了,请注意,正常来说到目前为止,我们并有创建任何的bean,到现在我们做的仅仅是解析配置,并注册beanDefinition,如果有bean被创建意味着什么呢?大家可以思考下,当我们已经创建bean,代表着我们已经开始利用容器去进行我们的业务操作了,类似于我们执行了如下代码:

public static void main(String[] args) {
  1.  ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
  2.  Object d1 = context.getBean("d2");
  3.   /** 利用获取的d1进行业务操作*/
  4.  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      factory.registerBeanDefinition("我要再注册一个bean",new GenericBeanDefinition());
}

在这种情况下,Spring容器无法保证使用者是在线程安全的情况下调用了,也就是无法保证下面的代码不会出现线程安全问题,所以需要加锁。

this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }

另外,我们需要注意的是,虽然beanDefinitionMap底层使用的是ConcurrentHashMap,但是需要注意的是,ConcurrentHashMap只能保证自身操作的线程安全,并不能保证整体业务的线程安全,大家不要产生误解!

为什么要将this.beanDefinitionMap.put(beanName, beanDefinition)也放入同步代码块中?

我们之前已经知道了beanDefinitionNames是要按照注册顺序保存的,所以必须要加放入的动作也进行同步,不然可能出现顺序错误

为什么在加锁后给集合中添加元素还要进行一次类似于复制的操作(addAll)?

我们在业务操作过程中,很可能调用Spring的某些方法,这些方法需要遍历beanDefinitionNames,这些遍历方法通常都是使用迭代器的,我们知道迭代过程中如果我们又对集合进行了添加,移除的操作,会引发快速失败机制如果对快速失败机制不熟悉的请自行百度。为了避免这种情况所以选择新建一个集合然后进行复制

发布了135 篇原创文章 · 获赞 84 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41907991/article/details/97614337
今日推荐