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
,manualSingletonNames
,beanDefinitionMap
,frozenBeanDefinitionNames
是做什么的?
-
beanDefinitionNames:按照注册顺序的beanDefinition名称集合
-
beanDefinitionMap:beanDefinition的一个缓存,key是beanDefinitionName
-
manualSingletonNames:手动注册的单例类名称,按照注册顺序排序
-
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,这些遍历方法通常都是使用迭代器的,我们知道迭代过程中如果我们又对集合进行了添加,移除的操作,会引发快速失败机制如果对快速失败机制不熟悉的请自行百度。为了避免这种情况所以选择新建一个集合然后进行复制