SpringBoot基礎篇Bean之動態註冊

更多Spring文章,歡迎點擊 一灰灰Blog-Spring專題java

Spring中的Bean除了前面提到的幾種JavaConfig或者@Component等註解標識以外,也是能夠動態的向Spring容器註冊的,本篇博文將主要介紹git

  • 如何向Spring容器註冊Bean
  • 如何引用主動註冊的Bean
  • 註冊的Bean中,若是依賴其餘的Bean,怎麼操做

<!-- more -->github

I. 手動註冊Bean方式

1. 核心實現類

之前也寫過關於動態註冊Bean的博文,如 180804-Spring之動態註冊beanspring

咱們的實現方式和上面也沒什麼區別,依然是藉助BeanDefinition來建立Bean定義並註冊到BeanFactory中,具體實現的核心代碼以下app

public class ManualRegistBeanUtil {

     /**
     * 主動向Spring容器中註冊bean
     *
     * @param applicationContext Spring容器
     * @param name               BeanName
     * @param clazz              註冊的bean的類性
     * @param args               構造方法的必要參數,順序和類型要求和clazz中定義的一致
     * @param <T>
     * @return 返回註冊到容器中的bean對象
     */
    public static <T> T registerBean(ConfigurableApplicationContext applicationContext, String name, Class<T> clazz,
            Object... args) {
        if(applicationContext.containsBean(name)) {
            Object bean = applicationContext.getBean(name);
            if (bean.getClass().isAssignableFrom(clazz)) {
                return (T) bean;
            } else {
                throw new RuntimeException("BeanName 重複 " + name);
            }
        }


        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        for (Object arg : args) {
            beanDefinitionBuilder.addConstructorArgValue(arg);
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();

        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
        beanFactory.registerBeanDefinition(name, beanDefinition);
        return applicationContext.getBean(name, clazz);
    }
}

上面惟一的方法中,接收四個參數,源碼中也有說明,稍微須要注意下的是Spring容器中不容許出現同名的Bean框架

2. 測試用例

動態建立Bean,並非塞入容器之中就完結了,塞進去以後,是爲了後續的使用,天然而然的就會有下面幾種情形dom

a. 無其餘Bean依賴

即不依賴其餘的Bean, 單純的供其餘地方使用,這種狀況下,主要須要測試的就是別人能夠經過什麼方式來使用它ide

@Slf4j
public class ManualBean {

    private int id;

    public ManualBean() {
        Random random = new Random();
        id = random.nextInt(100);
    }

    public String print(String msg) {
        return "[ManualBean] print : " + msg + " id: " + id;
    }
}

b. 依賴其餘Bean

和前面一個不一樣,這個Bean內部須要注入其餘的Bean,所以咱們主動註冊Bean時,可否將依賴的Bean也注入進去呢?spring-boot

定義一個測試Beanpost

@Slf4j
public class ManualDIBean {

    private int id;

    @Autowired
    private OriginBean originBean;

    private String name;

    public ManualDIBean(String name) {
        Random random = new Random();
        this.id = random.nextInt(100);
        this.name = name;
    }

    public String print(String msg) {
        String o = originBean.print(" call by ManualDIBean! ");
        return "[ManualDIBean] print: " + msg + " id: " + id + " name: " + name + " originBean print:" + o;
    }
}

其依賴的普通Bean定義以下

@Slf4j
@Component
public class OriginBean {

    private LocalDateTime time;

    public OriginBean() {
        time = LocalDateTime.now();
    }

    public String print(String msg) {
        return "[OriginBean] print msg: " + msg + ", time: " + time;
    }
}

c. 普通Bean依賴主動註冊的Bean

這個其實就是使用case了,主動註冊的Bean也是被人使用的,那能夠怎麼使用呢?傳統的Autowired能否?

@Slf4j
@Component
public class AnoOriginBean {
    // 但願能夠注入 主動註冊的Bean
    @Autowired
    private ManualBean manualBean;

    public AnoOriginBean() {
        System.out.println("AnoOriginBean init: " + System.currentTimeMillis());
    }

    public String print() {
        return "[AnoOriginBean] print!!! manualBean == null ? " + (manualBean == null);
    }
}

d. Bean註冊實現

前面定義了兩個須要手動註冊的bean,因此就須要選擇一個合適的地方來處理主動註冊的邏輯,咱們把這段邏輯放在AutoConfig中,用於測試演示

@Configuration
public class BeanRegisterAutoConf {

    public BeanRegisterAutoConf(ApplicationContext applicationContext) {
        System.out.println("BeanRegisterAutoConf init: " + System.currentTimeMillis());
        registerManualBean((ConfigurableApplicationContext) applicationContext);
    }

    /**
     * 手動註冊自定義地bean
     * @param applicationContext
     */
    private void registerManualBean(ConfigurableApplicationContext applicationContext) {
        // 主動註冊一個沒什麼依賴的Bean
        ManualBean manualBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualBean", ManualBean.class);
        manualBean.print("test print manualBean");

        // manualDIBean 內部,依賴由Spring容器建立的OriginBean
        ManualDIBean manualDIBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualDIBean",
                ManualDIBean.class, "依賴OriginBean的自定義Bean");
        manualDIBean.print("test print manualDIBean");
    }
}

3. 實測演示

前面的測試case都準備好了,接着就須要實際的跑一下看看效果了,選擇Rest服務來演示,建立一個簡單的Controller

@RestController
public class ShowController {

    @Autowired
    private ManualBean manualBean;
    @Autowired
    private ManualDIBean manualDIBean;
    @Autowired
    private AnoOriginBean anoOriginBean;

    public ShowController() {
        System.out.println("ShowController init: " + System.currentTimeMillis());
    }

