寫在前面的話 適用讀者:有一定經驗的,本文不適合初學者,因為可能不能理解我在說什麼 文章思路:不會一開始就像別的博客文章那樣,Bean 的生命周期,源碼解讀(給你貼一大堆的源碼)。個人覺得應該由問題驅動,為什麼為出現 BeanFactory ,為什麼會有生命周期。 正文 一開始我們使用 bean 都 ...
寫在前面的話
適用讀者:有一定經驗的,本文不適合初學者,因為可能不能理解我在說什麼
文章思路:不會一開始就像別的博客文章那樣,Bean 的生命周期,源碼解讀(給你貼一大堆的源碼)。個人覺得應該由問題驅動,為什麼為出現 BeanFactory ,為什麼會有生命周期。
正文
一開始我們使用 bean 都是簡單bean,如 vo ,po,entity,dto,我們是這麼玩的
XXEntity xxEntity = new XXEntity();
xxEntity.setPropA("字元串");
後面可能出現了某個比較複雜的 bean ,它有一個對象做為屬性,需要在構造時或構造後設置值(示例而已,不要較真),如
// 構建序列化實例,這裡 Serializable 是介面,使用介面的好處是在使用別的序列化時,不需要修改 jedis 類
Serializable fastJsonSerizlizable = new FastJsonSerizlizable();
// 構建目標 jedis 實例 ,需要先構建序列化對象
Jedis jedis = new Jedis();
jedis.setSerializable(fastJsonSerizlizable);
這時來了 serviceA 類和 serviceB 類,它們都需要使用 redis,我不可能在每個類裡面都去把 jedis 實例化的過程寫一遍,這時有經驗的同學會寫一個工具類來創建 jedis ,像這樣
public BeanUtil {
// 可以把創建序列化單拿出來,因為除了 redis 需要序列化之外,kafka 也需要序列化
public static Serializable createSerializable(){
return new FastJsonSerizlizable();
}
public static Jedis createJedis(){
Jedis jedis = new Jedis();
jedis.setSerializable(createSerializable());
return jedis;
}
}
// 這裡我 serviceA,serviceB 都可以使用 createJedis 來直接獲取 jedis 實例 ,而不需要關心創建細節,使用哪個序列化等問題
上面代碼有幾個問題
- 每次使用時都會創建 jedis 對象 ,而每一個 jedis 對象又會單獨對一個 Serializable 對象 ,但是 fastJson 的序列化和 jedis 都只是工具類型的東西,一個實例足已。
- 無法對 Jedis 進行配置
- 不能讓使用者去創建 BeanUtil 實例 ,改進的代碼 如下
public BeanUtil {
// 禁用 BeanUtil 構建
private BeanUtil(){}
// 這裡我們可以使用 bean 的全路徑 => bean 實例來緩存 bean
static Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
static{
// 初始化時,在內容緩存這些 bean 的實例,因為 jedis 依賴於 serializable ,需要需要先創建 serializable
Serializable serializable = createSerializable();
beansCache.put(Serializable.class.getSimpleName(),serializable);
Jedis jedis = createJedis();
beansCache.put(jedis.class.getSimpleName(),jedis);
}
static Serializable createSerializable(String type){
Serializable serializable = beansCache.get("serializable");
if(serializable != null)return serializable;
switch(type){
case "kryo": // kryo 不能用單例,請忽略本問題,示例而已
return new KryoSerializable();
case "protostuff":
return new protostuffSerializable();
default:
return new FastJsonSerizlizable();
}
}
static Jedis createJedis(String serializableType){
Jedis jedis = new Jedis();
Serializable serializable = beansCache.get("serializable");
jedis.setSerializable(serializable);
return jedis;
}
//然後對外提供獲取 Bean 的方法
public static Object getBean(String beanName){
return beansCache.get(beanName);
}
public static T getBean(Class<T> type){
return beansCache.get(type.getSimpleName());
}
}
但如果寫這個類的是小明,經過一段時間後這個類里會出現大量的 createXx 和 XX 的初始化操作,而且依賴程度也非常複雜,這時小明想,是時候優化一波了,於是小明想了一種解決方案,定義了一種 xml 語法
使用 bean 標簽來定義一個 bean,每個 bean 都有唯一的一個 id 信息 ,使用 property 來定義它的屬性 ,如果是複雜屬性使用 ref ,解析這個xml 得到一個完整的 bean 依賴圖
<beans>
<bean id="serializable" class="com.xx.FastJsonSerizlizable" />
<bean id="jedis" class="com.xx.Jedis">
<property name="host" value="localhost" />
<property name="serializable" ref="serializable"/>
</bean>
</beans>
這時會有一個依賴問題,我創建 jedis 要先創建 serializable ,但是 serializable 的 xml bean 定義是寫在文件前面 的,小明想了一個辦法,先把 ref 使用字元串先存著,全部放到一個 bean 定義中,像這樣
Map<String,BeanDefinition> beanDefinitions = new HashMap();
然後把其解析成一顆依賴樹,這樣就可以先構造樹葉,然後逐層構造對象 ,但也有一種棘手的情況 ,那就是迴圈依賴
root
|-jedis
|- serializable
什麼是迴圈依賴呢,最簡單的 A 依賴於 B,B 依賴於 A ,或者中間有更多的依賴最後形成了一個圈,A-B-C-A
最原始的解決方式是這樣的,我們可以先使用構造函數把它們都創建出來,不能是有帶它們的構造函數,然後通過 set 把對象通過屬性設置值。所以除了構造註入外,通過屬性方式是可以解決迴圈依賴的。
這時我們的 BeanUtil 變成了這樣,想想不能叫工具類了,改為實體類 Factory
public BeanFactory {
Map<String,BeanDefinition> beanDefinitions = new HashMap();
// 這裡我們可以使用 bean 的全路徑 => bean 實例來緩存 bean
Map<String,Object> beansCache = new ConcurrentHashMap<String,Object>();
{
// 載入 xml bean 配置文件
beanDefinitions = loadXml(contextConfigurations:String []);
//實例化所有 bean
beansCache = instanceBeans(beanDefinitions);
}
//然後對外提供獲取 Bean 的方法
public Object getBean(String beanName){
return beansCache.get(beanName);
}
public T getBean(Class<T> type){
return beansCache.get(type.getSimpleName());
}
}
這看起來已經足夠完美了,但這時程式員A提問了,我需要對我的某個類的初始化時,我要獲取一些比如連接資源,文件資源,然後在類銷毀時想要回收資源,但根據上面沒任何辦法可以做到。
小明說,這好辦,我提供幾個介面給你,你實現一下,我會在實例化 Bean 的時候 ,如果發現你有實現介面,在相應的過程里我就幫你調用一下,於是小明就添加了兩個介面
public interface InitializingBean{
void afterPropertiesSet() throws Exception;
}
public interface DisposableBean{
void destroy() throws Exception;
}
程式員A 的問題解決了,這時程式員B說,有沒有一種辦法,可以對所有 Bean 的初始化過程進行攔截,而不是我當前這個類,我想把每一個 service 改成代理類,我想要給 service 中的方法添加事務。
小明說,那好吧,我把 bean 的屬性都註入完了,然後給這個 bean 交給你,你裝飾一下這個 bean 然後再還給我,於是小明提供出了這樣一個介面 ,在 bean 初始化前和初始化後,你都可以來修改 bean ,不要要註意,這個是針對全局的,不是你個人的 bean ,要做好過濾操作
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException ;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}
程式員C 這時又發問了,我創建了一個 BeanA 但我怎麼樣可以拿到 BeanC 啊,我想看看 c 的一些屬性。
小說說,真煩,我乾脆把 map 都給你好,不,我把 BeanFactory 都給你好了,於是有了這個介面
public interface BeanFactoryAware{
void setBeanFactory(BeanFactory beanUtil);
}
這時程式D 又問了,我在 setBeanFactory 的時候 ,我創建的全局 processor 執行了嗎,還是在之後執行,頭大。
小明說,我整理下執行順序,取個名吧,叫 bean 的生命周期,順便再提供幾個實用的介面,bean 的名字我還沒告訴你呢,於是整理的生命周期如下
反射創建 Bean
填充對象屬性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多個
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多個
DisposableBean.destory()
程式員E 又說了,xml 配置太麻煩了,jdk1.5 不是有註解嗎,我在類上加個標識,你掃描我的類,幫我創建實例唄
然後我需要用的時候,我在屬性上加個標識,你同樣可以根據類型找到依賴的類,然後把對應的實例創建好,幫我把值放進去就好了,如果這個類的創建過程比較複雜,我自己來創建,然後我把它返回給你,我定義一個方法,加個 Bean 的標識,你來讀進容器。
於是小明又加了 @Component
來表示組件,@Bean
來表示自定義實例創建,@Autowired
來註入對象 @PostConstruct
來執行類的初始化工作 @PreDestroy
來做類的銷毀工作,類的生命周期變成這樣
反射創建 Bean
填充對象屬性
BeanNameAware.setBeanName();
BeanFactoryAware.setBeanFactory ();
BeanPostProcessor.postProcessBeforeInitialization(); 多個
PostConstruct
InitializingBean.afterPropertiesSet()
BeanPostProcessor.postProcessAfterInitialization(); 多個
PreDestroy
DisposableBean.destory()
但是為了相容以前的 xml 形式,小明這時把 BeanFactory 抽象成介面,提供 getBean 方法,根據職責單一原則,BeanFactory 不應該再做解析 Bean 的工作;
再創建一個介面用於載入 Bean 定義,有兩個實現 XmlBeanRegistry ,AnnotationBeanRegistry ,載入 Bean 定義後再合併,考慮到以後還有可能添加別的註冊 bean 的方式 ,一次性提供一個對外的介面
public interface BeanFactoryPostProcessor{
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
你可以把你規則寫成的 bean 定義,實例化為我要求的 BeanDefinition 然後發給我就可以自定義實現把你自定義的 bean 添加到容器中了
一點小推廣
創作不易,希望可以支持下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。
Excel 通用導入導出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代碼 ,從資料庫生成代碼 ,及一些項目中經常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven