Spring中的Bean除了前面提到的幾種JavaConfig或者@Component
等註解標識以外,也是能夠動態的向Spring容器註冊的,本篇博文將主要介紹java
之前也寫過關於動態註冊Bean的博文,如 180804-Spring之動態註冊beangit
咱們的實現方式和上面也沒什麼區別,依然是藉助BeanDefinition
來建立Bean定義並註冊到BeanFactory中,具體實現的核心代碼以下github
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容器中不容許出現同名的Beanspring
動態建立Bean,並非塞入容器之中就完結了,塞進去以後,是爲了後續的使用,天然而然的就會有下面幾種情形app
即不依賴其餘的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;
}
}
複製代碼
和前面一個不一樣,這個Bean內部須要注入其餘的Bean,所以咱們主動註冊Bean時,可否將依賴的Bean也注入進去呢?dom
定義一個測試Beanide
@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定義以下spring-boot
@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
能否?post
@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