    @GetMapping(path = "show")
    public String show(String msg) {
        Map<String, String> result = new HashMap<>(8);
        result.put("manualBean", manualBean == null ? "null" : manualBean.print(msg));
        result.put("manualDIBean", manualDIBean == null ? "null" : manualDIBean.print(msg));
        result.put("anoOriginBean",anoOriginBean.print());
        return JSONObject.toJSONString(result);
    }
}

上面就使用了三個Bean,兩個主動註冊的外加一個依賴了主動註冊Bean的anoOriginBean (其實Controller自己也是一個使用主動註冊Bean的Bean)

先預測一下結果:

  • 若是 manualBean, manualDIBean 爲空,表示不能直接經過 @Autowired 註解的方式引入手動註冊的Bean;此時會拋npe
  • 若是沒有npe,且 AnoOriginBean內部依賴的manualBean也不是null,則表示直接用@Autowired來注入沒啥毛病(是否絕對呢?)
  • manualDIBean 內部依賴了originBean,也是經過註解方式注入,若是正常返回,表示手動註冊的也能夠這麼引用其餘的Bean;不然不行

手動註冊演示

執行結果如上圖,簡單來講,就是手動註冊的Bean,和咱們通常使用的Bean也沒什麼兩樣,原來能夠怎麼用,如今依然能夠這麼用

II. BeanDefinitionRegistryPostProcessor擴展方式

前面這種手動注入的方式有個很差的地方就是主動註冊的這個邏輯,感受寫在什麼地方都不太優雅,在Spring項目的源碼中經過實現BeanDefinitionRegistryPostProcessor擴展方式接口的方式比較多,好比org.springframework.cloud.autoconfigure.RefreshAutoConfiguration

依葫蘆畫瓢實現一個

1. 實現類

@Slf4j
@Configuration
public class AutoBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 註冊Bean定義,容器根據定義返回bean

        //構造bean定義
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(AutoBean.class);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        //註冊bean定義
        registry.registerBeanDefinition("autoBean", beanDefinition);


        // AutoDIBean 的注入方式
        beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(AutoDIBean.class);
        beanDefinitionBuilder.addConstructorArgValue("自動注入依賴Bean");
        beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        registry.registerBeanDefinition("autoDiBean", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        // 註冊Bean實例,使用supply接口, 能夠建立一個實例,並主動注入一些依賴的Bean;當這個實例對象是經過動態代理這種框架生成時,就比較有用了

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AutoFacDIBean.class, () -> {
            AutoFacDIBean autoFacDIBean = new AutoFacDIBean("autoFac");
            autoFacDIBean.setAutoBean(factory.getBean("autoBean", AutoBean.class));
            autoFacDIBean.setOriginBean(factory.getBean("originBean", OriginBean.class));
            return autoFacDIBean;
        });
        BeanDefinition beanDefinition = builder.getRawBeanDefinition();
        ((DefaultListableBeanFactory) factory).registerBeanDefinition("autoFacDIBean", beanDefinition);
    }
}

接口的實現中,Bean的註冊方式和前面的實際上是同樣的,這個接口提供了兩個方法,一般實現第一個方法來作Bean的註冊;二者從根本上也沒太大的區別,上面只是給出了一種使用演示

2. 測試用例

測試的思路基本上和前面同樣,定義了三個須要咱們註冊的Bean,一個沒有外部依賴的AutoBean

public class AutoBean {

    public String print() {
        return "[AutoBean] " + System.currentTimeMillis();
    }
}

一個依賴外部Bean的AutoDIBean

public class AutoDIBean {

    private String name;

    @Autowired
    private OriginBean originBean;

    public AutoDIBean(String name) {
        this.name = name;
    }

    public String print() {
        return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null);
    }

}

一個用於主動建立和設置依賴的AutoFacDIBean (用於前面的實現類中的第二個方法的註冊方式)

public class AutoFacDIBean {
    private String name;

    @Setter
    private OriginBean originBean;

    @Setter
    private AutoBean autoBean;

    public AutoFacDIBean(String name) {
        this.name = name;
    }

    public String print() {
        return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null) + " | autoBean==null ? " +
                (autoBean == null);
    }

}

一個依賴了主動註冊AutoBean的 AnoAutoOriginBean

@Component
public class AnoAutoOriginBean {
    @Autowired
    private AutoBean autoBean;

    public AnoAutoOriginBean() {
        System.out.println("AnoAutoOriginBean init: " + System.currentTimeMillis());
    }

    public String print() {
        return "[AnoAutoOriginBean] print!!! autoBean == null ? " + (autoBean == null);
    }
}

3. 實測演示

一樣寫一個RestApi進行演示,經過實際的演示結果發現和前面沒什麼太大的區別

@Autowired
private AutoBean autoBean;
@Autowired
private AutoDIBean autoDIBean;
@Autowired
private AutoFacDIBean autoFacDIBean;
@Autowired
private AnoAutoOriginBean anoAutoOriginBean;
@GetMapping(path = "auto")
public String autoShow() {
    Map<String, String> result = new HashMap<>(8);
    result.put("autoBean", autoBean == null ? "null" : autoBean.print());
    result.put("manualDIBean", autoDIBean == null ? "null" : autoDIBean.print());
    result.put("autoFacDIBean",autoFacDIBean == null ? "null" : autoFacDIBean.print());
    result.put("anoAutoOriginBean",anoAutoOriginBean.print());
    return JSONObject.toJSONString(result);
}

接口方式註冊演示

III. 其餘

0. 相關

a. 文檔

b. 源碼


1. 一灰灰Blog

一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

2. 聲明

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

知識星球

goals

相關文章
相關標籤/搜索