IOC 容器及DI設計模式解讀

IOC/DI是Spring的核心思想说莫,其在Spring生態(tài)圈中有著舉足輕重的作用,地位崇高,今天我們就來看看IOC/DI緣起何處俱济,路在何方。

在Java社區(qū)中曾掀起一股輕量級容器的潮流钙勃,它們致力于將不同項目中的組件裝配到一個高內(nèi)聚的應用中蛛碌。其實,這些容器的底層使用了同一種設計模式辖源,也就是通常所所說的IOC(控制反轉)蔚携。在下面的內(nèi)容中,將會探究下這種模式(依賴注入)是如何運作的克饶,而且還會與Service Locator(服務定位器酝蜒,也是一種IOC實現(xiàn)方式)進行對比。重要的并不是選擇哪種實現(xiàn)方式彤路,DI也好秕硝,Service Locator也罷芥映,重要的是配置與使用分離的原則洲尊,這個思想才是IOC的精髓所在。

在Java的世界中奈偏,有一件非常有趣的現(xiàn)象坞嘀,出現(xiàn)了很多主流或者說官方JavaEE技術的替代的開源的組件或活動。在很大程度上惊来,有很多組件是出于對重量級且復雜的主流JavaEE技術的本能應對丽涩,但是也有很多是源自于創(chuàng)造性想法的替代產(chǎn)品。這些組件需要解決一個通用問題:如何將不同的元素連接并裝配在一起裁蚁。比如矢渊,如何將一個web控制器架構體系與數(shù)據(jù)庫接口體系組裝在一起,而這兩個組件是由不同團隊開發(fā)的枉证,他們之間彼此并不認識矮男。很多框架已經(jīng)嘗試解決這個問題,他們中有些提供一個通過不同分層來裝配組件的通用能力室谚,這些組件通常被稱為輕量級容器毡鉴,比如Spring和PicoContainer崔泵。

這些容器的底層是很多有意思的設計原則,接下來會介紹這里面的幾個原則猪瞬,示例代碼使用Java憎瘸,但是其中的大部分原則同樣適用于其他OO環(huán)境,尤其是.NET陈瘦。

Comonents and Services

談到連接元素就不得不說到Component(組件)和Service(服務)這兩個令人困惑的技術術語努释,你可能讀到很多定義這兩個概念的文章,他們很多是冗長且矛盾的惶桐。以下就是我目前對這兩個重載術語的使用脖镀。

我使用Component表示提供給其他應用程序使用的一系列軟件(不會主動變化),而這個應用程序并不受Component開發(fā)者的控制线婚,換句話說Component是個售出的成品(比如手機)遏弱,用戶直接使用,廠家并不知道用戶如何使用塞弊。所謂的不會主動變化漱逸,我的意思是應用程序不會去改變Component的源碼,但是可以通過Component提供的接口來改變Component的行為游沿,進而實現(xiàn)對Component的擴展饰抒。

Service與Component的相近,它被用來提供給為外部程序你使用诀黍。我認為它們之間主要的不同在于袋坑,Component是本地使用(如jar文件,assembly, dll或 a source import)眯勾,Service將通過遠程接口遠程使用枣宫,同步或異步都可以(比如web Service,messsaging system, RPC or socket)吃环。

我在文章中會大量使用Service也颤,但是其中的大部分邏輯也可以應用于本地Component。事實上郁轻,通常我們需要使用一些本地Component去訪問遠程的Service翅娶。由于Component or Service不易讀寫,所以就簡寫為Service好唯,這樣形式看上去更加“時髦”竭沫。

A Naive Example

閑話少說,直接上代碼骑篙。為了不至于讓讀者陷入復雜業(yè)務場景中蜕提,文中都使用極其簡單的示例代碼,但這足以讓你get到其中的精髓替蛉。

在下面這個例子中我將編寫一個Component贯溅,它有一個提供某個導演的電影的名稱列表拄氯。我們使用一個方法就可以實現(xiàn)這個非常實用的功能,代碼片段如下:

