一般情况下,我们Spring应用中的bean都是通过注解或者xml注入到容器中的,有些情况下我们可能想手动往容器中注入bean,即编程方式注入bean。

本文所使用源码包版本:spring-beans-5.0.5.RELEASE.

如何注册?

Spring 中用BeanDefinition接口描述一个bean,Spring容器中用Map<String, BeanDefinition> beanDefinitionMap

存储beanName和BeanDefinition对象的映射关系【beanDefinitionMap 可参考DefaultListableBeanFactory】。Spring在实例化一个bean,都是先从 beanDefinitionMap 中获取beanDefinition对象,进而构造出对应的bean。因此,我们手动注册bean的问题,就演化为如何往这个 beanDefinitionMap 放入我们要注册bean对应的 BeanDefinition 对象。

Spring 提供了 BeanDefinitionRegistry 接口来操作底层beanFactory实现的beanDefinitionMap。

【2020-08-17】备注:推荐通过实现 BeanDefinitionRegistryPostProcessor 接口操作 BeanDefinitionRegistry:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {/*** Modify the application context's internal bean definition registry after its* standard initialization. All regular bean definitions will have been loaded,* but no beans will have been instantiated yet. This allows for adding further* bean definitions before the next post-processing phase kicks in.* @param registry the bean definition registry used by the application context* @throws org.springframework.beans.BeansException in case of errors*/void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;}

测试示例:

我们有个类 H,把它手动添加到Spring 容器中成为一个bean:

public class H {private String s ;public void test(){System.out.println("hello world!");}public void setS(String s) {this.s = s;}
}

主程序:

@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(BootStrap.class);DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(H.class);beanFactory.registerBeanDefinition("h", beanDefinition);H h = (H) applicationContext.getBean("h");h.test();}
}

程序运行结果:

hello world!

从容器中获取到了名称为“h”的bean,并且正确打印了方法调用结果。另外,在类H中,可以使用@Autowired注入其他已有的bean的,bean的依赖关系也是支持的。如下:

public class H {private String s ;@AutowiredY y;public void test(){y.test();System.out.println("hello world!");}public void setS(String s) {this.s = s;}
}

H类依赖一个已有的bean Y:

@Component
public class Y {public Y() {System.out.println("Construct Y");}public void test(){System.out.println("y.test()...");}
}

上述测试代码输出结果:

Construct Y
y.test()...
hello world!

涉及源码:

BeanDefinition 接口:

BeanDefinition描述了一个bean实例,该实例具有属性值,构造函数参数值以及具体实现所提供的更多信息。类图如下,主要三个实现类:RootBeanDefinition、ChildBeanDefinition 和 GenericBeanDefinition。Bean的属性,比如对应Java类的Class、作用域scope、是否延迟加载lazyInit等,都可以通过BeanDefinition控制。

RootBeanDefinition 可以在配置阶段用于注册各个Bean定义,上面的测试用例 BeanDefinition 也可以使用 RootBeanDefinition 实现。 但是官方推荐,从Spring 2.5开始,以编程方式注册Bean定义的首选方法是{@link GenericBeanDefinition}类。

BeanDefinitionRegistry 接口:

包含BeanDefinition的注册表的接口,例如 RootBeanDefinition 和 ChildBeanDefinition 实例。 通常由内部使用AbstractBeanDefinition层次结构的 BeanFactory 实现。

 /*** Register a new bean definition with this registry.* Must support RootBeanDefinition and ChildBeanDefinition.* @param beanName the name of the bean instance to register* @param beanDefinition definition of the bean instance to register* @throws BeanDefinitionStoreException if the BeanDefinition is invalid* or if there is already a BeanDefinition for the specified bean name* (and we are not allowed to override it)* @see RootBeanDefinition* @see ChildBeanDefinition*/void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException;

注意用到的 registerBeanDefinition 方法,如果构造的 BeanDefinition 无效,或者指定的bean名称已经存在BeanDefinition(如果不允许覆盖它)会抛出 BeanDefinitionStoreException 异常。BeanDefinitionRegistry 还有其他的方法,比如根据bean名称移除beanDefinition的方法 removeDefinition(String beanName)等。

DefaultListableBeanFactory 类:

DefaultListableBeanFactory中用Map<String,BeanDefinition> beanDefinitionMap 保存beanName和BeanDefinition对象的映射关系,同时实现了BeanDefinitionRegistry接口,用来操作beanDefinitionMap,其中 registerBeanDefinition 方法:

