依賴注入,DI(Dependency Injection),它的作用自然不必多說,提及DI容器,例如spring,picoContainer,EJB容器等等,近日,google誕生了更輕巧的DI容器……Guice!
廢話不多講了,先看看Guice是如何實現注入的吧。
定義一個簡單的service接口和它的實現吧!
public interface MyService ...{
void myMethod();
}
public class MyServiceImpl implements MyService ...{
public void myMethod() ...{
System.out.println("Hello,World!");
}
}
以上是最普通的接口和其實現,沒什麼可說的。
定義一個測試類,這個類裡邊包括service對象的一個引用,這個對象是需要Guice進行注入的。
import com.google.inject.Inject;
public class Client ...{
MyService service;
@Inject //告訴容器,這裡的service對象的引用,需要進行注入
void setService(MyService service) ...{ //這裡的方法名字可以任意定義
this.service = service;
}
public void myMethod() ...{
service.myMethod();
}
}
這裡除了加了一個@Inject,和Spring的配置沒有任何的區別,@Inject,是表示對容器說,這裡的service需要注射,等到運行的時候,容器會拿來一個實例給service,完成注射的過程。
定義Guice的Module文件,告訴容器如何進行注入。
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
public class MyModule implements Module ...{
public void configure(Binder binder) ...{
binder.bind(MyService.class).to(MyServiceImpl.class).in(Scopes.SINGLETON);
// 這句代碼的意思是說:運行時動態的將MyServiceImpl對象賦給MyService定義的對象,
而且這個對象是單例的。
}
}
創建測試類
import com.google.inject.Guice;
import com.google.inject.Injector;
public class Test ...{
public static void main(String[] args) ...{
MyModule module = new MyModule();// 定義注射規則
Injector injector = Guice.createInjector(module);// 根據注射規則,生成注射者
Client client = new Client();
injector.injectMembers(client);// 注射者將需要注射的bean,按照規則,把client這個客戶端進行注射
client.myMethod();
}
}
運行測試類,控制台輸出:Hello,World!
完成注入過程
下面看看Guice還有哪些其它的使用特性。
1、如果在實現你確定MyService定義的對象,就要被注射為MyServiceImpl而不是其它的實現類的話,可以在MyService接口加上@ImplementedBy(MyServiceImpl.class)。
import com.google.inject.ImplementedBy;
@ImplementedBy(MyServiceImpl.class)
//我總覺得這樣有點背離了依賴注入的初衷了
public interface MyService ...{
void myMethod();
}
這樣的話,在MyModule裡的configure方法中就可以不加任何東西,容器就會自動注射給MyServiceImpl對象。
2、可以對Field進行注解式注入
在Client.java中也可以把這個@Inject標注在MyService service;的前邊,如:@Inject MyService service;
3、可使用自定義Annotation標注。
package mypackage;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
@Retention(RetentionPolicy.RUNTIME)
@Target( ...{ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface MyInterface ...{
}
那麼Client.java需要改為:
package mypackage;
import com.google.inject.Inject;
public class Client ...{
@Inject @MyInterface MyService service;
void setService(MyService service) ...{ // 這裡的方法名字可以任意定義
this.service = service;
}
public void myMethod() ...{
service.myMethod();
}
}
MyModule.java中的configure方法內容需改為:
binder.bind(MyService.class).annotatedWith(MyInterface.class).to(
MyServiceImpl.class).in(Scopes.SINGLETON);
意思是說對於標注為MyInterface的MyService定義的對象進行注入。
進行Annotation標注的成員(Field,method,argument等)進行自定義Annotation標注,該成員既擁有該屬性,可以在運行,根據這些成員的不同屬性,做一些不同的事情,例如:spring的AspectJ,xdoclet等都是如此。
下邊是我做了一下對比:
Spring
Guice
使用XML
使用將類與類之間的關系隔離到xml中,由容器負責注入被調用的對象,因此叫做依賴注入
不使用xml,將類與類之間的關系隔離到Module中,聲名何處需要注入,由容器根據Module裡的描述,注入被調用的對象。
使用Annotation
使用
支持自定義Annotation標注,對於相同的接口定義的對象引用,為它們標注上不同的自定義Annotation注釋,就可以達到同一個類裡邊的同一個接口的引用,注射給不同的實現,在Module裡用標注做區分,靈活性大大增加。
使用Annotation也未必是好事,范型等新特性也未必是好事,目前大多的服務器均不支持jdk1.5,wls要9以前才支持,而目前的客戶由於價格原因也很少選用wls9的,至少我們做過的項目中都沒有。功能再強,客戶不需要,何用?
運行效率
裝載spring配置文件時,需解析xml,效率低,getBean效率也不高,不過使用環境不會涉及到getBean,只有生產環境的時候會用到getBean,在裝載spring應用程序的時候,已經完成全部的注射,所以這個低效率的問題不是問題。
使用Annotation,cglib, 效率高與spring最明顯的一個區別,spring是在裝載spring配置文件的時候把該注入的地方都注入完,而Guice呢,則是在使用的時候去注射,運行效率和靈活性高。
類耦合度
耦合度低,強調類非侵入,以外部化的方式處理依賴關系,類裡邊是很干淨的,在配置文件裡做文章,對類的依賴性極低。
高,代碼級的標注,DI標記@inject侵入代碼中,耦合到了類層面上來,何止侵入,簡直侵略,代碼耦合了過多guice的東西,大大背離了依賴注入的初衷,對於代碼的可維護性,可讀性均不利
類編寫時
需要編寫xml,配置Bean,配置注入
只需聲明為@inject,等著被注入,
最後在統一的Module裡聲明注入方式
僅支持IOC
否,spring目前已經涉獵很多部分
是,目前僅僅是個DI容器
是否易於代碼重構
統一的xml配置入口,更改容易
配置工作是在Module裡進行,和spring異曲同功
支持多種注入方式
構造器,setter方法
Field,構造器,setter方法
靈活性
1,如果同一個接口定義的引用需要注入不同的實現,就要編寫不同的Module,煩瑣
2,動態注入
如果你想注射的一個實現,你還未知呢,怎麼辦呢,spring是沒辦法,事先在配置文件裡寫死的,而Guice就可以做到,就是說我想注射的這個對象我還不知道注射給誰呢,是在運行時才能得到的的這個接口的實現,所以這就大大提高了依賴注射的靈活性,動態注射。
與現有框架集成度
1,高,眾多現有優秀的框架(如struts1.x等)均提供了spring的集成入口,而且spring已經不僅僅是依賴注入,包括眾多方面。
2,Spring也提供了對Hibernate等的集成,可大大簡化開發難度。
3,提供對於orm,rmi,webservice等等接口眾多,體系龐大。
1,可以與現有框架集成,不過僅僅依靠一個效率稍高的DI,就想取代spring的地位,有點難度。
配置復雜度
在xml中定位類與類之間的關系,難度低
代碼級定位類與類之間的關系,難度稍高
再借斧子的例子說一說spring與guice的區別。
看下邊對於不同社會形態下一個人(java對象,調用者)需要一把斧子(java對象,被調用者)的例子:
(1)原始社會時,勞動社會基本沒有分工,需要斧子的人(調用者)只好自己去磨一把斧子,每個人擁有自己的斧子,如果把大家的石斧改為鐵斧,需要每個人都要學會磨鐵斧的本領,工作效率極低。
對應Java裡的情形是:java程序裡的調用者new一個被調用者的實例。類耦合度極高,修改維護煩瑣,效率極低。
(2)工業社會時,工廠出現,斧子不再由普通人完成,而由工廠生產,當人們需要斧子的時候,可以到工廠購買斧子,無需關心斧子是怎麼制造出來的,如果廢棄鐵斧為鋼斧,只需改變工廠的制造工藝即可,制作工藝是工廠決定的,工廠生產什麼斧子,工人們就得用什麼斧子。
對應的Java裡的情形是:Java程序的調用者可以以來簡單工廠創建被調用者,變化點被隔離到了簡單工廠裡,雖然耦合度降低,但是調用者會和工廠耦合,而且需要定位自己的工廠。
(3)近代工業社會,工廠蓬勃發展,人們需要什麼斧子,只需要提供一個斧子圖形,商家會按照你提供的圖形將你的斧子訂做好,送上門。
對應Java裡的情形:spring的依賴注入
(4)進入按需要分配社會,信息進入現代化,人們不再去工廠購買斧子,不再拘泥於需要什麼斧子事先畫好什麼樣的圖形,只需要打個電話,描述一下需要什麼類型的斧子,或許想打造一個物美價廉的斧子,商家會根據市場零件的價格,計算出最優制作工藝,打造最適合的斧子送過來,更加信息化,更加人性化。
對應Java裡的情形:基於描述的注入,動態的,靈活簡單的注入,如:Guice。
對於該不該使用Guice,我想也是仁者見仁,智者見智,就象好多論壇裡動不動有人會在那裡討論到底學Java還是學.net或者是使用eclipse還是Jbuilder的這類無聊話題,適合和滿足項目需求的,又能省工省力簡單的完成工作的,就是最好的。