依賴注入與JSR-330的參考實現——Guice

gww3 9年前發布 | 58K 次閱讀 Guice 面向方面AOP/IoC

原文出處: wwsun.me

依賴注入(控制反轉的一種形式),它是Java開發主流中一個重要的編程范式(思維方式)。簡單的說,使用DI技術可以讓對象從別處獲得依賴項,而不是由它自己來構造。使用DI有很多好處,它能降低代碼之間的耦合度,讓代碼更易于測試、更易讀。Java DI的官方標準是JSR-330,本文我們還會介紹JSR-330的參考實現Guice 3,一個輕量級 的DI框架。

IoC and DI

使用IoC范式編程時,程序邏輯的流程通常是由一個功能中心來控制的。而使用IoC,這個“中心控制”的設計原則會被反轉過來。調用者的代碼處理程序的執行順序,而程序邏輯則被封裝在接受調用的子流程中。通過一個例子來理解IoC:

在GUI應用中,GUI框架負責控制調用事件處理器,而不是應用邏輯。當用戶點擊了一個動作,比如“向前”,GUI框架會自動調用 對應的事件處理器,而應用邏輯可以把重點放在處理動作上。程序的控制被反轉了,將控制權由應用邏輯轉移到了GUI框架。

IoC也被稱為好萊塢原則:會有另一段代碼擁有最初的控制線程,并且由它來調用你的代碼,而不是由你的代碼調用它。

不要給我們打電話,我們會打給你。——好萊塢原則

IoC有多種不同的實現,包括工廠模式、服務器定位模式,當然還有依賴注入。需要注意的是,DI并不等于IoC,DI只是IoC的一種實現方式,IoC是一種機制。

DI是IoC的一種特定形態,是指尋找依賴項的過程不在當前執行代碼的直接控制之下。Java中為DI提供的容器有Guice、Spring、PicoContainer等。DI的好處有:松耦合、易測試、強內聚、可重用、更輕盈的代碼。

Example

下面編寫代碼來解釋到底什么是DI,如何使用DI。我們首先編寫傳統的代碼,然后使用工廠模式解耦,進而再使用DI來改進代碼,通過這個過程,你將了解到DI的精妙之處。這些改進都基于同一個關鍵技術,即面向接口編程。

假設你想找到所有對Java可開發人員比較友善的好萊塢經紀人。首先,我們有了一個AgentFinder接口,及其兩個實現類SpreadSheetAgentFinder和WebServiceAgentFinder。AgentFinder接口如下:

public interface AgentFinder {
    public List<Agent> findAllAgents();
}

傳統方式尋找友善經紀人

為了找到經紀人,項目中有個默認的HollywoodService類,它會從SpreadSheetAgentFinder里得到一個經紀人列表,并且過濾出友善的經紀人,最終返回該列表。

public class HollywoodService {
    public static List<Agent> getFriendlyAgents() {
        AgentFinder finder = new SpreadsheetAgentFinder();
        List<Agent> agents = finder.findAllAgents();
        List<Agent> friendlyAgents = filterAgents(agents,"Java Developers");

    return friendlyAgents;
}
//filterAgents

}</pre>

這是最傳統的編碼方式,顯然,HollywoodService被SpreadsheetAgentFinder這個AgentFinder的具體實現死死的綁定住了。

為了改進這個問題,通常我們會想到一個常用的設計模式——工廠模式。工廠模式可以一定程度上解耦程序,它也是IoC的一種實現方式。

工廠模式

利用工廠模式(其實這里用到的是一個簡單工廠)重新編寫上面的代碼,如下:

public List<Agent> getFriendlyAgents(String agentFinderType) {
    AgentFinderFactory factory = AgentFinderFactory.getInstance();
    AgentFinder finder = factory.getAgentFinder(agentFinderType);
    List<Agent> agents = finder.findAllAgents();
    return filterAgents(agents, "Java Developers");
}