@Overridepublic 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 {//校验传入beanDefinition的正确性((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}BeanDefinition oldBeanDefinition;oldBeanDefinition = this.beanDefinitionMap.get(beanName);if (oldBeanDefinition != null) {//如果已存在该bean名称的beanDefinition,并且不允许覆盖,则抛异常if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +"': There is already [" + oldBeanDefinition + "] bound.");}else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (this.logger.isWarnEnabled()) {this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +oldBeanDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(oldBeanDefinition)) {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + oldBeanDefinition +"] with [" + beanDefinition + "]");}}else {if (this.logger.isDebugEnabled()) {this.logger.debug("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + oldBeanDefinition +"] with [" + beanDefinition + "]");}}this.beanDefinitionMap.put(beanName, beanDefinition);}else {//检查beanFactory的bean创建阶段是否已开始,可以理解为容器是否已经初始化过了if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)synchronized (this.beanDefinitionMap) {//把新加入的beanName-beanDefinition映射加入beanDefinitionMap//把beanName加入的维护的beanDefinitionName列表(ArrayList)中this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;//如果手动注册的单例bean名称包含了beanName,需要移除//这个地方默认构造的beanDefinition对象作用域也是“”,等价于单例,但是却没有把 beanName加入到manualSingletonNames集合(LinkedHashSet)中//最终beanName对应的bean实例化后,manualSingletonNames还是没有这个beanName,略奇怪。。。if (this.manualSingletonNames.contains(beanName)) {Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);updatedSingletons.remove(beanName);this.manualSingletonNames = updatedSingletons;}}}else {// Still in startup registration phasethis.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.manualSingletonNames.remove(beanName);}this.frozenBeanDefinitionNames = null;}//如果oldBeanDefinition不为null,且单例池缓存中已经有beanName对应的bean,则重置给定bean的所有bean定义缓存,包括从其派生的bean的缓存。if (oldBeanDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}}

SingletonBeanRegistry 接口:

SingletonBeanRegister接口,是单例bean的管理接口,也可以用来注册单例bean,方法:

void registerSingleton(String beanName, Object singletonObject);

DefaultListableBeanFactory类也实现了该接口,我们可以将上面的测试代码修改下,也可以达到注册一个单例bean的目的:

@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(BootStrap.class);DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();beanFactory.registerSingleton("h", new H());H h = (H) applicationContext.getBean("h");h.test();}
}

实现起来简单,但是有限制:一是bean依赖项不好处理;二是待注入的实例应该被完全初始化,注册表不会执行任何初始化回调方法,比如InitializingBean的{@code afterPropertiesSet}方法等;。

Spring 手动注册bean相关推荐

  1. spring注册bean

    手动注册bean的两种方式: 实现ImportBeanDefinitionRegistrar 实现BeanDefinitionRegistryPostProcessor 1.实现ImportBeanD ...

  2. 【Spring杂烩】探讨Spring向容器注册Bean的三种方式

    探讨Spring向容器注册Bean的三种方式 重点了解@Import实现的三种子方式 前提概要 Spring向容器注册Bean的三种方式 通过@ComponentScan.@Componet 通过@B ...

  3. Spring之动态注册bean

    Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运 ...

  4. Spring注解驱动开发第7讲——如何按照条件向Spring容器中注册bean?这次我懂了!!

    写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,而 ...

  5. 【小家Spring】Spring注解驱动开发---向Spring Ioc容器中注册Bean的7种方式

    每篇一句 比你有钱的人一定会比你努力,而比你努力的人终有一天会比你有钱 前言 Spring是一个非常强大的反转控制(IOC)框架,以帮助分离项目组件之间的依赖关系.因此可以说Spring容器对Bean ...

  6. Spring注解驱动开发第10讲——在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

    写在前面 在前面的文章中,我们学习了如何使用@Import注解向Spring容器中导入bean,不仅可以使用@Import注解快速向容器中导入bean,也可以在@Import注解中使用ImportSe ...

  7. 手写简版spring --2--实现Bean的定义、注册、获取

    一.目标 在上一章节我们初步依照 Spring Bean 容器的概念,实现了一个粗糙版本的代码实现.那么本章节我们需要结合已实现的 Spring Bean 容器进行功能完善,实现 Bean 容器关于 ...

  8. 【工具类】手动获取被spring管理的bean对象

    package com.zxl;import org.springframework.beans.BeansException; import org.springframework.context. ...

  9. 四、Spring中使用@Conditional按照条件注册Bean

    以前其实是写过@Condtional注解的笔记的,这里附上链接: Spring中的@conditional注解 但已经忘记的差不多了,所以今天再重新学习下,可以互补着学习 @Contional:按照一 ...

最新文章

  1. MySQL外键设置中的的 Cascade、Restrict、SET NULL 、NO ACTION
  2. MobileViT 网络测试
  3. 【Android 组件化】路由组件 ( 注解处理器调试 )
  4. Android 从清单配置文件元数据中获取值
  5. php自动计数,PHP 实现精确统计在线人数功能
  6. java tostring的用处_JAVA的tostring()方法的作用是什么呢?
  7. 详解Windows 搭建MRTG流量检控服务器
  8. 基础编程题目集 6-7 统计某类完全平方数 (20 分)
  9. Java常用的排序查找算法
  10. 机器学习 | 算法笔记- 决策树(Decision Tree)
  11. win10系统的安装
  12. mysql报错:1194-table “xxx“ is marked as crashed and should be repaired
  13. QCC304x系列开发教程(实战篇) 之7.3 QCC3040之swift pair
  14. 为程序员提供一杯免费咖啡
  15. Windows文件名太长无法删除
  16. Aquariusの瓶子的眼泪
  17. 【LSTM+embeddingbag】进行文本分类完整代码~
  18. javascript版谷歌身份验证器google authenticator
  19. 《SEO实战密码》——SEO网站结构优化
  20. Wireshark—高级特性命令行模式

热门文章

  1. 音乐门面怎样操作IT程序员工个人和企业价值双赢
  2. 【H5U PLC应用】
  3. JAVA学习笔记- - - day 1
  4. Neutron-常用配置-学习笔记
  5. 思科华为网络工程师必修-什么是trunk?带你快速了解trunk
  6. 基于Autoware制作高精地图(三)
  7. 60分钟短线波段战术
  8. 对于2020五岳中旬的研究生复试准备
  9. 使用Codejock的换肤界面
  10. 函数的prototype属性(原型对象)