使用Spring容器動態注冊和獲取Bean


有時候需要在運行時動態注冊Bean到Spring容器,並根據名稱獲取注冊的Bean。比如我們自己的SAAS架構的系統需要調用ThingsBoard API和Thingsboard交互,就可以通過ThingsBoard提供的RestClient工具類。但這要求每個租戶使用自己唯一的RestClient,為了達到此目的,系統啟動時需要將每個租戶的RestClient加載到Spring容器中以供租戶隨時使用,另外系統管理員可以在系統中隨時創建新的租戶,因此就需要在系統啟動后運行過程隨時可以注冊新的RestClient到Spring容器中。

下面從運行時手動注冊Bean到Spring容器以及從Spring容器中獲取容器管理的Bean入手進行介紹。

  1. 運行時注冊Bean到Spring容器

訪問接口

/**
     * 注冊bean到Spring容器。使用構造函數參數初始化bean。
     * 備注:需要有默認構造器,即需要有無參構造器。
     * @param beanName
     * @param clazz
     * @param constructorArgs
     */
    public static void registerBean(String beanName, Class<?> clazz, Object... constructorArgs) {
        registerBean(beanName, clazz, new InitBean() {
            @Override
            public void init(BeanDefinitionBuilder beanDefinitionBuilder) {
                log.info("使用構造函數參數初始化class[{}]",clazz);
                if(constructorArgs!=null&&constructorArgs.length>0){
                    for (Object constructorArg : constructorArgs) {
                        beanDefinitionBuilder.addConstructorArgValue(constructorArg);
                    }
                }
            }
        });
    }


    /**
     * 注冊bean到spring容器中。使用屬性參數初始化bean。
     * @param beanName 名稱
     * @param clazz    class
     */
    public static void registerBean(String beanName, Class<?> clazz, Map<String, Object> propertyValueMap) {
       registerBean(beanName, clazz, new InitBean() {
           @Override
           public void init(BeanDefinitionBuilder beanDefinitionBuilder) {
               log.info("使用屬性參數初始化class[{}]",clazz);
               if(propertyValueMap!=null){
                   propertyValueMap.forEach((k,v)->{
                       beanDefinitionBuilder.addPropertyValue(k, v);
                   });
               }
           }
       });
    }

核心代碼:

    private static void registerBean(String beanName, Class<?> clazz, InitBean initBean) {
        // 1. 檢查是否存在重名的bean,如果存在打印警告日志,並且返回,
        if (defaultListableBeanFactory.containsBean(beanName)) {
            log.warn("The Bean  [{}] for  type [{}] is already exists. Please check.", beanName, clazz.getName());
            return;
        }
        // 2. 通過BeanDefinitionBuilder創建bean定義
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);

        //3. 初始化Bean
        if (initBean != null) {
            initBean.init(beanDefinitionBuilder);
        }

        // 4. 注冊bean
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
        log.info("register bean [{}],Class [{}] success.", beanName, clazz);
    }

由於初始化Bean有2重方式,一種是設置Property的方式(必須有默認的構造函數),一種是構造函數的方式,為了避免重復的代碼特寫了回調類InitBean

   public interface InitBean{
        void init( BeanDefinitionBuilder beanDefinitionBuilder);
    }

ApplicationContext和DefaultListableBeanFactory的獲取

@Slf4j
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;
    private static DefaultListableBeanFactory defaultListableBeanFactory;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtil.applicationContext == null) {
            SpringContextUtil.applicationContext = applicationContext;
        }
        //將applicationContext轉換為ConfigurableApplicationContext
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        // 獲取bean工廠並轉換為DefaultListableBeanFactory
        this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        log.info("init ApplicationContext and  BeanFactory Success.");
    }
    ....
  1. Bean從Spring容器中的動態獲取

提供三種方式從Spring 容器中獲取bean,分別是根據bean的名稱,bean的class類型(bean是gingleton的)根據bean的名稱以及class類型。

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

如上,使用如上介紹的注冊和獲取Bean的方式就可以輕松獲得,運行時動態注冊和獲取Bean的能力。

備注:在SpringBoot微服務啟動時手動完成Bean的注冊可以利用SpringBoot的提供的org.springframework.CommandLineRunner或者org.springframework.bootApplicationRunner`

參考示例:

@Component
@Slf4j
public class TenantRestClientInit implements CommandLineRunner {
 

    @Override
    public void run(String... args) throws Exception {
        initSomeThings();
    }

    private void initSomeThings(){
       		....
        }

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM