IoC容器總結(jié)與簡(jiǎn)單模擬
當(dāng)一個(gè)組件需要外部資源時(shí),最直接也最明智的方法是執(zhí)行查找,這種行為稱為主動(dòng)查找。但這種查找存在一個(gè)缺點(diǎn)——組件需要知道如何獲得資源。一個(gè)好的獲取資源的解決方案是應(yīng)用IoC(Inversion of Control,控制反轉(zhuǎn))。它的思想是反轉(zhuǎn)資源獲取的方向。傳統(tǒng)的資源查找方式是要求組件向容器發(fā)起請(qǐng)求來(lái)查找資源,作為回應(yīng),容器適時(shí)的返回資源。而應(yīng)用了IoC之后,則是容器主動(dòng)的將資源推送到它所管理的組件里,組件所要做的僅僅是選擇一種合適的方式接受資源。
IoC是一種通用的設(shè)計(jì)原則,而DI(Dependency Injection,依賴注入)則是具體的設(shè)計(jì)模式,它體現(xiàn)了IoC的設(shè)計(jì)原則。DI是IoC典型的實(shí)現(xiàn),所以IoC與DI術(shù)語(yǔ)會(huì)被混用。IoC與DI的關(guān)系就好比Java中的"接口"和"接口的實(shí)現(xiàn)類"的關(guān)系一樣。
在DI模式下,容器全權(quán)負(fù)責(zé)的組件的裝配,容器以一些預(yù)先定義好的方式(例如setter方法或構(gòu)造函數(shù))將匹配的資源注入到每個(gè)組件里。目前有三種類型的DI:
setter注入,setter注入會(huì)存在一些問(wèn)題,1. 容易出現(xiàn)忘記調(diào)用setter方法注入組件所需要的依賴,將會(huì)導(dǎo)致NullPointerException異常。2. 代碼會(huì)存在安全問(wèn)題,第一次注入后,不能阻止再次調(diào)用setter,除非添加額外的處理工作。但是由于setter注入非常簡(jiǎn)單所以非常流行(絕大多數(shù)Java IDE都支持自動(dòng)生成setter方法)。
構(gòu)造器注入,構(gòu)造器注入能夠一定程度上解決setter注入的問(wèn)題。但是該中注入方式也會(huì)帶來(lái)一些問(wèn)題,如果組件有很多的依賴,則構(gòu)造函數(shù)的參數(shù)列表將變得冗長(zhǎng),會(huì)降低代碼可讀性。
接口注入 ,該注入方式使用的非常少,它要求組件必須實(shí)現(xiàn)某個(gè)接口,容器正是通過(guò)這個(gè)接口實(shí)現(xiàn)注入依賴的。接口注入的缺點(diǎn)比較明顯,使用接口注入需要實(shí)現(xiàn)特定的接口,而接口又特定于容器,所以組件對(duì)容器產(chǎn)生了依賴,一旦脫離容器,組件不能重用。這是一種"侵入式"注入。
其中"setter注入"和"構(gòu)造器注入"是被廣泛運(yùn)用的,絕大多數(shù)的IoC容器都支持這兩種DI類型。
模仿Spring IoC容器
假設(shè)一個(gè)系統(tǒng)的功能之一是能夠生成PDF或HTML格式的報(bào)表。
- /*生成報(bào)表的通用接口*/
- public interface ReportBuilder
- {
- public void build(String data);
- }
生成PDF和HTML格式的實(shí)現(xiàn)類:
- /*生成HTML格式報(bào)表*/
- public class ReportHtmlBuilder implements ReportBuilder {
- @Override
- public void build(String data) {
- /*示意代碼*/
- System.out.println("build html report!");
- }
- }
- /*生成PDF格式報(bào)表*/
- public class ReportPdfBuilder implements ReportBuilder {
- @Override
- public void build(String data) {
- System.out.println("build pdf report!");
- }
- }
報(bào)表服務(wù)類:
- /*報(bào)表服務(wù)類*/
- public class ReportService
- {
- /*依賴"ReportBuilder"*/
- private ReportBuilder builder;
- public ReportBuilder getBuilder()
- {
- return builder;
- }
- /*setter注入*/
- public void setBuilder(ReportBuilder builder)
- {
- this.builder = builder;
- }
- public void builderYearReport(int year)
- {
- this.builder.build("data");
- }
- }
IoC容器配置文件"component.properties"
- pdfBuilder=com.beliefbitrayal.ioc.inter.imp.ReportPdfBuilder
- htmlBuilder=com.beliefbitrayal.ioc.inter.imp.ReportHtmlBuilder
- reportService=com.beliefbitrayal.ioc.server.ReportService
- reportService.builder=htmlBuilder
IoC容器:
- public class Container
- {
- /*用于儲(chǔ)存Component的容器*/
- private Map<String, Object> repository = new HashMap<String, Object>();
- public Container()
- {
- try
- {
- /*讀取容器配置文件"component.properties"*/
- Properties properties = new Properties();
- properties.load(new FileInputStream("src/component.properties"));
- /*獲取配置文件的每一行信息*/
- for(Map.Entry<Object, Object> entry : properties.entrySet())
- {
- String key = (String)entry.getKey();
- String value = (String)entry.getValue();
- /*處理配置文件的每一行信息*/
- this.handler(key, value);
- }
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- private void handler(String key,String value) throws Exception
- {
- /*
- * reportService=com.beliefbitrayal.ioc.server.ReportService
- * reportService.builder=htmlBuilder
- * 第一種情況,key值中間沒(méi)有"."說(shuō)明為一個(gè)新組件。對(duì)它的處理為創(chuàng)建它的對(duì)象,將其對(duì)象放入Map中。
- * 第二種情況,key值中間出現(xiàn)"."說(shuō)明這個(gè)屬性條目是一個(gè)依賴注入。根據(jù)"."的位置將這個(gè)key值劃分為兩部分,第一部分為組件的名字,第二部分為
- * 該組件需要設(shè)置的屬性。
- */
- String[] parts = key.split("\\.");
- /*情況1*/
- if(parts.length == 1)
- {
- /*通過(guò)反射的方式創(chuàng)建組件的對(duì)象*/
- Object object = Class.forName(value).newInstance();
- this.repository.put(key, object);
- }
- else
- {
- /*對(duì)于情況2,首先用key值的第一部分(組件名)獲取組件*/
- Object object = this.repository.get(parts[0]);
- /*再使用value值指定的組件名從Map對(duì)象中獲取依賴*/
- Object reference = this.repository.get(value);
- /*將獲取的依賴注入到指定的組件的相應(yīng)屬性上,"PropertyUtils"類屬于Apache下Commons BeanUtil第三方類庫(kù),
- * 要使用它還需要下載Commons Logging第三方類庫(kù)
- */
- PropertyUtils.setProperty(object, parts[1], reference);
- }
- }
- public Object getComponent(String key)
- {
- return this.repository.get(key);
- }
- }
根據(jù)配置文件,我們?cè)趫?chǎng)景類中使用的報(bào)表應(yīng)該是HTML格式的:
- public class Client
- {
- public static void main(String[] args)
- {
- /*創(chuàng)建容器*/
- Container container = new Container();
- /*從容器中獲取"報(bào)表服務(wù)類"*/
- ReportService reportService = (ReportService)container.getComponent("reportService");
- /*顯示報(bào)表*/
- reportService.builderYearReport(0);
- }
- }
控制臺(tái)的輸出:
- build html report!
我們?nèi)粜枰狿DF格式的只需要修改屬性文件即可:
- pdfBuilder=com.beliefbitrayal.ioc.inter.imp.ReportPdfBuilder
- htmlBuilder=com.beliefbitrayal.ioc.inter.imp.ReportHtmlBuilder
- reportService=com.beliefbitrayal.ioc.server.ReportService
- reportService.builder=pdfBuilder
場(chǎng)景類不變,控制臺(tái)輸出:
- build pdf report!
容器可以從基于文本的控制文件中讀取組件的定義,這使得容器可以重用。現(xiàn)在即使隨意改變組件的定義,都不用修改容器的代碼。這個(gè)例子很好的演示了IoC容器的核心原理和機(jī)制。
通過(guò)以上分析和舉例,控制反轉(zhuǎn)IoC就是一個(gè)組件的依賴是由容器來(lái)裝配,組件不做定位查詢,只提供普通的Java方法讓容器去裝配依賴關(guān)系,IoC容器是一般通過(guò)setter注入或構(gòu)造函數(shù)注入的方式將依賴注入到組件中的,組件的依賴我們一般通過(guò)一個(gè)配置文件來(lái)描述(XML或Properties),配置文件在IoC容器被構(gòu)建時(shí)讀取解析。
原文鏈接:http://www.cnblogs.com/beliefbetrayal/archive/2012/02/02/2335192.html
【編輯推薦】