乾貨點:
看完該篇文章我們可以瞭解到爲何要動態註冊bean、動態註冊bean的方式、註冊和實例化的區別、spring是什麼時候實例化bean的。
描述:
前陣子在給項目寫組件的時候研究了下spring組件拓展流程,其中遇見了動態註冊bean的操作,對於長期使用spring容器管理bean的同學來說動態註冊bean應該是活久見系列,爲此打算以組件中使用到的方式講講動態註冊方面的相關解說。
應用場景
在自定義組件的時候,需要自定義命名空間註冊器,其中需要提供一個配置解析器ConfigDefinitionParser,配置解析器需要繼承AbstractBeanDefinitionParser, 其中需要重寫函數parseInternal,返回的是AbstractBeanDefinition類型的數據。爲了返回AbstractBeanDefinition,我借鑑了其他spring內部組件的寫法,這邊就用到了動態註冊bean了。
在進入動態註冊bean的相關解說前之前有必要大致描述下spring容器註冊bean的相關知識!
瞭解下DefaultListableBeanFactory
DefaultListableBeanFactory是spring容器註冊bean的核心,是spring註冊及加載bean的默認實現。
一句話來描述其應用那就是:在spring項目啓動的時候通過DefaultListableBeanFactory註冊所有的BeanDefinition後放入definition表,後面在我們使用的時候就可以直接從表中加載了。
瞭解下BeanDefinition
在說到動態註冊bean之前,要先提下BeanDefinition
這是從官方文檔截出來的,對此我的理解是,BeanDefinition是bean在實例化之前存在spring容器中的一種狀態。
分不清註冊和實例化的同學們在這裏內心應該是 ━━( ̄ー ̄*|||━━ wocao,實例化和註冊,什麼跟什麼 。
別急,在文章最後會點一下實例化和註冊的哈!
大致瞭解了這兩個之後便可以真正講解如何動態註冊bean了
使用 BeanDefinitionBuilder註冊bean
舉個可以運行的栗子
package com.example.testdemo.beanDefinitionBuilderTest.main;
public class TestBean {
private String str;
private TestBean2 testBean2;
public void setStr(String str) {
this.str = str;
}
public void setTestBean2(TestBean2 testBean2) {
this.testBean2 = testBean2;
}
@Override
public String toString() {
return "TestBean{" +
"str='" + str + '\'' +
", testBean2=" + testBean2 +
'}';
}
}
複製代碼
package com.example.testdemo.beanDefinitionBuilderTest.main;
public class TestBean2 {
private int a;
public void setA(int a) {
this.a = a;
}
@Override
public String toString() {
return "TestBean2{" +
"a=" + a +
'}';
}
}
複製代碼
主要main類,可分爲三個步驟:定義、註冊、再取出
package com.example.testdemo.beanDefinitionBuilderTest.main;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class BeanDefinitionBuilderExample {
public static void main(String[] args) {
init();
}
private static void init() {
// 構建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊testBean2
registerTestBean2(beanFactory);
// 註冊testBean
registerTestBean1(beanFactory);
// 從DefaultListableBeanFactory中讀取bean
TestBean bean = beanFactory.getBean(TestBean.class);
System.out.println(bean);
}
private static void registerTestBean1(DefaultListableBeanFactory beanFactory) {
// 構建TestBean
BeanDefinitionBuilder b1 = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class);
// 添加屬性
b1.addPropertyValue("str", "myStringValue");
// 添加testBean2引用
b1.addPropertyReference("testBean2", "testBean2");
// 註冊TestBean
beanFactory.registerBeanDefinition("testBean", b1.getBeanDefinition());
}
private static void registerTestBean2(DefaultListableBeanFactory beanFactory) {
// 構建TestBean2
BeanDefinitionBuilder b2 = BeanDefinitionBuilder.rootBeanDefinition(TestBean2.class);
// 添加屬性
b2.addPropertyValue("a", 1);
// 註冊TestBean2
beanFactory.registerBeanDefinition("testBean2", b2.getBeanDefinition());
}
}
複製代碼
運行後輸出
TestBean{str='myStringValue', testBean2=TestBean2{a=1}}
關於如何給BeanDefinitionBuilder添加各種成員屬性和構造傳參可以直接查看api地址:
使用 BeanFactoryPostProcessor註冊bean
BeanFactoryPostProcessor允許自定義BeanDefinition,使用的GenericBeanDefinition,然後再註冊入DefaultListableBeanFactory。
舉個可以運行的栗子
這裏只給出main類,和上面的例子的區別在於註冊函數的不同
package com.example.testdemo.beanDefinitionBuilderTest.main;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class BeanDefinitionBuilderExample {
public static void main(String[] args) {
init();
}
private static void init() {
// 構建DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 註冊testBean2
registerTestBean2(beanFactory);
// 註冊testBean
registerTestBean1(beanFactory);
// 從DefaultListableBeanFactory中讀取bean
TestBean bean = beanFactory.getBean(TestBean.class);
System.out.println(bean);
}
rivate static void registerTestBean1(DefaultListableBeanFactory beanFactory) {
// 構建TestBean
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(TestBean.class);
// 添加屬性
bd.getPropertyValues().add("str", "myStringValue");
// 添加testBean2引用
bd.getPropertyValues().add("testBean2", beanFactory.getBean("testBean2"));
// 註冊TestBean
beanFactory.registerBeanDefinition("testBean", bd);
}
private static void registerTestBean2(DefaultListableBeanFactory beanFactory) {
// 構建TestBean2
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(TestBean2.class);
// 添加屬性
bd.getPropertyValues().add("a", 1);
// 註冊TestBean2
beanFactory.registerBeanDefinition("testBean2", bd);
}
}
複製代碼
運行後輸出
TestBean{str='myStringValue', testBean2=TestBean2{a=1}}
可以看到註冊bean的時候對GenericBeanDefinition的處理是可以自定義了,最終運行效果也是一樣的。
關於註冊和實例化
註冊和實例化其實就是spring容器管理bean的一個過程,先有註冊bean纔有實例化bean。 那麼spring什麼時候實例化bean呢?這裏可以分爲2種情況
-
如果我們使用BeanFactory作爲bean的工廠類,如我上面那樣的,則所有的bean都是在第一次使用該Bean的時候實例化,也就是從容器get出來的時候。
-
如果我們使用ApplicationContext作爲bean的工廠類,則又分爲以下幾種情況:
- 如果bean的scope是singleton的,也就是單例,並且lazy-init爲false(默認是false,所以可以不用設置),意思就是懶初始化,則ApplicationContext啓動的時候就實例Bean,並且將實例化後的Bean放在緩存中,下次再使用該Bean的時候,直接從這個緩存中。
- 如果bean的scope是singleton的,而lazy-init爲true,則該Bean的實例化是在第一次使用該Bean的時候進行實例化。
- 如果bean的scope是prototype的,則該Bean的實例化是在第一次使用該Bean的時候進行實例化。
ApplicationContext由BeanFactory類派生而來,所以提供了更多面向實際應用的功能。 以上可以看出spring容器其實是很懶的!!!