一般情況下,我們Spring應用中的bean都是通過註解或者xml注入到容器中的,有些情況下我們可能想手動往容器中注入bean,即編程方式注入bean。
本文所使用源碼包版本:spring-beans-5.0.5.RELEASE.
如何註冊?
Spring 中用BeanDefinition接口描述一個bean,Spring容器中用Map<String, BeanDefinition> beanDefinitionMap
存儲beanName和BeanDefinition對象的映射關係【beanDefinitionMap 可參考DefaultListableBeanFactory】。Spring在實例化一個bean,都是先從 beanDefinitionMap 中獲取beanDefinition對象,進而構造出對應的bean。因此,我們手動註冊bean的問題,就演化爲如何往這個 beanDefinitionMap 放入我們要註冊bean對應的 BeanDefinition 對象。
好的框架肯定有好的拓展性,Spring 提供了 BeanDefinitionRegistry 接口來操作底層beanFactory實現的beanDefinitionMap。
測試示例:
我們有個類 H,把它手動添加到Spring 容器中成爲一個bean:
public class H {
private String s ;
public void test(){
System.out.println("hello world!");
}
public void setS(String s) {
this.s = s;
}
}
主程序:
@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(H.class);
beanFactory.registerBeanDefinition("h", beanDefinition);
H h = (H) applicationContext.getBean("h");
h.test();
}
}
程序運行結果:
hello world!
從容器中獲取到了名稱爲“h”的bean,並且正確打印了方法調用結果。另外,在類H中,可以使用@Autowired注入其他已有的bean的,bean的依賴關係也是支持的。如下:
public class H {
private String s ;
@Autowired
Y y;
public void test(){
y.test();
System.out.println("hello world!");
}
public void setS(String s) {
this.s = s;
}
}
H類依賴一個已有的bean Y:
@Component
public class Y {
public Y() {
System.out.println("Construct Y");
}
public void test(){
System.out.println("y.test()...");
}
}
上述測試代碼輸出結果:
Construct Y
y.test()...
hello world!
涉及源碼:
BeanDefinition 接口:
BeanDefinition描述了一個bean實例,該實例具有屬性值,構造函數參數值以及具體實現所提供的更多信息。類圖如下,主要三個實現類:RootBeanDefinition、ChildBeanDefinition 和 GenericBeanDefinition。Bean的屬性,比如對應Java類的Class、作用域scope、是否延遲加載lazyInit等,都可以通過BeanDefinition控制。
RootBeanDefinition 可以在配置階段用於註冊各個Bean定義,上面的測試用例 BeanDefinition 也可以使用 RootBeanDefinition 實現。 但是官方推薦,從Spring 2.5開始,以編程方式註冊Bean定義的首選方法是{@link GenericBeanDefinition}類。
BeanDefinitionRegistry 接口:
包含BeanDefinition的註冊表的接口,例如 RootBeanDefinition 和 ChildBeanDefinition 實例。 通常由內部使用AbstractBeanDefinition層次結構的 BeanFactory 實現。
/**
* Register a new bean definition with this registry.
* Must support RootBeanDefinition and ChildBeanDefinition.
* @param beanName the name of the bean instance to register
* @param beanDefinition definition of the bean instance to register
* @throws BeanDefinitionStoreException if the BeanDefinition is invalid
* or if there is already a BeanDefinition for the specified bean name
* (and we are not allowed to override it)
* @see RootBeanDefinition
* @see ChildBeanDefinition
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
注意用到的 registerBeanDefinition 方法,如果構造的 BeanDefinition 無效,或者指定的bean名稱已經存在BeanDefinition(如果不允許覆蓋它)會拋出 BeanDefinitionStoreException 異常。BeanDefinitionRegistry 還有其他的方法,比如根據bean名稱移除beanDefinition的方法 removeDefinition(String beanName)等。
DefaultListableBeanFactory 類:
DefaultListableBeanFactory中用Map<String,BeanDefinition> beanDefinitionMap 保存beanName和BeanDefinition對象的映射關係,同時實現了BeanDefinitionRegistry接口,用來操作beanDefinitionMap,其中 registerBeanDefinition 方法:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
//校驗傳入beanDefinition的正確性
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//如果已存在該bean名稱的beanDefinition,並且不允許覆蓋,則拋異常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
//檢查beanFactory的bean創建階段是否已開始,可以理解爲容器是否已經初始化過了
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
//把新加入的beanName-beanDefinition映射加入beanDefinitionMap
//把beanName加入的維護的beanDefinitionName列表(ArrayList)中
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
//如果手動註冊的單例bean名稱包含了beanName,需要移除
//這個地方默認構造的beanDefinition對象作用域也是“”,等價於單例,但是卻沒有把 beanName加入到manualSingletonNames集合(LinkedHashSet)中
//最終beanName對應的bean實例化後,manualSingletonNames還是沒有這個beanName,略奇怪。。。
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
//如果oldBeanDefinition不爲null,且單例池緩存中已經有beanName對應的bean,則重置給定bean的所有bean定義緩存,包括從其派生的bean的緩存。
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
SingletonBeanRegistry 接口:
SingletonBeanRegister接口,是單例bean的管理接口,也可以用來註冊單例bean,方法:
void registerSingleton(String beanName, Object singletonObject);
DefaultListableBeanFactory類也實現了該接口,我們可以將上面的測試代碼修改下,也可以達到註冊一個單例bean的目的:
@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.registerSingleton("h", new H());
H h = (H) applicationContext.getBean("h");
h.test();
}
}
實現起來簡單,但是有限制:一是bean依賴項不好處理;二是待注入的實例應該被完全初始化,註冊表不會執行任何初始化回調方法,比如InitializingBean的{@code afterPropertiesSet}方法等;。