public class MovieLister {
    private MovieFindler findler;
    ...
    public Movie[] moviesDirectedBy(String args) {
        List<Movie> allMovies = findler.findAll();
        for (Iterator<Movie> it = allMovies.iterator(); it.hasNext(); ) {
            Movie movie = it.next();
            if (!(args).equals(movie.getDirector())) {
                it.remove();
            }
        }
        return allMovies.toArray(new Movie[allMovies.size()]);
    }
}

這段代碼很簡單它浅,做了3件事:

  1. 從findler對象(稍后介紹)獲取所有電影的列表
  2. 遍歷該列表译柏,刪除不是該導演的電影名稱
  3. 返回剩余的電影名稱列表(即該導演執(zhí)導的電影)

我并不打算修正這段代碼使其看上去更加高級,因為這是本文所要講述的真實的要點姐霍,這至關重要鄙麦。

這個要點就是這個finder對象,或者說如何將finder對象和lister對象關聯(lián)起來镊折。

這個例子有趣的地方在于我想要movieDirectedBy這個方法與電影如何被存儲是完全沒有依賴的或者說完全解耦的胯府,所以這個方法引用了finder對象,而finder對象所要做的就是通過finderAll方法將數(shù)據(jù)響應給lister對象恨胚。我可以通過為finder定義一個接口來實現(xiàn)這一點:

public interface MovieFindler {
    List<Movie> findAll();
}

現(xiàn)在所有的一切都很好的解耦骂因,但是如果要正常使用,還需要頂一個具體類來實現(xiàn)findAll接口赃泡,在這個例子中我把創(chuàng)建代碼放在lister類的構造函數(shù)中寒波。

public MovieLister(){
    findler = new ColonDelimitedMovieFinder("movies1.txt");
}

這個實現(xiàn)類的名稱源于這是從一個內(nèi)容是逗號分割的文件中獲取數(shù)據(jù),這樣的命名意義明確升熊,是一種良好的命名習慣俄烁。

現(xiàn)在,這個實現(xiàn)類只是我一個人用级野,這一切都很好页屠。但是,如果我的朋友覺得我寫的這個功能十分誘人并且想要拷貝一份我的代碼呢蓖柔?如果他們的電影列表也剛好存在用冒號分割的文件中并且文件名也是movie1.txt辰企,這當然是極好的。如果他們的文件名不同渊抽,我們可以設計一個屬性文件蟆豫,將對應的文件名配置為在里面议忽,然后從該屬性文件中讀取電影文件名懒闷,這樣實現(xiàn)起來也很簡單。但是栈幸,如果朋友們使用其他的存儲介質:比如SQL數(shù)據(jù)庫愤估,XML文件或者使用Web Service,或者使用另一種格式的文本文檔呢速址?這個時候玩焰,我們需要另一個類用于抓取數(shù)據(jù)。因為我們現(xiàn)在是面向接口編程芍锚,并不需要去修改moviesDirectedBy方法昔园,但是蔓榄,我們確實需要采取某種方式來獲取正確的finder實現(xiàn)類對象。

Figure 1: The dependencies using a simple creation in the lister class

從上圖中我么你可以看到示例代碼中的依賴關系默刚,MovieLister依賴MovieFinlder接口和其實現(xiàn)ColonDelimitedMovieFinder甥郑。我們更喜歡讓它只依賴接口,但是怎么才能創(chuàng)建出合適的對象呢荤西?

根據(jù)上述描述澜搅,finder的實現(xiàn)不能再編譯期就連接到lister中,因為我并不知道我的朋友使用哪種方式來獲取數(shù)據(jù)邪锌。此外勉躺,我還希望我的lister可以使用所有的finder實現(xiàn)類而且這個實現(xiàn)將會在以后的某個點插入到lister中。那么觅丰,現(xiàn)在的問題就是饵溅,我應該如何創(chuàng)建這種連接使lister在finder對象實現(xiàn)一無所知的情況下還可以與finder實例對象對話以完成其工作。

