Spring Bean生命周期之Bean的注册详解

目录
  • 前言
  • BeanFactory的继承体系
  • Bean的注册
  • alias别名的注册
  • 总结

前言

上篇文章介绍了Bean元信息的配置与解析过程,限于篇幅Bean注册过程就没展开。

这里主要围绕BeanDefinitionReaderUtils#registerBeanDefinition展开分析下Bean注册过程

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {
		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

上面无论是注册bd还是建立alias-beanName之间的关系,均用到了BeanDefinitionRegistry,因此我们就以它为突破口来展开

BeanFactory的继承体系

对图中常用接口或类进行说明:

  • ListableBeanFactory 集合类型BeanFactory 提供一种可以查找所有Bean实例的能力

    • getBeanNamesForType(Class) 根据类型去查找Bean名称列表不会强制Bean的初始化,可从源码中看出来
    • getBeansOfType(Class) 根据类型去查找Bean实例列表,会强制Bean的初始化,可从源码中看出来
    • getBeanNamesForAnnotation(Class) 根据注解类型获取Bean名称列表
    • getBeansWithAnnotation(Class) 根据注解类型获取Bean实例列表
    • findAnnotationOnBean(String,Class) 根据指定名称+标注类型获取Bean实例
  • Hierarchical([ˌhaɪəˈrɑːkɪkl])BeanFactory 层次性BeanFactory,有父子容器的概念,可在ConfigurableListableBeanFactory设置其父容器
    • getParentBeanFactory() 获取父容器
    • boolean containsLocalBean(String name) 在当前容器中查找是否存在该名称的Bean实例
  • SingletonBeanRegistry 单实例BeanFactory,与单实例有关
  • ConfigurableBeanFactory 可配置的BeanFactory,这个一般不用于应用程序,是给其他BeanFactory扩展用的。的确,定义了很多配置方法
  • ConfigurableListableBeanFactory 可配置的集合类型的BeanFactory
  • AutowireCapableBeanFactory 提供具有自动装配能力的BeanFactory

透过继承体系可以看出,BeanDefinitionRegistry的实现类是DefaultListableBeanFactory,该类同时实现了诸多接口,可谓是BeanFactory中集大成者,因此我们到DefaultListableBeanFactory中阅读下bd注册及别名注册的源码

Bean的注册

先来分析下DefaultListableBeanFactory的几个重要的成员属性

// 这个实质上就是IoC容器中Bean的载体,没错 它很重要,但它是无序的
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
//它代表了bd名称的集合,它是有序的 遵循bd注册的顺序
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
// 这是已创建bd名称的集合,在doGetBean方法根据beanName创建Bean时,beanName会被加到此集合中
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

上面两个属性都比较重要,两者结合使用的话可以实现bd的顺序访问(其实就是遍历beanDefinitionNames集合时,使用beanDefinitionMap去获取bd)

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
		//对beanName、bd进行非空验证
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
		//如果bd是AbstractBeanDefinition类型,则对bd进行验证(一般情况下 我们场景的bd都是继承自AbstractBeanDefinition的)
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
			   //bd验证
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				//省略异常代码
			}
		}
		//从beanDefinitionMap根据beanName取bd
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		//如果beanName名称的bd已经存在
		if (existingDefinition != null) {
			//如果不允许Bean被重新注册 则抛出异常,这里默认值是true
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			//如果已被注册bd的角色值小于当前待注册bd的角色值
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// 省略日志输出
			}
			//如果已注册的同名bd 与本次注册的bd不相同
			else if (!beanDefinition.equals(existingDefinition)) {
				//省略日志输出
			}
			else {
				//省略日志输出
			}
			//将beanName-bd键值对放入beanDefinitionMap集合
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			//流程走到这里 说明在beanDefinitionMap中不存在同名bd
			//条件成立 说明alreadyCreated不为空 即有bd已被创建
			if (hasBeanCreationStarted()) {
				// 如果在此之间 有bean正在被创建 则这里进行加锁处理
				synchronized (this.beanDefinitionMap) {
				    //将beanName-bd键值对放入beanDefinitionMap集合
					this.beanDefinitionMap.put(beanName, beanDefinition);
					//将beanName添加到beanDefinitionNames集合中
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					//如果beanName是手动注册的单例Bean名称,则更新manualSingletonNames
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						//这里从集合中删除的原因个人理解:
						//manualSingletonNames记录的是在registerSingleton时被添加的单实例beanName,而这里注入的不是单实例Bean。因为manualSingletonNames包含了此beanName,因此需要剔除
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				//如果没有bean在被创建
				//将beanName-bd键值对放入beanDefinitionMap集合
				this.beanDefinitionMap.put(beanName, beanDefinition);
				//将beanName添加到集合中
				this.beanDefinitionNames.add(beanName);
				//这里从manualSingletonNames中剔除,个人理解为拖地操作,毕竟若集合中没有此beanName 也remove不了
				this.manualSingletonNames.remove(beanName);
			}
			//这个集合表示冻结配置时缓存的beanName集合,暂时未理解透此集合的用途
			this.frozenBeanDefinitionNames = null;
		}
		//如果已存在同名bd或已存在同名的单例对象,则重置所有已被缓存的同名的bd数据,因此这里bd注册成功后,肯定后续还会再生成Bean的
		if (existingDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

其实分析下来发现Bean注册的过程还是比较容易理解的,下面试着总结一下:

  • 若bd未被注册过,则将bd信息存入BeanDefinitionMap等集合中
  • 若bd已被注册过,允许覆盖注册的情况下,将bd信息存入BeanDefinitionMap等集合中,并清除已被缓存的同名bd信息

下面看一下清除bd信息的代码逻辑

protected void resetBeanDefinition(String beanName) {
		// 如果此bd属于被合并的BeanDefinition,则这里将其从MergeBeanDefinition集合中剔除
		clearMergedBeanDefinition(beanName);
		// 如果已存在同名的单例对象 则销毁,具体细节先不展开
		destroySingleton(beanName);
		// 这里for循环逻辑与MergeBeanDefinition相关,如果存在MergedBeanDefinitionPostProcessor,则重置此bd
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			if (processor instanceof MergedBeanDefinitionPostProcessor) {
				((MergedBeanDefinitionPostProcessor) processor).resetBeanDefinition(beanName);
			}
		}
		// BeanDefinition运行有层级的,如果此bd拥有多个父级bd,那么这里递归地重置其父bd
		for (String bdName : this.beanDefinitionNames) {
			if (!beanName.equals(bdName)) {
				BeanDefinition bd = this.beanDefinitionMap.get(bdName);
				if (beanName.equals(bd.getParentName())) {
					resetBeanDefinition(bdName);
				}
			}
		}
	}

