Spring Bean生命周期之BeanDefinition的合并过程详解
作者:码农的进阶之路 发布时间:2023-11-29 02:50:35
写在前面
注:本文章使用的 SpringBoot 版本为 2.2.4.RELEASE,其 Spring 版本为 5.2.3.RELEASE
前言
书接上文,BeanDefinition注册到IoC容器后,紧接着就是要使用Bean了,要使用必须先要获取Bean,这里我们就以DefaultListableBeanFactory#getBean
方法来引出本次讨论的内容:BeanDefinition的合并
通过前面的章节我们了解到了BeanDefinition,那什么是BeanDefinition的合并呢?为什么要进行合并呢? 带着这个问题,我们到源码中去找找答案。
为了使源码逻辑有个参照,这里先给出一个案例,在分析源码时 将这个案例也代入进去方便我们理解源码
BeanDefinition的合并源码分析
实体类
@Data
public class SuperUser implements Serializable {
private String address;
public SuperUser(String address) {
this.address = address;
}
public SuperUser() {
}
}
@Data
@ToString(callSuper = true)
public class User extends SuperUser {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
基于GenericBeanDefinition注册有层次的Bean
public class GenericBeanDefinitionDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//父BeanDefinition
GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
rootBeanDefinition.setBeanClass(SuperUser.class);
//设置参数
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("address", "地址");
rootBeanDefinition.setPropertyValues(propertyValues);
//子BeanDefinition
GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
childBeanDefinition.setBeanClass(User.class);
//设置构造参数
ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
argumentValues.addIndexedArgumentValue(0, "我就是我");
argumentValues.addIndexedArgumentValue(1, 18);
childBeanDefinition.setConstructorArgumentValues(argumentValues);
childBeanDefinition.setParentName("superUser");
//类型相同时 以子类为主
childBeanDefinition.setPrimary(true);
context.registerBeanDefinition("superUser", rootBeanDefinition);
context.registerBeanDefinition("user", childBeanDefinition);
context.refresh();
User user = context.getBean("user", User.class);
System.out.println(user);
SuperUser superUser = context.getBean("superUser", SuperUser.class);
System.out.println(superUser);
context.close();
}
}
在分析源码时我们要有侧重点,这里会将不太相关的逻辑一带而过。
AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//将name解析为beanName,如果传入的是alias,根据aliasMap进行转换,我们在前面介绍过了
final String beanName = transformedBeanName(name);
Object bean;
// 如果是单例Bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//省略日志输出
// 这里的逻辑是根据beanName判断是否为FactoryBea,并采用相应方式去处理
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
//如果不是单例对象
else {
// 对原型对象进行验证,如果当前beanName已经在创建中了 抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 获取父BeanFactory,前面我们介绍过了 BeanFactory允许有层级,可已存在父BeanFactory
BeanFactory parentBeanFactory = getParentBeanFactory();
//如果存在父BeanFactory 去父BeanFactory中查找bean
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
//省略去父BeanFactory查找Bean的过程
}
//typeCheckOnly默认为false ,这里将beanName放到alreadyCreated集合中 表示该Bean正在创建中
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// 这里来到了我们要重点关注的地方了,bd的合并 ⭐️
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
//如果存在依赖Bean,需要进行依赖查找
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
// 省略dependsOn 依赖查找代码
}
// 这里的if..else if .. else 是根据scope取值来的
//scope=singleton时
if (mbd.isSingleton()) {
//省略单实例Bean创建过程
}
//scope=prototype时
else if (mbd.isPrototype()) {
//省略Prototype Bean创建过程
}
//scope=request、application、session时
else {
// 省略其他Scope Bean的创建过程
}
if (requiredType != null && !requiredType.isInstance(bean)) {
//省略类型转换代码
}
// 返回创建的Bean
return (T) bean;
}
上面的方法实现比较长、比较复杂,这里只对重要的地方进行些注释说明并将与本次讨论无关的代码先行进行注释。
下面就进入到BeanDefinition的合并逻辑了
//假设beanName=user
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// 检查缓存,若存在直接返回
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
//getBeanDefinition(beanName)==>实际上去DefaultListableBeanFactory.beanDefinitionMap中根据key查找BeanDefinition,这在注册阶段已经放到beanDefinitionMap了。
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd)
throws BeanDefinitionStoreException {
return getMergedBeanDefinition(beanName, bd, null);
}
//根据上面的举例可知beanName=user,bd是User类的BeanDefinition,containingBd=null
protected RootBeanDefinition getMergedBeanDefinition(
String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)
throws BeanDefinitionStoreException {
synchronized (this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
// 尝试从缓存中拿
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
if (mbd == null) {
//如果当前BeanDefinition没有指定parentName,说明其不存在父BeanDefinition,不需要合并。以RootBeanDefinition形式展现
if (bd.getParentName() == null) {
// 如果bd是RootBeanDefinition类型,直接类型转换
if (bd instanceof RootBeanDefinition) {
mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();
}
else {
//通过bd属性构造RootBeanDefinition
mbd = new RootBeanDefinition(bd);
}
}
else {
// 走到这里说明存在parentName,当前bd需要与其父bd合并
BeanDefinition pbd;
try {
//得到父BeanName
String parentBeanName = transformedBeanName(bd.getParentName());
//!beanName.equals(parentBeanName) 条件成立 说明当前beanName属于子bd
if (!beanName.equals(parentBeanName)) {
//递归地以父bd名称 查找父BeanDefinition。之所以递归地查找,是因为 可能此时的parentBeanName还有父,实体类存在多重继承关系
pbd = getMergedBeanDefinition(parentBeanName);
}
else {
//走到这里,说明beanName.equals(parentBeanName),很有可能是父bd查找BeanDefinition时走来的。
//获取父BeanFactory,BeanFactory也是有层次的,有父子关系的,可参见ConfigurableBeanFactory#setParentBeanFactory
BeanFactory parent = getParentBeanFactory();
if (parent instanceof ConfigurableBeanFactory) {
pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);
}
else {
throw new NoSuchBeanDefinitionException(parentBeanName,
"Parent name '" + parentBeanName + "' is equal to bean name '" + beanName +
"': cannot be resolved without an AbstractBeanFactory parent");
}
}
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName,
"Could not resolve parent bean definition '" + bd.getParentName() + "'", ex);
}
// pbd是父BeanDefinition,由其构造为RootBeanDefinition
mbd = new RootBeanDefinition(pbd);
//bd是子BeanDefinition,主要是继承父类的属性,并覆盖与父类同名的属性,有兴趣的可以看一下overrideFrom方法实现
mbd.overrideFrom(bd);
}
// 如果父bd未指定scope,则设置默认值
if (!StringUtils.hasLength(mbd.getScope())) {
mbd.setScope(RootBeanDefinition.SCOPE_SINGLETON);
}
//由于containingBd=null 这里就不看了
if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
mbd.setScope(containingBd.getScope());
}
if (containingBd == null && isCacheBeanMetadata()) {
this.mergedBeanDefinitions.put(beanName, mbd);
}
}
//最终返回根据当前beanName找到的bd
return mbd;
}
}
分析了上面的源码,我们试着总结一下:
1、如果不存在parentName,即不需要被合并,直接将bd转为RootBeanDefinition 返回即可
2、如果存在parentName
先根据parentName 找到父bd,若实体存在多级继承关系,则需要递归地查找。
将父bd转为RootBeanDefinition,并将子bd与父bd进行合并
设置一些其他属性
来源:https://blog.csdn.net/zyxwvuuvwxyz/article/details/123228093