將其擴展到一個真實系統(tǒng)中妇萄,我們肯能會有很多這樣的服務或組件概说。在每一種情況下,我們都可以將使用到的組件抽象出接口嚣伐,然后通過接口來交互(如果組件不是面向接口開發(fā)的糖赔,就使用適配器來進行適配,進而通過適配器進行交互)轩端。但是如果我們想要以不同方式部署系統(tǒng)放典,就需要使用插件來處理與服務的交互,這樣我們就可以在不同的發(fā)布環(huán)境中使用不同的實現(xiàn)了基茵。

所以這個核心問題就是如何把這些插件裝配到應用中呢奋构?這是新型輕量級容器面臨的主要問題之一,通常他們都是通過Inversion of Control(控制反轉)來實現(xiàn)的拱层。

Inversion of Control

有些容器說自己是很有用的弥臼,原因竟然是因為他們實現(xiàn)了IOC,這讓我困惑不已根灯。IOC是框架的共有屬性径缅,所以說輕量級容器因為使用了IOC就變得特殊了,就像說我的汽車之所以不一樣烙肺,是因為它有輪子纳猪,這聽起來就挺滑稽的。

現(xiàn)在的問題是:什么方面的控制被反轉了桃笙?當我第一次遇到IOC時氏堤,它出現(xiàn)在一個用戶交互的控制界面中。早期的用戶界面是由應用程序控制的搏明,你將會看到一系列的指令鼠锈,如“輸入用戶名”闪檬,“輸入地址”;這個應用程序將會給出對應的提示购笆,并對你的輸入給出響應谬以。使用圖形化的或者基于屏幕的UI框架,框架包含對某事件的主循環(huán)過程由桌,而應用程序提供對屏幕各個字段的事件處理器为黎。在這里,對程序的主要控制被轉了行您,從你轉移到了框架铭乾。簡而言之,用戶只需要提供屬性信息娃循,處理過程交給了UI框架炕檩,比如用戶只需要輸入用戶名和密碼,點擊登錄即可捌斧,至于其中的用戶名重復校驗等等交給UI框架自動去完成笛质,或者請求提交這個操作,不是由人敲回車實現(xiàn)交互了捞蚂,而是交由UI框架去執(zhí)行這個交互過程妇押。

對于這種新型的容器,反轉指的是如何查找一個接口實現(xiàn)姓迅。在上面的小例子中敲霍,lister對象通過直接實例化finder對象實現(xiàn)查找。這樣的話丁存,finder對象就不可插拔了肩杈。這些容器要求接口使用者遵循一些規(guī)定,從而可以將一些分離的模塊實現(xiàn)注入到lister對象中解寝。

因此扩然,我們需要一個更加具體的名字來命名這個模式。IOC是一個太通用的術語聋伦,這讓人感到困惑夫偶。最后經(jīng)過IOC擁護者的大量討論,最后決定使用Dependency Injection(依賴注入)這個名字來命名該模式嘉抓。

接下來我們要講的是依賴注入的各種實現(xiàn)形式索守,但是我要指出的是這并不是從應用程序中移除依賴轉換為插件式的實現(xiàn)的唯一方式晕窑。另外一種可以實現(xiàn)這一點的是Service Locator,在介紹完DI之后抑片,我將會繼續(xù)介紹Service Locator。

Forms of Dependency Injection

依賴注入的基本思想是有一個單獨的對象(一個裝配器)杨赤,它可以從finder接口的實現(xiàn)中選擇一個合適的對象填充從到lister對象的finder屬性中敞斋,依賴圖如下:

主要有三種注入方式:Contructor Injection(構造器注入),Setter Injection(set方法注入),Interface Injection(接口注入)截汪。如果你以前閱讀過IOC的介紹,你可能會聽說過type 1控制反轉(interface injection),type 2控制反轉(setter injection)和type 3控制反轉(constuctor injection)植捎。數(shù)字類型的名字不易區(qū)分衙解,所以我這里使用更有表征意義的名字。

Constructor Injection with Spring

