如何向Spring IOC 容器 动态注册bean

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 如何向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);
    }
}

结果:image.png

我们就到这里了吗?我们观察一下上面将一个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-framework/docs/5.2.x/spring-framework-reference/core.html#beans-child-bean-definitions)大致看一下:

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进去,你就会发现有很多熟悉的面孔:

image.png

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

image.png

lazyInit是否是懒加载。

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

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

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

image.pngimage.png

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

image.png

Bean 加入IOC容器的几种方式

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

  • 启动时加入
  • @Component
  • @Service
  • @Controller
  • @Repository
  • @import
  • @Qualifier
  • @Resource
  • @Inject
  • 配置类: @Configuration+@Bean
  • 配置文件: xml
  • 注解形式
  • 运行时加入
    这三种最终都是通过BeanDefinitionRegistry来注入的,ImportBeanDefinitionRegistrar是一个接口,留给我们实现的方法如下:
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
  • ImportBeanDefinitionRegistrar
  • 手动构造BeanDefinition注入(我们上面就是自己手动构造BeanDefinition注入)
  • 借助BeanDefinitionRegistryPostProcessor注入

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

void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

总结一下

有种越学越不会的感觉。

相关文章
|
17天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
71 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
24天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
1月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
16天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
1月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
84 1
|
1月前
|
Java Spring
获取spring工厂中bean对象的两种方式
获取spring工厂中bean对象的两种方式
45 1
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
71 0
|
1月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
48 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
下一篇
无影云桌面