猜你喜欢
- 背景在开发需求当中,当有总收益、总用户数等数字要显示时,为了更好的给用户提供展示效果,往往会想加入炫酷的数字滚动动画,使呆板平静的数字变得灵
- Android 2.3.7.r1 按menu键时会停止录像。改成录像时按menu键不做处理,可做如下修改: 在packages/apps/C
- 最近工作需要,自定了一个颜色选择器,效果图如下:颜色种类是固定的,圆环上有个指示器,指示选中的颜色,这个定义起来应该是很简单了,直接上代码。
- JSR303 是一套 JavaBean 参数校验的标准1、pom导入依赖<dependency><groupId>o
- 这是之前软工课设我写的java访问mysql工具类,它经过了多轮的测试,应该能够适应大多数的操作需求。比之前大二写的更鲁棒,更易用。pack
- 最近在做项目的时候有用到对两个集合中的元素进行对比求其交集的情况,因为涉及到的数据量比较大,所以在进行求两个集合中元素交集的时候,就应该考虑
- 这篇文章中我们来继续学习Picasso中还提供了哪些扩展功能,一个好的框架一定是扩展性强的,你需要的我刚好有。下面看一下都提供了哪些扩展功能
- 1:首先。创建一个springboot项目,这里我使用以及构建好基本框架的脚手架,打开是这个样子:Result类:已经封装好了三种返回类型的
- 现在的应用在注册登录或者修改密码中都用到了短信验证码,那在android中是如何实现获取短信验证码并自动填写的呢?首先,需要要在manife
- Quote在学习 Kotlin 的过程中,对 Kotlin 的类型系统产生了好奇,Kotlin 是否存在类似于 Java 中 Object
- 前言本文将提供一个redis的工具类,可以用在Spring boot以及Spring Cloud项目中,本工具类主要整合了将Redis作为N
- 关键词IDEA 如何控制编辑左侧的功能图标 ICONIDEA 左侧的图标不见了怎么恢复1、操作步骤依次打开 File | Settings
- 有一天,你写了好多好多带“形参”的构造函数(就是“方法”,同义),而且需要向这些构造函数里传递同样的“实参”,然后你就憨憨地一个一个函数的调
- 对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除。一、
- 同步日志的业务流程处理和日志打印是在同一个线程,日志打印的过程实际上是写文件IO的过程,这个过程是相对耗时的,并且会阻塞主线程的执行,只有日
- 前言不知道你是否参加过拼多多上邀请微信好友砍价功能,这个功能实现首先需要考虑的就是获取微信用户的信息。获取用户信息就是获取公众号下微信用户的
- 一直写过数组全排列的算法,当时接触的是使用回溯的方法,这样可以保证生成的全排列一定是按照字典序的,但是今天在做leetcode上的一道题时,
- 本文实例为大家分享了android自定义环形对比图的具体代码,供大家参考,具体内容如下1.首先在res/values里创建一个attr.xm
- 本文实例讲述了C#基于COM方式读取Excel表格的方法。分享给大家供大家参考,具体如下:using System;using System
- 百度了许多相关资料,对两种修改app图标的方式进行总结:第一种:(最简单的方法)将你准备好的 图标放入res目录下的drawable,在An