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