关于spring:如何向Spring-IOC-容器-动态注册bean

38次阅读

共计 4593 个字符,预计需要花费 12 分钟才能阅读完成。

本文的纲要如下

从一个需要谈起

这周遇到了这样一个需要,从第三方的数据库中获取值,只是一个简略的分页查问,解决这种问题,我个别都是在配置文件中配置数据库的地址等相干信息,而后在 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);
    }
}

后果:

咱们就到这里了吗?咱们察看一下下面将一个 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 进去,你就会发现有很多相熟的脸孔:

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

lazyInit 是否是懒加载。

这些都是形容 Spring Bean 的信息,咱们能够类比到 Java 中的类,每个类都会有 class 属性,咱们在配置类或者 xml 中的配置 Bean 的元信息,也被映射到这里。供 IOC 容器将 Bean 退出时应用。所以咱们能够为对 Spring Bean 的生命周期的了解打一个补丁:

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

咱们能够打断点来验证一下:

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

Bean 退出 IOC 容器的几种形式

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

  • 启动时退出

    • 配置类: @Configuration+@Bean
    • 配置文件: xml
    • 注解模式

      • @Component
      • @Service
      • @Controller
      • @Repository
      • @import
      • @Qualifier
      • @Resource
      • @Inject
  • 运行时退出

    • ImportBeanDefinitionRegistrar
    • 手动结构 BeanDefinition 注入(咱们下面就是本人手动结构 BeanDefinition 注入)
    • 借助 BeanDefinitionRegistryPostProcessor 注入

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

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}

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

void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

总结一下

有种越学越不会的感觉。

参考资料

  • 180804-Spring 之动静注册 bean https://blog.hhui.top/hexblog…
  • 从 spring 容器中动静增加或移除 bean https://blog.csdn.net/qq_2016…
  • 《从 0 开始深刻学习 Spring》https://juejin.cn/book/685791…

正文完
 0