向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); } }
結果:
我們就到這裡瞭嗎? 我們觀察一下上面將一個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
運行時加入
這三種最終都是通過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); } }
參考資料
- 180804-Spring之動態註冊bean https://www.jb51.net/article/145136.htm
- 從spring容器中動態添加或移除bean
- 《從 0 開始深入學習 Spring》https://www.jb51.net/books/478522.html
以上就是向Spring IOC 容器動態註冊bean實現方式的詳細內容,更多關於Spring IOC 容器動態註冊bean的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 手把手帶你實現一個萌芽版的Spring容器
- SpringBoot項目如何將Bean註入到普通類中
- Spring容器中添加bean的5種方式
- 淺析對Spring aware接口理解
- Spring擴展接口知識總結