alias别名的注册

看了Bean的注册,再来看别名的注册 发现流程比较清晰,基本上一目了然。

//注意 这里的name 不要具象为beanName,虽然我们是从建立beanName--alias关系出发追溯到这里的
public void registerAlias(String name, String alias) {
		//对name、alias进行断言验证
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			//如果别名与beanName相同,那别名就没有必要存在了,因此选择直接从this.aliasMap中移除此别名
			if (alias.equals(name)) {
				this.aliasMap.remove(alias);
				//省略日志输出
			}
			else {
			   //从aliasMap中根据别名获取name
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
				    //如果已存在的registeredName与此此要注册的name一致,那就没必要注册了
					if (registeredName.equals(name)) {
						return;
					}
					//流程走到这里,说明同一个别名,对应两个name,如果不允许alias覆盖 则抛出异常
					if (!allowAliasOverriding()) {
						//省略异常及日志输出
				}
				//这里对alias进行循环检查,避免出现A的别名是B,B的别名是A的情况
				checkForAliasCircle(name, alias);
				//将alias--name 放入aliasMap
				this.aliasMap.put(alias, name);
				//省略日志输出
			}
		}
	}

alias与beanName的映射关系,为根据名称查找Bean又提供了一种思路。就是说除了根据beanName外,也可以根据alias去查找Bean。

这部分源码如下

//name可以是beanName,也可以是alias
public String canonicalName(String name) {
		//局部变量赋值
		String canonicalName = name;
		// Handle aliasing...
		String resolvedName;
		do {
		    //如果从aliasMap中能根据alias分析出beanName
			resolvedName = this.aliasMap.get(canonicalName);
			if (resolvedName != null) {
				canonicalName = resolvedName;
			}
		}
		while (resolvedName != null);
		// 无论入参name是beanName还是alias,这里返回的都应该是beanName了
		return canonicalName;
	}

总结