如你所見,這里不再有具體的實現來黏住你,而是通過注入agentFinderType的方式讓你選擇想要的AgentFinder。但這里仍然還有問題:

  1. 代碼注入的僅僅是一個引用憑據(agentFinderType),而不是真正實現AgentFinder的對象
  2. 方法getFriendlyAgents中還有獲取其依賴的代碼,達不到只關注自身智能的理想狀態

我們需要通過DI來達成這兩個目標。

手工實現DI

public static List<Agent> getFriendlyAgents(AgentFinder finder){
    List<Agent> agents = finder.findAllAgents();
    return filterAgents(agents,"Java Developers");
}

上面的代碼是一個純手工打造的DI方案,AgentFinder被直接注入到getFriendlyAgents方法中。現在這個getFriendlyAgents方法干凈利落,只專注于純業務邏輯。

但是,這種手工方式的DI顯然存在問題,如何配置AgentFinder具體實現的問題并沒有解決,原本AgentFinderFactory要做的工作還是要找一個地方去做。解決這個問題的方式是借助DI框架,而DI框架就是把你的代碼打包起來的運行時環境,在你需要的時候注入依賴項。

使用Guice

Java中DI方面的標準規范是JSR-330,它提供了統一的DI通用功能的標準,而且提供了你需要了解的幕后規則及限制。這里不具體介紹JSR-330,有興趣的可以查找相關資料閱讀。我們重點介紹JSR-330的參考實現Guice,它是一個由Google實現的針對Java 6以上版本的流行的、輕量級的DI框架。

下面我們使用Guice來解決手工方式實現DI的不足問題:

首先我們需要創建一個定義綁定關系的AgentFinderModule模塊(配置類)。

public class AgentFinderModule extends AbstractModule{
    @Override
    protected void configure() {
        bind(AgentFinder.class).to(WebServiceAgentFinder.class);
    }
}

綁定關系的確立在調用Guice的bind方法時發生,把要綁定的類(AgentFinder)傳給它,然后調用to方法指明要注入到哪個實現類。

下面我們來Guice版本的HollywoodService:

public class HollywoodServiceGuice {
    private AgentFinder finder = null;

    @Inject
    public HollywoodServiceGuice(AgentFinder finder){
        this.finder = finder;
    }

    public List<Agent> getFriendlyAgents(){
        List<Agent> agents = finder.findAllAgents();
        return filterAgents(agents, "Java Developers");
    }

    //...
}

我們在模塊中聲明了綁定關系,下面我們就可以讓注入器構建對象關系圖了。接下來我們看看在獨立Java程序和Web應用程序這兩種情況下分別要如何實現:

1.構建Guice對象關系圖——獨立Java程序

public class HollywoodServiceClient {
     public static void main(String[] args) {
         Injector injector = Guice.createInjector(new AgentFinderModule());
         HollywoodServiceGuice hollywoodServiceGuice = injector.getInstance(HollywoodServiceGuice.class);
         List<Agent> agents = hollywoodServiceGuice.getFriendlyAgents();
     }
 }

2.構建Guice對象關系圖——Web應用程序 把Gucie-servlet.jar加到web應用的類庫中,然后再web.xml中添加下面的配置項:

 <filter>
     <filter-name>guiceFilter</filter-name>
     <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
   </filter>

   <filter-mapping>
     <filter-name>guiceFilter</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>

3.然后是標準動作,擴展ServletContextListener以便使用Guice的ServeltModule:

public class MyGuiceServletConfig extends GuiceServletContextListener {

   @Override
   protected Injector getInjector() {
     return Guice.createInjector(new ServletModule());
   }
 }

4.最好,將其加入到web.xml文件中,以便servlet容器在部署應用時觸發該類:

<listener>
   <listener-class>com.example.MyGuiceServletConfig</listener-class>
 </listener>

經由注入器創建HollywoodServiceGuice,你得到一個配置完備的類,馬上就可以調用其中的getFriendlyAgents方法了。

關于JSR-330和Guice的更多用法,請參考其Github頁面

References

  1. https://github.com/google/guice/wiki/GettingStarted
  2. The Well-Grounded Java Developer, chapter 3, Dependency Injection
 本文由用戶 gww3 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!