Spring是一個應用廣泛的企業(yè)級框架焰枢,它包括事務抽象層蚓峦,持久層框架,web應用開發(fā)和JDBC等济锄。它支持構造方法和setter方法注入暑椰。

下面介紹一下Spring使用構造方法來實現(xiàn)將finder實現(xiàn)注入到lister類中。所以荐绝,MovieLister類需要聲明一個構造方法一汽,它包含所需要注入的所有東西。

class MovieLister...

public MovieLister(MovieFindler findler) {
    this.findler = findler;
}

finder類本身也需要被Spring管理低滩,text文件名也可以使用這個容器文件注入召夹。

public class ColonDelimitedMovieFinder implements MovieFindler {
    private String fileName;

    public ColonDelimitedMovieFinder(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public List<Movie> findAll() {
        return null;
    }
}

然后,Spring需要被告知哪個接口和哪個實現(xiàn)類關聯(lián)以及需要把哪個字符串注入到finder類恕沫。換句話說监憎,我們需要告訴容器誰需要注入,至于如何注入婶溯,由容器去完成枫虏。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="movieLister" class="com.springframework.roadmap.ioc.demo_00002.MovieLister">
        <constructor-arg ref="movieFinder"/>
    </bean>

    <bean id="movieFinder" class="com.springframework.roadmap.ioc.demo_00002.ColonDelimitedMovieFinder">
        <constructor-arg value="F:\SourceCode\SpringRoadMap\src\test\resources\movie1.txt"/>
    </bean>
</beans>
public void testConstructorInjectWithSpring() {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\SourceCode\\SpringRoadMap\\src\\main\\resources" +
            "\\applicationContext.xml");
    MovieLister movieLister = (MovieLister) ctx.getBean("movieLister");
    Movie[] movies = movieLister.moviesDirectedBy("yoyo");

    System.out.println("===============" + "The Big Bang".equals(movies[0].getTitle()) + "===============");
}

我們會在另一個類中增加配置代碼,使用MovieLister類的朋友可以在配置類中編寫適合自己的配置代碼爬虱。當然隶债,通常會把這些配置信息以單獨的配置文件的形式保存。你可以寫一個類來讀取這個配置文件并在恰當?shù)臅r候把這些信息配置到容器中跑筝。Spring項目的設計哲學之一就是分離配置文件與底層實現(xiàn)機制.

Setter Injection with Spring

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="movieLister" class="com.springframework.roadmap.ioc.demo_00003.MovieLister">
        <property name="movieFinder">
            <ref bean="movieFinder"/>
        </property>
    </bean>

    <bean id="movieFinder" class="com.springframework.roadmap.ioc.demo_00003.ColonDelimitedMovieFinder">
        <property name="fileName">
            <value>F:\SourceCode\SpringRoadMap\src\test\resources\movie1.txt</value>
        </property>
    </bean>
</beans>
private static void testSetterInjectWithSpring() {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\SourceCode\\SpringRoadMap\\src\\test\\resources\\applicationContext_00003.xml");
    MovieLister movieLister = (MovieLister) ctx.getBean("movieLister");
    Movie[] movies = movieLister.moviesDirectedBy("yoyo");
    System.out.println("===============" + "The Big Bang".equals(movies[0].getTitle()) + "===============");

}

Interface Injection

這個不做具體展開死讹,想要進一步了解的可以看下文末的參考資料。

對于使用者而言曲梗,無論是constructor注入還是setter注入赞警,使用上并沒有區(qū)別,其依賴注入解析過程如下:

  • 創(chuàng)建ApplicaionContxt并根據(jù)配置的元數(shù)據(jù)(文中applicationContext_*.xml)初始化bean虏两。配置元素數(shù)據(jù)可以使用XML愧旦、Java Code或者注解。
  • 對于每個bean定罢,它的依賴可以使用屬性笤虫、構造參數(shù)或者靜態(tài)工廠方法參數(shù)來表達。當這個bean被創(chuàng)建的時候,它所依賴的bean會被自動創(chuàng)建
  • 每個屬性或構造參數(shù)是對需要在容器中實際設置的另一個bean的值或引用的定義
  • 對于每個值類型的屬性或構造參數(shù)可以從某種特殊的格式轉化為其實際的類型琼蚯。在Spring容器中酬凳,默認將字符串格式的值轉換為內(nèi)置類型,如int遭庶、long宁仔、String、boolean等等峦睡。