好了,这篇主要分析了BeanDefinition的注册,顺带着也说了别名的注册情况。既然BeanDefinition已经注册完成,那紧接着就是BeanDefinition的实例化过程了,这个放到下次分析吧。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • Spring解密之XML解析与Bean注册示例详解

    为什么开始看spring的源码 半路转行写代码快一年半了,从开始工作就在使用spring框架,虽然会用,会搭框架,但是很多时候不懂背后的原理,比如:spring是怎样控制事务的,springmvc是怎样处理请求的,aop是如何实现的...这让人感觉非常不踏实,那就开始慢慢边看书边研究spring的源码吧!!! 怎样高效的看源码 我的答案是带着具体的问题去看源码,不然非常容易陷入源码细节中不能自拔,然后就晕了,最后你发现这看了半天看的是啥玩意啊. 引言 Spring是一个开源的设计层面框架,解决了

  • Spring之动态注册bean的实现方法

    Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运行了,而这个基础平台是一直都在运行的,所以在新来任务时,最直观需要注册的就是 DataSource 数据源这个bean了,那么可以怎么玩? I. 主动注册Bean支持 借助BeanDefinition来实现bean的定义,从最终的使用来看,代码比较少,几行而已 public <T> T regis

  • 详解Spring系列之@ComponentScan批量注册bean

    目录 回顾 本文内容 @ComponentScan基本原理和使用 基本原理 使用案例 定义配置类 容器扫描和使用 @ComponentScan进阶使用 源码简析 案例1:使用Filters过滤 案例2:使用自定义的bean名称生成策略 案例3:自定义bean的作用域策略 @Componet及其衍生注解使用 使用元注解和组合注解 总结 回顾 在前面的章节,我们介绍了@Comfiguration和@Bean结合AnnotationConfigApplicationContext零xml配置文件使用S

  • spring动态bean注册示例分享

    1.在一些特殊的场景中需要动态向spring注册bean2.spring版本2.5.6 复制代码 代码如下: public class ServiceServiceImpl implements ServiceService, ApplicationContextAware { @Override public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext)

  • springboot注册bean的三种方法

    spring在启动时会自己把bean(java组件)注册到ioc容器里,实现控制反转,在开发人员使用spring开发应用程序时,你是看不到new关键字的,所有对象都应该从容器里获得,它们的 生命周期 在放入容器时已经确定! 下面说一下三种注册bean的方法 @ComponentScan @Bean @Import @ComponentScan注册指定包里的bean Spring容器会扫描@ComponentScan配置的包路径,找到标记@Component注解的类加入到Spring容器. 我们经

  • Spring运行时动态注册bean的方法

    在spring运行时,动态的添加bean,dapeng框架在解析xml的字段时,使用到了动态注册,注册了一个实现了FactoryBean类! 定义一个没有被Spring管理的Controller public class UserController implements InitializingBean{ private UserService userService; public UserService getUserService() { return userService; } pu

  • Spring Bean生命周期之属性赋值阶段详解

    目录 前言 属性自动装配 属性赋值前回调 属性的真正赋值 总结 前言 上节在谈论Bean的实例化过程时,在说明实例化后阶段时只是粗略地看了一下populateBean,并未展开分析.本节接着populateBean开始分析对象赋值阶段的事情. populateBean其实主要做了以下几件事: Bean实例化后回调,来决定是否进行属性赋值 (上节分析过了) 对属性进行自动装配 InstantiationAwareBeanPostProcessor属性赋值前回调 属性的真正赋值 protected

  • Spring生命周期回调与容器扩展详解

    本篇主要总结下Spring容器在初始化实例前后,提供的一些回调方法和可扩展点.利用这些方法和扩展点,可以实现在Spring初始化实例前后做一些特殊逻辑处理. 下面主要介绍: 类级别的生命周期初始化回调方法init-method配置.InitializingBean接口和PostConstruct注解 容器级别的扩展BeanPostProcessor接口和BeanFactoryPostProcessor接口 1.类级别生命周期回调 1.1init-method 参照:Springbeanxsdin

  • Spring Bean生命周期之Bean的实例化详解

    目录 前言 实例化前阶段 实例化阶段 实例化后阶段 总结 前言 上一节说到了BeanDefinition的合并过程,这节该说Bean的实例化过程了.根据AbstractAutowireCapableBeanFactory#createBean源码逻辑 可将实例化过程分为实例化前阶段.实例化过程.实例化后阶段. 实例化前阶段 protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[]

  • Android Activity生命周期和堆栈管理的详解

    Activity的生命周期 Activity是Android中的四大组件之一,也是最基本,最重要的组件,是android系统提供一个可视化的,能与用户交换的组件. 系统提供的组件,不需要用户实例化,用户也不能实例化,是系统进行回调,例如web开发的servlet也是系统提供的,和android 的其他系统组件一样. 那么不需要我们实例化我们怎么用呢,这些组件都有相同的特点就是: 1.都需要在配置文件中注册 2.都需要自定义类去继承系统的Api 3.都有自己的生命周期 那么Activity的生命周

  • Asp.Net Core中服务的生命周期选项区别与用法详解

    前言 最近在做一个小的Demo中,在一个界面上两次调用视图组件,并且在视图组件中都调用了数据库查询,结果发现,一直报错,将两个视图组件的调用分离,单独进行,却又是正常的,寻找一番,发现是配置依赖注入服务时,对于服务的生命周期没有配置得当导致,特此做一次实验来认识三者之间(甚至是四者之间的用法及区别). 本文demo地址(具体见WebApi控制器中):https://gitee.com/530521314/koInstance.git (本地下载)  一.服务的生命周期 在Asp.Net Core

  • Vue生命周期和钩子函数的详解与经典面试题

    目录 1. vue生命周期 2.钩子函数 2.1 分为4大阶段8个方法: 2.2 初始化阶段 2.3 挂载阶段 2.4 更新阶段 2.5 销毁阶段 面试题: 总结 1. vue生命周期 一组件从 创建 到 销毁 的整个过程就是生命周期 Vue 实例从创建到销毁的过程,就是生命周期.也就是从开始创建.初始化数据.编译模板.挂载Dom→渲染.更新→渲染.卸载等一系列过程,我们称这是 Vue 的生命周期. 2.钩子函数 Vue 框架内置函数,随着组件的生命周期阶段,自动执行,特定的时间点,执行特定的操

  • Android  Activity生命周期和堆栈管理的详解

    Activity的生命周期 Activity是Android中的四大组件之一,也是最基本,最重要的组件,是android系统提供一个可视化的,能与用户交换的组件. 系统提供的组件,不需要用户实例化,用户也不能实例化,是系统进行回调,例如web开发的servlet也是系统提供的,和android 的其他系统组件一样. 那么不需要我们实例化我们怎么用呢,这些组件都有相同的特点就是: 1.都需要在配置文件中注册 2.都需要自定义类去继承系统的Api 3.都有自己的生命周期 那么Activity的生命周

  • Android开发中Activity的生命周期及加载模式详解

    本文给大家介绍Activity的生命周期,如果大家学习过iOS的小伙伴的话,Activity的生命周期和iOS中ViewController的生命周期非常类似.生命周期,并不难理解.一个人的生命周期莫过于生老病死,花儿的生命周期就是花开花谢了.在Android中Activity的生命周期莫过于Activity的创建到消亡的过程了.本篇博客就会介绍Activity生命周期中的不同阶段,通过实例的形式来窥探一下Activity的生命周期.搞明白Activity的生命周期是至关重要的,因为只有搞明白每

  • react新版本生命周期钩子函数及用法详解

    和旧的生命周期相比 准备废弃三个钩子,已经新增了两个钩子 React16 之后有三个生命周期被废弃(但并没有删除) componentWillMount( 组件将要挂载的钩子) componentWillReceiveProps(组件将要接收一个新的参数时的钩子) componentWillUpdate(组件将要更新的钩子) 新版本的生命周期新增的钩子 getDerivedStateFromProps 通过参数可以获取新的属性和状态 该函数是静态的 该函数的返回值会覆盖掉组件状态 getSnap

  • Vue生命周期介绍和钩子函数详解

    目录 Vue生命周期介绍和钩子函数 VUE生命周期钩子 Vue生命周期简介 create 和 mounted 相关 update 相关 destroy 相关 总结 Vue生命周期介绍和钩子函数 组件每个阶段它的内部构造是不一样的,所以一般特定的钩子做特定的事,比如Ajax获取数据就可以在mounted阶段.从Vue实例被创建开始到该实例最终被销毁的整个过程叫做VUE的生命周期,在这个生命周期内发生了下面的事情:从vue实例被创建开始,首先vue实例被创建,之后开始数据的初始化,编译模板,挂载do

随机推荐