本文的大纲如下
文章图片
从一个需求谈起 【如何向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 clazzbean类
* @param args用于向bean的构造函数中添加值 如果loadType是set,则要求传递map.map的key为属性名,value为属性值
* @param 返回一个泛型
* @param loadType
* @return
*/
public T registerBeanByLoadType(String beanName, Class 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 propertyMap = (Map) args[0];
for (Map.Entry 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 getBeanByName(String beanName,Class requiredType){
returnconfigurableApplicationContext.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的生命周期再完善 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.这段说的可能有点抽象, 你点BeanDefinition进去,你就会发现有很多熟悉的面孔:
bean 的定义信息可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。子 bean 定义可以从父 bean 定义继承配置数据。子 bean 的定义信息可以覆盖某些值,或者可以根据需要添加其他值。使用父 bean 和子 bean 的定义可以节省很多输入(实际上,这是一种模板的设计形式)。
文章图片
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注入
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { }
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...
推荐阅读
- spring|Spring Cloud构建分布式微服务架构 - 企业分布式微服务云架构构建
- java|SpringCloud 分布式微服务架构
- spring|Spring Cloud分布式微服务云架构服务组件
- 记一个 Base64 有关的 Bug
- spring|Alibaba微服务分布式事务组件—Seata详解,应该没有比这还详细的了吧
- Spring|Spring Boot——整合 Thymeleaf模板引擎
- springboot系列|SpringBoot 整合 Thymeleaf & 如何使用后台模板快速搭建项目
- html|SpringBoot学习(五)——————Thymeleaf +bootstrap 分页
- spring|初学者快速入门SpringBoot,并学会创建工程