首頁 > 軟體

向Spring IOC 容器動態註冊bean實現方式

2022-07-15 14:03:23

本文的大綱

從一個需求談起

這周遇到了這樣一個需求,從第三方的資料庫中獲取值,只是一個簡單的分頁查詢,處理這種問題,我一般都是在組態檔中設定資料庫的地址等相關資訊,然後在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);
    }
}

參考資料

以上就是向Spring IOC 容器動態註冊bean實現方式的詳細內容,更多關於Spring IOC 容器動態註冊bean的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com