SpringBoot基礎篇Bean之動態註冊
Spring中的Bean除了前面提到的幾種JavaConfig或者 @Component
等註解標識之外,也是可以動態的向Spring容器註冊的,本篇博文將主要介紹
- 如何向Spring容器註冊Bean
- 如何引用主動註冊的Bean
- 註冊的Bean中,如果依賴其他的Bean,怎麼操作
I. 手動註冊Bean方式
1. 核心實現類
以前也寫過關於動態註冊Bean的博文,如 ofollow,noindex">180804-Spring之動態註冊bean
我們的實現方式和上面也沒什麼區別,依然是藉助 BeanDefinition
來建立Bean定義並註冊到BeanFactory中,具體實現的核心程式碼如下
public class ManualRegistBeanUtil { /** * 主動向Spring容器中註冊bean * * @param applicationContext Spring容器 * @param nameBeanName * @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,並不是塞入容器之中就完結了,塞進去之後,是為了後續的使用,自然而然的就會有下面幾種情形
a. 無其他Bean依賴
即不依賴其他的Bean, 單純的供其他地方使用,這種情況下,主要需要測試的就是別人可以通過什麼方式來使用它
@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也注入進去呢?
定義一個測試Bean
@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)
先預測一下結果:
@Autowired @Autowired originBean

執行結果如上圖,簡單來說,就是手動註冊的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. 原始碼
- 工程: spring-boot-demo
- model: 006-dynamicbean
1. 一灰灰Blog
- 一灰灰Blog個人部落格blog.hhui.top
- 一灰灰Blog-Spring專題部落格spring.hhui.top
一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 宣告
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激