轉載:http://www.cnblogs.com/youngC/archive/2012/12/21/2828419.htmlhtml
前言java
Google Guice 是一個輕量級的依賴注入框架,它支持Java 5或者更高版本的JDK,得利於Java 5中提供的泛型 (Generics) 和註釋 (Annotations) ,它可使得代碼類型安全 (type-safe) 。那麼什麼時候使用在代碼中使用 Guice 進行注入呢?通常來講,若是在你的應用代碼中業務對象 (Business Objects) 之間的關係或者依賴須要手動維護的話,你就可使用Guice 進行注入。數據庫
該文章中,首先我將經過一些例子來初步的認識一下 Guice 框架,而後我將介紹下 依賴注入框架的理論知識 以及在應用程序中使用依賴注入的好處,一樣我也會和你們探討一下 Guice 提供的用於簡化代碼的 API (包括Annotations) 。最後經過大量使用 Guice API的例子來使你們更好地理解這些API。編程
依賴注入(Dependency Injection)緩存
因爲Gucie 是一個依賴注入框架 (Dependency Injection Framework) ,所以咱們首先要很清楚依賴注入 (Dependency Injection) 是什麼概念。這些年來,依賴注入變得愈來愈流行,變得愈來愈重要,在不少典型的應用中它甚至變成了一個必需的機制,好比 J2EE 5.0, Spring, JBoss Seam就是使用依賴注入的很好的例子。如今咱們來使用一個簡單的例子來講明使用依賴注入框架的必要性。安全
請看如下代碼:app
interface Storage{ public void store(String uniqueId, Data data); public Data retrieve(String uniqueId); }
上面的接口 Storage 提供了存儲 (store) 和獲取 (retrieve) 數據的機制,因爲數據能夠存儲在數據庫中也能夠存儲在一個文件中,所以上面接口 Storage 的實現能夠以下。框架
class FileStorage implements Storage{ public void store(String uniqueId, Data data){ // Store the object in a file using Java Serialization mechanism. } public Data retrieve(String uniqueId){ // Code to retrieve the object. } }
實現類 FileStorage 能夠將數據存儲到硬盤文件中,一樣也能夠從硬盤文件中獲取存儲數據。接下來是 Storage 接口的另外一種實現,它用於將數據存儲到數據庫中。ide
class DatabaseStorage implements Storage{ public void store(String uniqueId, Data data){ // Open a connection and store the data. } public Data retrieve(String uniqueId){ // Get the data from the Database. } }
如今,咱們來看一個 Storage 應用客戶端的例子。下面的 StorageClient 代碼片斷中,首先初始化一個 FileSorage,而後在轉向 DatabaseStorage 實現。工具
public class StorageClient { public static void main(String[] args) { // Making use of file storage. Storage storage = new FileStorage(); storage.store("123", new Data()); // Making use of the database. storage = new DatabaseStorage(); storage.store("456", new Data()); } }
仔細看下 StorageClient 模塊中的代碼,儘管接口 (Storage) 和實現類 (FileStorage/DatabaseStorage) 鬆耦合,可是 客戶端 (StorageClient) 模塊須要手動地去建立實現類的實例對象 (instance) ,一樣接口和實現類之間的關係 (Relationship) 是直接在客戶端代碼中寫死的,然而在大多數狀況下,在代碼編譯的時候,客戶端應用程序已經知道須要綁定哪種接口實現類,若是隻綁定某一個具體的實現類, 確定比上面的代碼中同時實現兩個類 (某一個是不必的) 更有用。Google Guice 就是幹這個工做的,它在應用程序的客戶端代碼中建立不一樣形式服務 (Services) 實例, 並且客戶端和服務之間的依賴是經過一些簡單的配置機制 (Configuration Mechanism) 自動注入的。
接下來我將提供一個簡單使用 Guice Framework 的例子。
一個簡單的 Guice 例子
在這個簡單的例子中,讓咱們看一下 Guice 在維護不一樣對象之間的關係/依賴時如何簡化開發的。讓咱們看一下下面的代碼片斷,咱們建立了一個 Add 接口,而且在裏面定義了一個 add() 方法。
public interface Add { public int add(int a, int b); }
下面是接口 Add 的一個實現類
public class SimpleAdd implements Add{ public int add(int a, int b) { return a + b; } }
接着咱們定義一個 Module 類,這個類用於使用 Guice API 在一個應用程序中建立 Bindings。Module 和 Bindings 理論方面的詳細介紹在後面章節。如今,你只須要明白經過 Binder 類,你能夠將一些 Bindings 配置到某個 Module中。在 Guice 條目中,Binding 提供了一種方式將接口 (interface) 和實現類相關聯。
import com.google.inject.Binder; import com.google.inject.Module; public class AddModule implements Module{ public void configure(Binder binder) { binder.bind(Add.class).to(SimpleAdd.class); } }
在上面的代碼中,咱們告訴 Guice 將 SimpleAdd 實現類綁定到 Add 接口上,也就是說在客戶端調用Add.add() 方法時,實際會去執行 SimpleAdd.add() 方法。下面給出了一個客戶端例子用戶使用 Add 接口。
import com.google.inject.Guice; import com.google.inject.Injector; public class AddClient { public static void main(String[] args) { Injector injector = Guice.createInjector(new AddModule()); Add add = injector.getInstance(Add.class); System.out.println(add.add(10, 54)); } }
更多關於 Injector, Guice 的理論知識將會在後面的章節介紹。injector.getInstance(Add.class) 將會建立並返回一個 SimpleAdd 類型的實例。其實是經過 AddModule.configure() 方法來獲取具體的綁定信息的。
Guice API 探討
讓咱們探討一下實現 Guice 依賴注入不一樣的 API。特別會涉及如下的 接口/實現類。
1. Binder
Binder 接口主要是由與 Bindings 相關的信息組成的。一個 Binding 其實就是一個接口和其相應的實現類的映射關係。例如,回想一下上面的例子,咱們建立了一個由接口 Add 指向 實現類 SimpleAdd 的映射關係。
從程序角度來講,能夠經過如下代碼方式實現。注意的是不管是接口 (interface) 仍是實現類 (implementation classes),都是經過 bind() 和 to()方法實現映射的。
binder.bind(Add.class).to(SimpleAdd.class)
一樣也能夠將一個接口直接映射到一個具體的實例對象,代碼以下。
binder.bind(Add.class).to(new SimpleAdd())
第三種方式是將一個接口綁定到一個相應的 Provider 類。默認狀況下,Guice 框架會建立並返回應用程序須要的實例對象。可是,若是須要定製化一個對象建立流程(Object Creation Process),該怎麼辦? Providers 能夠很簡單的實現這種定製化。 你只須要遵循傳統的工廠模式(Factory Pattern)建立對象的方式使用 Providers,例以下面的代碼。
binder.bind(Add.class).to(new AddProvider<Add>())
後面我將會經過一些例子講解如何建立 Provider 對象。不過如今,你只須要知道在 AddProvider 類中提供了一種工廠方法,它會返回具體的 Add 實現類的實例對象。後面我一樣會講解到如何將一個接口綁定到多個具體實現上。
2. Injector
Injectors 一般會在客戶端 (Clients) 使用,它只關心如何建立 (Creating)和維護 (Maintaining) 對象(生命週期)。Injectors 會去維護一組默認的 Bindings (Default Bindings),這裏咱們能夠獲取建立和維護不一樣對象間關係的配置信息 (Configuration information)。如下代碼將會返回 Add 的實現類對象。
Add addObject = injector.getInstance(Add.class)
你能夠簡單地經過 Injector.getBindings() 方法獲取與 Injector 相關的 Bindings信息,getBindings() 方法會返回一個 Map。
Map<Key, Binding> allBindings = injector.getBindings()
這裏須要注意的是每個 Binding 一般有一個對應的 Key 對象,該對象是由 Guice 自動建立並維護的。若是你想要獲取於Injector相關的 Providers 的話,你能夠經過如下方法獲取。
Provider<SomeType> provider = injector.getProvider(SomeType.class)
3. Module
Module 對象會去維護一組 Bindings。在一個應用中能夠有多個 Module 。反過來 Injectors 會經過 Module 來獲取可能的 Bindings。Module 是經過一個包含須要被重寫 override 的 Module.configure() 方法的接口去管理 Bindings。 簡單地說,就是你要繼承一個叫作 AbstractModule的類,這個類實現了 Module 接口,而且重寫 configure() 方法, 代碼以下。
class MyModule extends AbstractModule{ public void configure(Binder binder){ // Code that binds information using the various // flavours of bind method. } }
4. Guice
客戶端 (Clients) 是經過 Guice 類直接和其餘 Objects 進行交互的。Injector 和不一樣的 Modules 之間的聯繫是經過 Guice 創建的。例以下面的代碼。
MyModule module = new MyModule(); Injector injector = Guice.createInjector(module);
這裏須要注意的是 Guice.createInjector() ,該方法接受一個 Module 對象做爲參數。 Module 類必須要重寫 configure() 方法, 該方法是用於傳遞一個 默認 Binder 對象, 該 Binder 對象爲應用程序用於填充特定的 Bindings (to Classes, Objects and Providers)。 當客戶端調用 Injector 類的 getInstance() 方法建立一個實例的時候,Injector 會從 Binder 對象維護的各類 Bindings 中獲取原來的對象。
Guice 註釋 (Annotations)
Guice 提供了一些十分有用的 Annotations ,這些 Annotations 能夠用來在應用程序中添加 元數據 (meta-data)。 這一章節我將要講如下幾個註釋。
1. Implemented By
該 Annotation 用於指向接口的實現類。例如,若是 Add 接口有多個實現類,可是咱們但願 SimpleAdd 是 Add 的默認實現類,因而咱們能夠像下面同樣處理。
@ImplementedBy(SimpleAdd.class) interface Add{ public int add(int a, int b); }
2. Inject
咱們可使用 Inject Annotation 來直接將實例注入到客戶端的代碼中。該註釋能夠用於某個類的構造方法上,代碼以下。
class Client{ @Inject public Client(MyService service){ } }
上面的代碼,咱們是基於構造方法層次 (Constrcctor-level)的 注入,而且假設 MyService 接口的具體實現已經在應用程序的 Module 中定義映射好了。一樣你也能夠在方法層次 (Method-level) 和 字段層次 (Field-level) 使用註釋。
3. Provided By
假設咱們想要爲一些接口定製化對象建立的流程 (Object creation process),那麼咱們須要依賴 Guice Provider 機制, 對於接口 Add 來講,咱們須要使用 AddProvider 來建立並返回 SimpleAdd 對象。在這個案例中,咱們能夠直接在接口聲明處使用 ProvidedBy 註釋來指定該接口的 Provider 類型, 代碼以下。
@ProvidedBy(AddProvider.class) public interface Add{ }
4. Singleton
默認狀況下,客戶端能夠屢次使用 Injector.getInstance() 來調用對象,每個都會返回一個新建立的對象。若是咱們想要使用單例模式(Singleton Pattern)來獲取對象,即 One Instance in the application,你能夠在實現類上使用 Singleton 註釋去標記。
@Singleton
public class MyConnection{ public void connect(){ } public void disconnect(){ } }
例子 (Samples)
這一章節將會提過更多的例子幫助你理解和使用 Guice API ,我將會更加詳細的解析。
1. 簡單的例子
在這個簡單的例子中咱們沒有使用接口編程,即將接口和實現分離。咱們只有一個實現類 Player 和一個依賴它的客戶端 PlayerTest, 這裏 Guice 沒有作什麼,只是提供了一個映射。
首先來看一下 Player 類。
public class Player { public String name; public Player(){ } public String toString(){ return name; } }
下面是客戶端代碼的例子,用於使用 Player 類。這裏須要注意的是咱們沒有在 Guice.createInjector() 方法裏面傳遞 Module ,由於咱們不須要在程序代碼中綁定對象。
import com.google.inject.Guice; import com.google.inject.Injector; public class PlayerTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Player player = injector.getInstance(Player.class); player.name = "David Boon"; System.out.println(player); } }
2. 處理多個依賴 (Multiple Dependencies)
這一小節裏面,咱們將探討如何是用 @Inject 註釋來處理多個依賴。 比方說有一個對象直接依賴其它兩個或者多個對象。這裏咱們建立一個簡單的 Case ,一我的有一臺筆記和一個手機。
首先咱們給出 Mobile 類和 Laptop 類。
public class Laptop { private String model; private String price; public Laptop(){ this.model = "HP 323233232"; this.price = "$545034"; } public String toString(){ return "[Laptop: " + model + "," + price + "]"; } }
public class Mobile { private String number; public Mobile(){ this.number = "988438434"; } public String toString(){ return "[Mobile: " + number + "]"; } }
接下來咱們將會在 Person 類中使用 @Inject 註釋來直接引用 Laptop 和 Mobile 對象。注意咱們這兒使用的是構造方法層次上的注入。
import com.google.inject.Inject; public class Person { private Mobile mobile; private Laptop laptop; @Inject public Person(Mobile mobile, Laptop laptop){ this.mobile = mobile; this.laptop = laptop; } public void diplayInfo(){ System.out.println("Mobile:" + mobile); System.out.println("Laptop:" + laptop); } }
最後是客戶端的代碼,這段代碼用於使用這個例子。因爲咱們沒有使用到 Bindings, 咱們沒有在 Guice.createInject() 方法中傳遞 Module 對象。
import com.google.inject.Guice; import com.google.inject.Injector; public class MultipleDependencyTest { public static void main(String[] args) { Injector injector = Guice.createInjector(); Person person = injector.getInstance(Person.class); person.diplayInfo(); } }
上面程序的運行結果以下:
Mobile:[Mobile: 988438434] Laptop:[Laptop: HP 323233232,$545034]
3. 使用 Binding 註釋
在 Guice 中,一個類型不能綁定多個實現,以下,代碼會拋 Runtime Error.
binderObject.bind(SomeType.class).to(ImplemenationOne.class); binderObject.bind(SomeType.class).to(ImplemenationTwo.class);
因爲 Guice 並不知道客戶端究竟要綁定哪個實現類,所以拋出了異常。可是在相似 Java 的語言中,一個類能夠實現多個接口,基於這個思想,Guice 提供了一種依賴 Binding 註釋的方式來實現一個類型綁定多個實現。例如,接口 Player 定義以下,
public interface Player { public void bat(); public void bowl(); }
接着咱們提供了 Player 的兩種實現類, GoodPlayer 和 BadPlayer。
public class GoodPlayer implements Player{ public void bat() { System.out.println("I can hit any ball"); } public void bowl() { System.out.println("I can also bowl"); } }
public class BadPlayer implements Player{ public void bat() { System.out.println("I think i can face the ball"); } public void bowl() { System.out.println("I dont know bowling"); } }
如今咱們開始介紹 Guice ,對於接口 Player 而言,有兩個實現類 GoodPlayer 和 BadPlayer。不管如何,最終客戶端只會使用其中一個具體的實現類,不管它使用GoodPlayer 實現類仍是 BadPlayer 實現類,經過一些註釋機制 (Annotaion mechanisms) 咱們能夠指示 Guice 使用不一樣的實現。代碼實現以下。
1 import com.google.inject.*; 2 3 public class PlayerModule implements Module{ 4 5 public void configure(Binder binder) { 6 7 binder.bind(Player.class).annotatedWith(Good.class).to( 8 GoodPlayer.class); 9 binder.bind(Player.class).annotatedWith(Bad.class).to( 10 BadPlayer.class); 11 } 12 }
注意第7行和第9行代碼,咱們分別使用了.annotatedWith(Good.class) 和 .annotatedWith(Bad.class), 這兩處代碼指明瞭若是使用Good註釋,那麼就綁定GoodPlayer實現類,若是使用了Bad註釋,那麼就綁定BadPlayer實現類。
上面的代碼中咱們使用了兩個自定義的 Annotation,Good 和 Bad。下面咱們給出 Good annotation 和 Bad annotation 的代碼。
import java.lang.annotation.*; import com.google.inject.BindingAnnotation; @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation @Target(ElementType. LOCAL_VARIABLE) public @interface Good {}
import java.lang.annotation.*; import com.google.inject.BindingAnnotation; @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation @Target(ElementType. LOCAL_VARIABLE) public @interface Bad {}
接下來是上面程序的客戶端代碼。這裏須要注意的是當在客戶端代碼中請求某一個接口的具體實現的時候,能夠直接經過指定不一樣的 Annotation 來指定返回不一樣的實現類。
1 import com.google.inject.Guice; 2 import com.google.inject.Injector; 3 import com.google.inject.Module; 4 5 public class PlayerClient { 6 7 public static void main(String[] args) { 8 9 PlayerModule module = new PlayerModule(); 10 Injector injector = Guice.createInjector(new Module[]{module}); 11 12 @Good Player player = (Player)injector.getInstance(Player.class); 13 player.bat(); 14 player.bowl(); 15 } 16 }
此處注意第10行和第12行。 第12行代碼 @Good 告訴 Guice 去 Playe Moduler 中獲取一個 GoodPlayer實例對象。
4. Named 註釋
像上面例子中,若是隻是爲了標記實現類以便於客戶端使用,而爲每個實現類建立新的 Annotation ,那麼是徹底沒有必要的。咱們可使用 @Named 註釋來命名這些 entities。這兒有一個工具方法 - Names.named() ,當你給它一個命名,它會返回好一個命名好的 Annotation。例如上面的例子中,在 Player Module 中可使用 Names.named() 來完成一些相同的事情。
import com.google.inject.Binder; import com.google.inject.Module; public class PlayerModule implements Module{ public void configure(Binder binder) { binder.bind(Player.class).annotatedWith(Names.named("Good")).to( GoodPlayer.class); binder.bind(Player.class).annotatedWith(Names.named("Bad")).to( BadPlayer.class); } }
如今在客戶端代碼中,咱們將使用 @Named() annotation來獲取註釋。
@Named("Good") Player goodPlayer = (Player)injector.getInstance(Player.class); @Named("Bad") Player badPlayer = (Player)injector.getInstance(Player.class);
5. 一個簡單的 Provider
在 Guice 中 Providers 就像 Factories 同樣建立和返回對象。在大部分狀況下,客戶端能夠直接依賴 Guice 框架來爲服務(Services)建立依賴的對象。可是少數狀況下,應用程序代碼須要爲一個特定的類型定製對象建立流程(Object creation process),這樣能夠控制對象建立的數量,提供緩存(Cache)機制等,這樣的話咱們就要依賴 Guice 的 Provider 類。
例如,咱們須要爲 MockConnection 建立一個對象建立和銷燬的流程,代碼以下。
public class MockConnection { public void connect(){ System.out.println("Connecting to the mock database"); } public void disConnect(){ System.out.println("Dis-connecting from the mock database"); } }
如今咱們來寫一個簡單的 Provider 類來實現 Guice 的 Provider 接口,使用它建立並返 MockConnection對象,代碼以下。
public class ConnectionProvider implements Provider<MockConnection>{ @Override public MockConnection get() { // Do some customization mechanism here. MockConnection connection = new MockConnection(); // Do some customization mechanism here too. return connection; } }
須要注意的是,全部的自定義 Provider 類必需實現 Provider 接口,而且重寫裏面的 get() 方法。如今 Module 須要留意這個自定義的 Provider 類,它須要請求 ConnectionProvider 來建立對象,而不是直接建立對象,實現的代碼以下。
1 import com.google.inject.*; 2 3 public class ConnectionTest { 4 5 public static void main(String args[]){ 6 Injector injector = Guice.createInjector( 7 new Module(){ 8 @Override 9 public void configure(Binder binder) { 10 binder.bind(MockConnection.class).toProvider( 11 ConnectionProvider.class); 12 } 13 } 14 ); 15 16 MockConnection connection = 17 injector.getInstance(MockConnection.class); 18 connection.connect(); 19 connection.disConnect(); 20 } 21 }
注意第10行,咱們使用 toProvider() 方法將 MockConnection.class 綁定到一個 Provider 上。
小結
這篇文章簡要的講解了一些 Guice 相關的內容,有時間我將講講Guice的一些高級應用,還有 Robo Guice的使用。
本文參考連接
http://code.google.com/p/google-guice/
http://www.javabeat.net/2007/08/introduction-to-google-guice/