向Spring IOC 容器动态注册bean实现方式


Posted in Java/Android onJuly 15, 2022

本文的大纲

向Spring IOC 容器动态注册bean实现方式

从一个需求谈起

这周遇到了这样一个需求,从第三方的数据库中获取值,只是一个简单的分页查询,处理这种问题,我一般都是在配置文件中配置数据库的地址等相关信息,然后在Spring Configuration 注册数据量连接池的bean,然后再将数据库连接池给JdbcTemplate, 但是这种的缺陷是,假设填错了数据库地址和密码,或者换了数据库的地址和密码,在配置文件里面重启之后,都需要重启应用。

我想能不能动态的向Spring IOC容器中注册和加载bean呢,项目在界面上填写数据库的地址、用户名、密码,存储之后,将JdbcTemplate和另一个数据库连接池加载到IOC容器中。答案是可以的,我经过一番搜索写出了如下代码:

@Component
public class BeanDynamicRegister {
    private final ConfigurableApplicationContext configurableApplicationContext;
    public BeanDynamicRegister(ConfigurableApplicationContext configurableApplicationContext) {
        this.configurableApplicationContext = configurableApplicationContext;
    }
    /**
     * 此方法提供出去,供其他bean动态的向IOC容器中注册bean。
     * 代表使用构造器给bean赋值
     *
     * @param beanName bean名
     * @param clazz    bean类
     * @param args     用于向bean的构造函数中添加值 如果loadType是set,则要求传递map.map的key为属性名,value为属性值
     * @param <T>      返回一个泛型
     * @param loadType
     * @return
     */
    public <T> T registerBeanByLoadType(String beanName, Class<T> clazz, LoadType loadType, Object... args) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        if (args.length > 0) {
            // 将参数加入到构造函数中
            switch (loadType) {
                case CONSTRUCTOR:
                    for (Object arg : args) {
                        beanDefinitionBuilder.addConstructorArgValue(arg);
                    }
                    break;
                case SETTER:
                    Map<String, Object> propertyMap = (Map<String, Object>) args[0];
                    for (Map.Entry<String, Object> stringObjectEntry : propertyMap.entrySet()) {
                        beanDefinitionBuilder.addPropertyValue(stringObjectEntry.getKey(), stringObjectEntry.getValue());
                    }
                    break;
                default:
                    break;
            }
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) configurableApplicationContext.getBeanFactory();
        beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
        return configurableApplicationContext.getBean(beanName, clazz);
    }
    public <T> T getBeanByName(String beanName,Class<T> requiredType){
        return  configurableApplicationContext.getBean(beanName,requiredType);
    }
    /**
     * 如果用户换了地址和密码,向IOC容器中移除bean。 重新注册
     *
     * @param beanName
     */
    public void removeBean(String beanName) {
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) configurableApplicationContext.getBeanFactory();
        beanDefinitionRegistry.removeBeanDefinition(beanName);
    }
}
@SpringBootTest
class SsmApplicationTests {
    @Autowired
    private LoadBeanService loadBeanService;
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private BeanDynamicRegister beanDynamicRegister;
    @Test
    public void test() {
        loadBeanService.loadDataSourceTest("root", "root");
        jdbcTemplate = beanDynamicRegister.getBeanByName("jdbcTemplateOne", NamedParameterJdbcTemplate.class);
        System.out.println("--------" + jdbcTemplate);
    }
}

结果: 

向Spring IOC 容器动态注册bean实现方式

我们就到这里了吗? 我们观察一下上面将一个bean加载到Spring IOC容器里经过了几步:

  • BeanDefineBuilder 构造BeanDefinition
  • 然后BeanDefinitionRegistry将其注册到IOC容器中。(这一步事实上只完成了注册,还未完成Bean的实例化,属性填充)

联系我们前面的文章《Spring Bean 的生命周期》,我们将Spring 的生命周期理解为“Spring 给我们提供的一些扩展接口,如果bean实现了这些这些接口,应用在启动的过程中会回调这些接口的方法。” , 这个理解并不完善,缺少了解析BeanDefinition这个阶段。

Spring Bean的生命周期再完善

BeanDefinition

那BeanDefinition是什么? BeanDefinition是一个接口,我们进Spring 官网(https://docs.spring.io/spring...)大致看一下:

A bean definition can contain a lot of configuration information, including constructor arguments, property values, and container-specific information, such as the initialization method, a static factory method name, and so on. A child bean definition inherits configuration data from a parent definition. The child definition can override some values or add others as needed. Using parent and child bean definitions can save a lot of typing. Effectively, this is a form of templating.

bean 的定义信息可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。子 bean 定义可以从父 bean 定义继承配置数据。子 bean 的定义信息可以覆盖某些值,或者可以根据需要添加其他值。使用父 bean 和子 bean 的定义可以节省很多输入(实际上,这是一种模板的设计形式)。

这段说的可能有点抽象, 你点BeanDefinition进去,你就会发现有很多熟悉的面孔:

向Spring IOC 容器动态注册bean实现方式

Bean的作用域: 单例,还是多例。

向Spring IOC 容器动态注册bean实现方式

lazyInit是否是懒加载。

这些都是描述Spring Bean的信息,我们可以类比到Java中的类,每个类都会有class属性,我们在配置类或者xml中的配置Bean的元信息,也被映射到这里。供IOC容器将Bean加入时使用。所以我们可以为对Spring Bean的生命周期的理解打一个补丁:

  • 从xml或配置类中解析BeanDefintion
  • BeanDefinition 注册,此时还未完成Bean的实例化。

我们可以打断点来验证一下:

向Spring IOC 容器动态注册bean实现方式

向Spring IOC 容器动态注册bean实现方式

  • Bean 实例化
  • Bean的属性赋值+依赖注入
  • Bean的初始化阶段的方法回调
  • Bean的销毁。

向Spring IOC 容器动态注册bean实现方式

Bean 加入IOC容器的几种方式

我们这里再来总结一下一个Bean注入Spring IOC容器的几种形式:

启动时加入

  • 配置类: @Configuration+@Bean
  • 配置文件: xml

注解形式

  • @Component
  • @Service
  • @Controller
  • @Repository
  • @import
  • @Qualifier
  • @Resource
  • @Inject

运行时加入

这三种最终都是通过BeanDefinitionRegistry来注入的,ImportBeanDefinitionRegistrar是一个接口,留给我们实现的方法如下:

default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
  • ImportBeanDefinitionRegistrar
  • 手动构造BeanDefinition注入(我们上面就是自己手动构造BeanDefinition注入)
  • 借助BeanDefinitionRegistryPostProcessor注入

​ BeanDefinitionRegistryPostProcessor也是一个接口,留给我们实现的方法如下:

void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

从spring容器中动态添加或移除bean

public class DemoUtil {
    @Autowired
    private ApplicationContext applicationContext;
//添加bean
    public void addBean(String beanName, Class<?> beanClass) {
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) {
            beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
        }
    }
//移除bean
    public void removeBean(String beanName) {
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
        beanDefinitionRegistry.getBeanDefinition(beanName);
        beanDefinitionRegistry.removeBeanDefinition(beanName);
    }
}

