本文的纲要如下
从一个需要谈起
这周遇到了这样一个需要,从第三方的数据库中获取值,只是一个简略的分页查问,解决这种问题,我个别都是在配置文件中配置数据库的地址等相干信息,而后在Spring Configuration 注册数据量连接池的bean,而后再将数据库连接池给JdbcTemplate, 然而这种的缺点是,假如填错了数据库地址和明码,或者换了数据库的地址和明码,在配置文件外面重启之后,都须要重启利用。
我想能不能动静的向Spring IOC容器中注册和加载bean呢,我的项目在界面上填写数据库的地址、用户名、明码,存储之后,将JdbcTemplate和另一个数据库连接池加载到IOC容器中。答案是能够的,我通过一番搜寻写出了如下代码:
@Componentpublic 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); }}@SpringBootTestclass 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...