Using a Service Locator

文中使用依賴注入最重要的好處就是它移除了MovieLister類對具體MovieFindler實現(xiàn)的依賴翎苫。這樣我就可以把這個MovieLister類提供給我的朋友,他們就可以在自己的環(huán)境中插入合適的實現(xiàn)(修改配置文件)榨了。依賴注入并不是打破這種依賴的唯一方式拉队,另外一種就是使用Service Locator(服務定位器)。

Service Locator的核心思想就是有一個對象阻逮,它知道如何獲取應用所需要的所有服務粱快。所以,一個應用的Service Locator有一個方法可以返回MovieLister需要的MovieFinder對象叔扼。當然事哭,這只是稍微減輕了負擔,我們?nèi)匀槐仨氁獙ervice Locator注入到MovieLister瓜富,所以依賴圖如下:

和注入方法一樣鳍咱,我們必須配置service locator。下面直接使用代碼實現(xiàn)配置与柑,也可以使用配置文件或注解形式谤辜。

public class ServiceLocator {
    private MovieFindler movieFindler;
    private static ServiceLocator soleInstance;

    public static MovieFindler movieFinder() {
        return soleInstance.movieFindler;
    }

    public static void load(ServiceLocator arg) {
        soleInstance = arg;
    }

    public ServiceLocator(MovieFindler movieFindler) {
        this.movieFindler = movieFindler;
    }

}
public class MovieLister {
    private MovieFindler movieFinder = ServiceLocator.movieFinder();


    public Movie[] moviesDirectedBy(String args) {
        List<Movie> allMovies = movieFinder.findAll();
        for (Iterator<Movie> it = allMovies.iterator(); it.hasNext(); ) {
            Movie movie = it.next();
            if (!(args).equals(movie.getDirector())) {
                it.remove();
            }
        }
        return allMovies.toArray(new Movie[allMovies.size()]);
    }

}
private static void configure() {
    ServiceLocator.load(new ServiceLocator(new ColonDelimitedMovieFinder("F:\\SourceCode\\SpringRoadMap" +
            "\\src\\test\\resources\\movie1.txt")));
}

private static void testServiceLocator(){
    configure();
    MovieLister movieLister = new MovieLister();

    Movie[] movies = movieLister.moviesDirectedBy("yoyo");
    System.out.println("===============" + "The Big Bang".equals(movies[0].getTitle()) + "===============");
}

如上,使用了靜態(tài)的單例對象做為ServiceLocator价捧,從其中獲取movieFindler丑念。顯然,這個ServiceLocator實現(xiàn)并不可以動態(tài)獲取movieFindler结蟋,就是直接返回movieFinder屬性的脯倚。并不建議這樣設計,文中只是為了方便測試嵌屎。

一個更高級的或者說更加優(yōu)秀的服務定位器可以設計service locator子類并將該子類傳遞到注冊表類的屬性變量中推正。將ServiceLocator直接調(diào)用movieFinder屬性的方法修改為調(diào)用靜態(tài)方法,可以一個ThreadLocal變量來存儲所有的子類宝惰。這樣并不修改使用該SeviceLocator的客戶端植榕,而且是線程相關,可以根據(jù)需求使用對應的ServiceLocator實現(xiàn)尼夺。

public class ServiceLocator {
    private MovieFindler movieFindler;
    private static ThreadLocal<ServiceLocator> threadLocal = new ThreadLocal<ServiceLocator>();

    public static MovieFindler movieFinder() {
        return getMovieFinder();
    }

    public static void load(ServiceLocator arg) {
        threadLocal.set(arg);
    }