参考资料

以上就是向Spring IOC 容器动态注册bean实现方式的详细内容,更多关于Spring IOC 容器动态注册bean的资料请关注三水点靠木其它相关文章!


Tags in this post...

Java/Android 相关文章推荐
Java实现二维数组和稀疏数组之间的转换
Jun 27 Java/Android
Java图书管理系统,课程设计必用(源码+文档)
Jun 30 Java/Android
使用Spring处理x-www-form-urlencoded方式
Nov 02 Java/Android
maven依赖的version声明控制方式
Jan 18 Java/Android
Java 超详细讲解设计模式之中的抽象工厂模式
Mar 25 Java/Android
Java 数据结构七大排序使用分析
Apr 02 Java/Android
Java 深入探究讲解简单工厂模式
Apr 07 Java/Android
Java详细解析==和equals的区别
Apr 07 Java/Android
Java 通过手写分布式雪花SnowFlake生成ID方法详解
Apr 07 Java/Android
Java 写一个简单的图书管理系统
Apr 26 Java/Android
SpringBoot使用ip2region获取地理位置信息的方法
Jun 21 Java/Android
Java代码规范与质量检测插件SonarLint的使用
Aug 05 Java/Android
SpringBoot详解执行过程
Jul 15 #Java/Android
spring 项目实现限流方法示例
SpringBoot详解整合Redis缓存方法
Jul 15 #Java/Android
maven 解包依赖项中的文件的解决方法
Jul 15 #Java/Android
SpringBoot详解自定义Stater的应用
Jul 15 #Java/Android
MyBatis XPathParser解析器使用范例详解
Jul 15 #Java/Android
SpringBoot接入钉钉自定义机器人预警通知
Jul 15 #Java/Android
You might like
php中利用post传递字符串重定向的实现代码
2011/04/21 PHP
phpword插件导出word文件时中文乱码问题处理方案
2014/08/19 PHP
php mysql like 实现多关键词搜索的方法
2016/10/29 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
2018/02/06 PHP
jscript之Read an Excel Spreadsheet
2007/06/13 Javascript
二行代码解决全部网页木马
2008/03/28 Javascript
javascript 无提示关闭窗口脚本
2009/08/17 Javascript
jquery实现的导航固定效果
2014/04/28 Javascript
提高jQuery性能优化的技巧
2015/08/03 Javascript
js简单时间比较的方法
2016/08/02 Javascript
jQuery鼠标事件总结
2016/10/13 Javascript
jQuery select自动选中功能实现方法分析
2016/11/28 Javascript
微信小程序实现导航栏选项卡效果
2020/06/19 Javascript
微信小程序实现禁止分享代码实例
2019/10/19 Javascript
[02:24]DOTA2痛苦女王 英雄基础教程
2013/11/26 DOTA
[01:34]传奇从这开始 2016国际邀请赛中国区预选赛震撼开启
2016/06/26 DOTA
[03:57]2016完美“圣”典风云人物:rOtk专访
2016/12/09 DOTA
python实现简单的TCP代理服务器
2014/10/08 Python
python3如何将docx转换成pdf文件
2018/03/23 Python
python实现自动发送报警监控邮件
2018/06/21 Python
Python 中包/模块的 `import` 操作代码
2019/04/22 Python
Python配置虚拟环境图文步骤
2019/05/20 Python
简单了解python单例模式的几种写法
2019/07/01 Python
python实现批量nii文件转换为png图像
2019/07/18 Python
TensorFlow 输出checkpoint 中的变量名与变量值方式
2020/02/11 Python
4行Python代码生成图像验证码(2种)
2020/04/07 Python
New Balance英国官方网站:始于1906年,百年慢跑品牌
2016/12/07 全球购物
美国睫毛、眉毛精华液领导品牌:RevitaLash Cosmetics
2018/03/26 全球购物
菲律宾购物网站:Lazada菲律宾
2018/04/05 全球购物
行政管理人员精品工作推荐信
2013/11/04 职场文书
人资专员岗位职责
2014/04/04 职场文书
求职信名称怎么写
2014/05/26 职场文书
经济类毕业生求职信
2014/06/26 职场文书
质检员岗位职责
2015/02/03 职场文书
写给孩子的新学期寄语
2015/02/27 职场文书
一年级语文教学随笔
2015/08/14 职场文书