《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
回顾
spring解析Bean主要分为三步
-
创建普通的BeanDefinition(解析默认的属性、XML标签)
-
修饰创建出来的BeanDefinition(解析自定义的属性和标签)
-
注册BeanDefinition
下面就来看一下如何进行注册的!对应代码就是下面这一行,
可以看到,解析和修饰BeanDefinition都是交由delegate去做的,也就是BeanDefinitionParserDelegate,而注册Bean则是由BeanDefinitionReaderUtils去做的,但其实本质上是由BeanDefinitionRegistry去做的,也就是getReaderContext().getRegistry()
其实从某种意义上,修饰也是解析,只不过解析的是自定义属性和标签,所以可以归纳为
-
BeanDefinitionParserDelegate:用于创建、解析Bean
-
BeanDefinitionReaderUtils:用于注册Bean
具体的实现代码如下
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
// 从BeanDefinitionHolder去获取Bean的名字
String beanName = definitionHolder.getBeanName();
//根据Bean的名字与BeanDefinitionHolder里的BeanDefinition进行注册
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
//获取Bean的别名(之前解析过别名,即alias属性)
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
//遍历所有别名
for (String alias : aliases) {
//注册别名,注意别名映射的是bean的名字
registry.registerAlias(beanName, alias);
}
}
}
可以看到,注册Bean其实就是其名字的注册以及别名的注册(而且本质上是由BeanDefinitionRegistry去做的,BeanDefinitionReaderUtils本质上只是起了个组合作用而已)
-
获取Bean的名字,根据Bean的名字与BeanDefinition(即Bean的内容)注册
-
获取Bean的别名,根据Bean的别名与Bean的名字进行注册,这里之所以不用BeanDefinition进行注册,是因为前面已经根据Bean的名字注册过了,所以仅仅需要Bean的名字就能够找到BeanDefinition了
根据Bean的名字进行注册
可以看到,对于BeanDefinitionRegistry,总共有三个实现类
-
SimpleBeanDefinitionRegistry
-
DefaultListableBeanFactory
-
GenericApplicationContext(默认实现类)
而默认的实现类是GenericApplicationContext,可以看到,现在注册Bean的工作已经交由BeanFactory去做了,所以BeanFactory的工作其实就是用来注册Bean,与IOC容器进行交互的!
并且GenericApplicationContext调用beanFactory进行实现Bean注册
并且BeanFactory的实现类为DefaultListableBeanFactory(相当于GenericApplicationContext装饰了DefaultListableBeanFactory)
下面就来看看代码是怎么实现的
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
//进行校验
//判断BeanName和BeanDefinition是否为空,只要其中一个为空就结束了
Assert.hasText(beanName, “Bean name must not be empty”);
Assert.notNull(beanDefinition, “BeanDefinition must not be null”);
//对Bean进行校验
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//对Bean进行校验
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
“Validation of bean definition failed”, ex);
}
}
//判断Bean是否存在,这就涉及到了底层存储Bean的对象
//为一个名为beanDefinitionMap的map容器
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
//如果存在
if (existingDefinition != null) {
//如果存在且在配置文件中配置了Bean不允许被覆盖
if (!isAllowBeanDefinitionOverriding()) {
//抛出异常
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
//如果可以被覆盖
//且覆盖的Bean的角色权限较大
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
//进行日志记录
if (logger.isInfoEnabled()) {
logger.info(“Overriding user-defined bean definition for bean '” + beanName +
“’ with a framework-generated bean definition: replacing [” +
existingDefinition + “] with [” + beanDefinition + “]”);
}
}
//如果覆盖的Bean的角色权限比较小或等于
//判断两个Bean是不是一样的
else if (!beanDefinition.equals(existingDefinition)) {
//如果不一样,日志记录
if (logger.isDebugEnabled()) {
logger.debug(“Overriding bean definition for bean '” + beanName +
“’ with a different definition: replacing [” + existingDefinition +
“] with [” + beanDefinition + “]”);
}
}
//两个Bean是一样的
else {
if (logger.isTraceEnabled()) {
logger.trace(“Overriding bean definition for bean '” + beanName +
“’ with an equivalent definition: replacing [” + existingDefinition +
“] with [” + beanDefinition + “]”);
}
}
//进行覆盖替换,前面的检查都没什么用,只是记录一些信息而已
//规定到底都是要记录的
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
//如果容器里面不存在这个Bean
//判断是否容器已经初始化好了
//这里底层是判断一个名为alreadyCreated的set集合是否为空
//这个集合存放的就是已经创建好的Bean的BeanName!!!
//其实就是判断IOC容器是否已经初始化了,即Spring项目已经启动了
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
//如果容器里面有Bean了,就很有可能产生并发问题
//因为IOC容器,beanDefinitionMap是一个全局变量,存在并发访问的情况
//所以在注册Bean的时候,对beanDefinitionMap进行上锁
synchronized (this.beanDefinitionMap) {
//往容器中去添加Bean
//其实注册的本质就是往容器中添加Bean
//透露一下,其实beanDefinitionMap是一个ConcurrentHashMap
//可见Spring对于IOC容器的问题是做的死死的!!!
this.beanDefinitionMap.put(beanName, beanDefinition);
//由于容器已经存在,所以需要更新
//新建一个ArrayList去存储新的beanName
//容量为原来的beanName集合+1
//个人猜测,这是为了避免内存泄漏
//因为要加载的Bean肯定是有限的,但不知道有多少个而已!
//而这里使用ArrayList去存储所有的BeanName,难免会出现数组容量不够的情况
//如果交由ArrayList自己的扩容机制去做,很大可能就会导致数组容量超出
//从而产生内存泄漏!
//因为容器已经在使用了,不能对容器本身进行修改,只能采用替换的策略!!
//得到的优点就是减少了内存泄漏
List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
//让新的beanName容器添加旧的beanName容器的所有beanName
updatedDefinitions.addAll(this.beanDefinitionNames);
//再将注册的新Bean的beanName加进去
updatedDefinitions.add(beanName);
//让变量指向新的beanName容器
this.beanDefinitionNames = updatedDefinitions;
//更新manualSinletonNames的set集合
//这个集合用来判断beanName是否重复的,重复就代表重复bean了
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
// 如果是刚启动直接添加即可
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
//更新manualSinletonNames的set集合
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
//重置bean的所有缓存
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
同理,对于更新manualSingletonNames也是做了相同的操作,判断是否已经初始化IOC容器了,假如已经启动好了,就不能进行新加,而是采用替换的方式,而这个manualSingletonNames则是记录了所有需要手动注册的Bean,假如注册成功了,就要从这个集合中移除!
这里还是用jdk1.8的新特性,传了一个Consumer接口(消费者)和一个Predicate接口(判断)
private void updateManualSingletonNames(Consumer<Set> action, Predicate<Set> condition) {
//判断容器是否已经启动好了
if (hasBeanCreationStarted()) {
//启动好了就进行加锁,并且采用替换方式来修改
// Cannot modify startup-time collection elements anymore (for stable iteration)
//锁住的仍然是IOC容器
synchronized (this.beanDefinitionMap) {
//利用传来的predicate接口进行判断
if (condition.test(this.manualSingletonNames)) {
//利用旧的集合创建新的集合
Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
//进行删除
action.accept(updatedSingletons);
//直接将新的BeanName的set容器进行替换
this.manualSingletonNames = updatedSingletons;
}
Java面试核心知识点笔记
其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。
Java中高级面试高频考点整理
最后分享Java进阶学习及面试必备的视频教学
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
ingletonNames = updatedSingletons;
}
Java面试核心知识点笔记
其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。
[外链图片转存中…(img-zTTRiVYU-1714767519614)]
Java中高级面试高频考点整理
[外链图片转存中…(img-g7owIWR5-1714767519614)]
[外链图片转存中…(img-IoKLkAOO-1714767519615)]
最后分享Java进阶学习及面试必备的视频教学
[外链图片转存中…(img-Ugnvx5Ee-1714767519615)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!