    public ServiceLocator(MovieFindler movieFindler) {
        this.movieFindler = movieFindler;
    }

    public static MovieFindler getMovieFinder() {
        return threadLocal.get().movieFindler;

    }

}
private static void configure() {
    ServiceLocator.load(new SubServiceLocator(new ColonDelimitedMovieFinder("F:\\SourceCode\\SpringRoadMap" +
            "\\src\\test\\resources\\movie1.txt")));
}

從上面代碼可以看到尊残,只是修改了配置configure炒瘸,客戶端調(diào)用并不需要修改。而且使用ThreadLocal實現(xiàn)了類似注冊表的機制夜郁,這樣該線程后續(xù)使用的就是該注冊的Service Locator什燕。

Using a Segregated Interface for the Locator

細心的的讀者可能會發(fā)現(xiàn)粘勒,MovieLister需要依賴一個完整的服務器定位類竞端,而你可能只需要其中一個服務。我們可以使用role interface來減少服務依賴庙睡,換句話說事富,MovieLister可以聲明它需要用到的服務,而不用使用全部的服務定位器接口乘陪。

在這種情形下统台,我們可以聲明一個定位接口,它提供一個獲取finder的方法啡邑。

public interface MovieFindlerLocator {
    MovieFindler movieFindler();
}
public class ServiceLocator implements MovieFindlerLocator {
...
    @Override
    public MovieFindler movieFindler() {
        return getMovieFinder();
    }
}
public class MovieLister {
    private MovieFindler movieFinder ;
    public void setMovieFindlerLocator(MovieFindlerLocator movieFindlerLocator) {
        this.movieFinder = movieFindlerLocator.movieFindler();
    }
...

}

讀者可以看到贱勃,因為我們使用了接口,我們不可以再通過靜態(tài)方法來訪問服務谤逼。我們不惜獲取一個locator實例贵扰,然后通過它來獲取我們需要的。

A Dynamic Service Loctor

以上的例子都是靜態(tài)的流部,通過一個service locator提供的方法獲取我們需要的數(shù)據(jù)戚绕。顯然,還有其他方式可以用枝冀,你可以編寫一個動態(tài)的service locator舞丛,它可以允許你存儲你需要的任何服務,而且你可以在運行時指定你需要哪一個果漾。

在下面的例子中球切,service locator提供使用map來存儲每一個服務,而不是使用一個屬性绒障,并且提供獲取服務的通用方法欧聘。

public class ServiceLocator {
    private Map services = new ConcurrentHashMap();
    private MovieFindler movieFindler;

    private static ServiceLocator soleInstance;

    public static Object getService(String key) {
        return soleInstance.services.get(key);
    }


    public static void loadService(String key, Object value) {
        soleInstance.services.put(key, value);
    }

    public static void load(ServiceLocator locator) {
        soleInstance = locator;
    }

}


private static void configure_2() {
    ServiceLocator locator = new ServiceLocator();
    ServiceLocator.load(locator);

    ServiceLocator.loadService("MovieFindler", new ColonDelimitedMovieFinder("F:\\SourceCode\\SpringRoadMap" +
            "\\src\\test\\resources\\movie1.txt"));

}


public class MovieLister {
    private MovieFindler movieFinder = (MovieFindler) ServiceLocator.getService("MovieFindler");
...
}

總的來說,上面這個例子提供了一種獲取服務的新思路端盆,但是需要通過一個String類型的key來獲取服務怀骤,這顯然不夠標準。我更希望使用嚴格的方法定義來獲取服務焕妙,因為這個很容易從接口定義中查找想要的服務蒋伦。

Deciding which option to use

現(xiàn)在我已經(jīng)介紹了IOC模式及其變種,現(xiàn)在我們開始討論它們的優(yōu)缺點焚鹊,進而幫助我們判斷什么時候應該使用哪種實現(xiàn)痕届。

Service Locator vs Dependency Injection

