更多Spring文章,歡迎點擊 一灰灰Blog-Spring專題java
Spring中的Bean除了前面提到的幾種JavaConfig或者@Component
等註解標識以外,也是能夠動態的向Spring容器註冊的,本篇博文將主要介紹git
<!-- more -->github
之前也寫過關於動態註冊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框架
動態建立Bean,並非塞入容器之中就完結了,塞進去以後,是爲了後續的使用,天然而然的就會有下面幾種情形dom
即不依賴其餘的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; } }
和前面一個不一樣,這個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; } }
這個其實就是使用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); } }
前面定義了兩個須要手動註冊的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"); } }
前面的測試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
註解的方式引入手動註冊的Bean;此時會拋npe@Autowired
來注入沒啥毛病(是否絕對呢?)originBean
,也是經過註解方式注入,若是正常返回,表示手動註冊的也能夠這麼引用其餘的Bean;不然不行執行結果如上圖,簡單來講,就是手動註冊的Bean,和咱們通常使用的Bean也沒什麼兩樣,原來能夠怎麼用,如今依然能夠這麼用
前面這種手動注入的方式有個很差的地方就是主動註冊的這個邏輯,感受寫在什麼地方都不太優雅,在Spring項目的源碼中經過實現BeanDefinitionRegistryPostProcessor擴展方式
接口的方式比較多,好比org.springframework.cloud.autoconfigure.RefreshAutoConfiguration
依葫蘆畫瓢實現一個
@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的註冊;二者從根本上也沒太大的區別,上面只是給出了一種使用演示
測試的思路基本上和前面同樣,定義了三個須要咱們註冊的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); } }
一樣寫一個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); }
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
一灰灰blog
知識星球