兩者都提供了解耦功能韧献,面向接口開發(fā),具體實現(xiàn)與接口分離研叫。兩者主要的不同在于锤窑,提供給應用的使用方式。使用Service Locator時嚷炉,應用需要顯式的通過locator定位服務渊啰,而DI并不需要顯式的請求,而是利用容器來實現(xiàn)服務注入進而實現(xiàn)了控制反轉申屹。

IOC是框架的一個通用特性绘证,但是在獲取IOC有點的同時也需要付出對應的代價。尤其是當你debug時很難理解并且找到問題產(chǎn)生的原因哗讥。所以嚷那,我們在使用時要根據(jù)自身需求,審慎的選擇杆煞。

兩者之間的一個關鍵不同是每一個應用都對Service Locator有依賴性魏宽,但是Service Locator隱藏了對其他實現(xiàn)的依賴。所以兩者之間的選擇取決于依賴是不是一個問題决乎。

使用DI可以幫助我們更容易看到組件的依賴是什么队询,現(xiàn)在的IDE可以很容易查找DI的依賴是什么并找到這些依賴。但是瑞驱,如果應用需要的東西都可以通過一個或幾個服務來完成娘摔,那么依賴就不是問題,DI的也就沒什么優(yōu)勢了唤反。

因為有了DI容器凳寺,你可以不必處理組件到容器的依賴關系,但是一旦配置好容器彤侍,組件就不能從容器中獲取進一步的服務肠缨。

Constructor versus Setter Injection

當我們需要組合使用一些服務的時候,為了把他們組裝到一起盏阶,我們需要制定一些規(guī)則晒奕。DI的優(yōu)勢在于它的規(guī)則很簡單,比如coustructor和setter注入名斟,你不必去讓組件做一些古怪的事情而且它的配置很簡單脑慧。與之相對的接口注入,則并不贊成使用砰盐,使用這種方式的注入闷袒,需要制定過多的接口,且不易維護岩梳。

constructor和setter注入反映出面向對象編程的一個普遍問題囊骤,你需要通過setter或constructor填充屬性晃择。使用constructor構造注入可以在一開始就制定需要使用的屬性,封裝性更好也物。相對而言宫屠,setter過多則顯得冗余,但是參數(shù)過多的構造函數(shù)注入也是很恐怖的滑蚯,這個時候就需要把對象進行拆分了浪蹂。

構造函數(shù)注入依賴參數(shù)的未知,如果都是相同類型膘魄,則易出錯乌逐,在這一方面竭讳,setter注入就沒有這個困擾创葡,注入更加具有指向性。所以绢慢,現(xiàn)在的大部分框架兩種注入都支持的灿渴,比如Spring,根據(jù)需求選擇合適的注入方式即可胰舆。

自動注入VS手動注入

Code or configuration files

有一個常見的問題:1.使用配置文件來裝配服務 2.使用代碼API裝配服務骚露。因為大部分應用可能被發(fā)布到不同場景,一個分離的配置文件還是很有意義的缚窿。很多時候這可能是xml文件棘幸,但是有些場景下直接使用代碼來裝配更容易。當我們的應用比較簡單而且發(fā)布場景也比較單一的情況下倦零,使用代碼比XML文件更清晰自然误续。

尤其在裝配時需要根據(jù)條件裝配不同的對象時,使用代碼比使用XML文件指定依賴更容易且更易維護扫茅。當然也可以使用一個簡單的XML配置文件,然后編寫創(chuàng)建類,根據(jù)配置文件來選擇如何裝配崖瞭。

很多時候驱证,人們總是急于定義配置文件。其實恋脚,一般來說腺办,編程語言可以制定簡單而強大的配置機制。現(xiàn)代編程語言可以容易的編寫裝配器來把插件裝配到大型系統(tǒng)糟描,如果編譯是個問題怀喉,我們還可以使用腳本語言,如python等蚓挤。

有一個不和諧的問題磺送,那就是每個組件維護一套自己的配置文件驻子,而使用者需要保持這些配置文件的同步正確,這很讓人困擾估灿。

我的建議是使用編程接口來進行配置崇呵,另外,再提供一個配置文件來關聯(lián)編程接口馅袁,這樣我們就可以進行簡單的配置就可以實現(xiàn)功能域慷。在設計層面,SpringBoot簡化依賴及構建過程的思想及去配置化的思路有異曲同工之妙汗销。

Separating Configuration from Use

配置與業(yè)務邏輯分離這與面向接口開發(fā)的OO思想殊途同歸犹褒,這樣更易解耦與擴展。使用多態(tài)而不是條件判斷來決定實例化哪一個對象弛针。

配置機制可以用來配置Service Locator或使用DI方式來直接配置對象叠骑。配置機制可以讓應用適配不同的組件,就像一個適配器或插件削茁,讓我們的應用更易組合不同組件并適配不同的環(huán)境宙枷。

Concluding Thoughts

當下的輕量級容器底層的原理多是有一個共性:如何裝配服務/組件(即依賴注入模式)。

相對而言茧跋,Service Locator指向性更加明確慰丛,但是如果開發(fā)的組件是供不同應用使用的情況下,DI則更合適瘾杭。

最重要的是將服務/組件的配置與服務/組件的使用分離的原則及思想诅病,這比在DI和Service Locator之間選擇更有價值。

Spring 部分依賴圖

image

PS:
Spring處理bean步驟:

  • 分離出配置文件
  • 讀取配置文件
  • 根據(jù)配置文件生成對象工廠
  • 使用對象時粥烁,從工廠中认桶省(注解或XML聲明實現(xiàn))而無需自己去new
  • 對象都是由容器在啟動時或使用時給new好了,直接聲明使用(注解或XML文件配置)页徐,無需自己new
    Spring核心思想:
  • 對于一個Java Bean來說苏潜,如果你依賴別的Bean ,只需要聲明(注解或XML配置)即可,spring 容器負責把依賴的bean 給“注入進去“变勇, 這就是控制反轉(IoC)
  • 如果一個Java Bean需要一些像事務恤左,日志,安全這樣的通用的服務搀绣,也是只需要聲明(注解或XML配置)即可飞袋, spring 容器在運行時能夠動態(tài)的“織入”這些服務, 這就是AOP

參考書籍:
Expert One-on-One J2EE Design and Development
Expert one on one J2EE development withoutEJB

參考資料

https://martinfowler.com/articles/injection.html

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末链患,一起剝皮案震驚了整個濱河市巧鸭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌麻捻,老刑警劉巖纲仍,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀袱,死亡現(xiàn)場離奇詭異,居然都是意外死亡郑叠,警方通過查閱死者的電腦和手機夜赵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乡革,“玉大人寇僧,你說我怎么就攤上這事》邪妫” “怎么了嘁傀?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長视粮。 經(jīng)常有香客問我细办,道長,這世上最難降的妖魔是什么馒铃? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任蟹腾,我火速辦了婚禮痕惋,結果婚禮上区宇,老公的妹妹穿的比我還像新娘。我一直安慰自己值戳,他們只是感情好议谷,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堕虹,像睡著了一般卧晓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赴捞,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天逼裆,我揣著相機與錄音,去河邊找鬼赦政。 笑死胜宇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的恢着。 我是一名探鬼主播桐愉,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掰派!你這毒婦竟也來了从诲?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤靡羡,失蹤者是張志新(化名)和其女友劉穎系洛,沒想到半個月后俊性,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡描扯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年磅废,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荆烈。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡拯勉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憔购,到底是詐尸還是另有隱情宫峦,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布玫鸟,位于F島的核電站导绷,受9級特大地震影響,放射性物質發(fā)生泄漏屎飘。R本人自食惡果不足惜妥曲,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钦购。 院中可真熱鬧檐盟,春花似錦、人聲如沸押桃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唱凯。三九已至羡忘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磕昼,已是汗流浹背卷雕。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留票从,地道東北人漫雕。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像纫骑,于是被迫代替她去往敵國和親蝎亚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容