Google Guice 入門教程01 - 依賴注入(1)

1. 依賴注入


1.1 類依賴注入

所謂的綁定就是將一個接口綁定到具體的類中,這樣客戶端不用關心具體的實現,而只需要獲取相應的接口完成其服務即可。

HelloWorld.java


1     public interface HelloWorld {
2
3         String sayHello();
4     }
5
然後是具體的實現,HelloWorldImpl.java


1     public class HelloWorldImpl implements HelloWorld {
2
3         @Override
4         public String sayHello() {
5             return "Hello, world!";
6         }
7     }
8
寫一個測試例子看看,HelleWorldTest.java


 1     public class HelleWorldTest {
 2
 3         @Test
 4         public void testSayHello() {
 5           Injector inj=  Guice.createInjector(new Module() {
 6                 @Override
 7                 public void configure(Binder binder) {
 8                     binder.bind(HelloWorld.class).to(HelloWorldImpl.class);
 9                 }
10             });
11           HelloWorld hw = inj.getInstance(HelloWorld.class);
12           Assert.assertEquals(hw.sayHello(), "Hello, world!");
13         }
14     }
15
這個例子非常簡單,通俗的將就是將一個HelloWorldImpl的實例與HelloWorld關聯起來,當想Guice獲取一個HelloWorld實例的時候,Guice就返回一個HelloWorldImpl的實例,然後我們就可以調用HelloWorld服務的方法了。

問題(1)HelloWorld是單例的麼?測試下。


1 HelloWorld hw = inj.getInstance(HelloWorld.class);
2 Assert.assertEquals(hw.sayHello(), "Hello, world!");
3 HelloWorld hw2 = inj.getInstance(HelloWorld.class);
4 System.out.println(hw.hashCode()+"->"+hw2.hashCode());
5 Assert.assertEquals(hw.hashCode(), hw2.hashCode());
解答(1)測試結果告訴我們,HelloWorld不是單例的,每次都會返回一個新的實例。

問題(2)HelloWorld的實例是HelloWorldImpl麼?可以強制轉型麼?

HelloWorld hw = inj.getInstance(HelloWorld.class);
System.out.println(hw.getClass().getName());
 

解答(2),結果輸出cn.imxylz.study.guice.helloworld.HelloWorldImpl,看來確實只是返回了一個正常的實例,並沒有做過多的轉換和代理。

問題(3),如果綁定多個實現到同一個接口上會出現什麼情況?


1 public class HelloWorldImplAgain implements HelloWorld {
2     @Override
3     public String sayHello() {
4         return "Hello world again.";
5     }
6 }
binder.bind(HelloWorld.class).to(HelloWorldImpl.class);
binder.bind(HelloWorld.class).to(HelloWorldImplAgain.class);
解答(3),很不幸,Guice目前看起來不允許多個實例綁定到同一個接口上了。

com.google.inject.CreationException: Guice creation errors:

1) A binding to cn.imxylz.study.guice.helloworld.HelloWorld was already configured at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:28).
  at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:29)

問題(4),可以綁定一個實現類到實現類麼?

1 Injector inj=  Guice.createInjector(new Module() {
2       @Override
3       public void configure(Binder binder) {
4           binder.bind(HelloWorldImpl.class).to(HelloWorldImpl.class);
5       }
6   });
7 HelloWorld hw = inj.getInstance(HelloWorldImpl.class);
8 System.out.println(hw.sayHello());
 

非常不幸,不可以自己綁定到自己。

1) Binding points to itself.
  at cn.imxylz.study.guice.helloworld.HelleWorldTest$1.configure(HelleWorldTest.java:28)

我們來看看bind的語法。


<T> AnnotatedBindingBuilder<T> bind(Class<T> type);


ScopedBindingBuilder to(Class<? extends T> implementation);
也就是說只能綁定一個類的子類到其本身。改造下,改用子類替代。


1     public class HelloWorldSubImpl extends HelloWorldImpl {
2
3         @Override
4         public String sayHello() {
5             return "@HelloWorldSubImpl";
6         }
7     }
8
1 Injector inj=  Guice.createInjector(new Module() {
2             @Override
3             public void configure(Binder binder) {
4                 binder.bind(HelloWorldImpl.class).to(HelloWorldSubImpl.class);
5             }
6         });
7       HelloWorldImpl hw = inj.getInstance(HelloWorldImpl.class);
8       System.out.println(hw.sayHello());
太好了,支持子類綁定,這樣即使我們將一個實現類發佈出去了(儘管不推薦這麼做),我們在後期仍然有辦法替換實現類。

使用bind有一個好處,由於JAVA 5以上的泛型在編譯器就確定了,所以可以幫我們檢測出綁定錯誤的問題,而這個在配置文件中是無法檢測出來的。

這樣看起來Module像是一個Map,根據一個Key獲取其Value,非常簡單的邏輯。

問題(5),可以綁定到我們自己構造出來的實例麼?

解答(5)當然可以!看下面的例子。


1 Injector inj=  Guice.createInjector(new Module() {
2             @Override
3             public void configure(Binder binder) {
4                 binder.bind(HelloWorld.class).toInstance(new HelloWorldImpl());
5             }
6         });
7       HelloWorld hw = inj.getInstance(HelloWorld.class);
8       System.out.println(hw.sayHello());
問題(6),我不想自己提供邏輯來構造一個對象可以麼?

解答(6),可以Guice提供了一個方式(Provider<T>),允許自己提供構造對象的方式。


 1 Injector inj=  Guice.createInjector(new Module() {
 2       @Override
 3       public void configure(Binder binder) {
 4           binder.bind(HelloWorld.class).toProvider(new Provider<HelloWorld>() {
 5               @Override
 6               public HelloWorld get() {
 7                   return new HelloWorldImpl();
 8               }
 9           });
10       }
11   });
12 HelloWorld hw = inj.getInstance(HelloWorld.class);
13 System.out.println(hw.sayHello());
問題(7),實現類可以不經過綁定就獲取麼?比如我想獲取HelloWorldImpl的實例而不通過Module綁定麼?

解答(7),可以,實際上Guice能夠自動尋找實現類。


Injector inj=  Guice.createInjector();
HelloWorld hw = inj.getInstance(HelloWorldImpl.class);
System.out.println(hw.sayHello());
問題(8),可以使用註解方式完成注入麼?不想手動關聯實現類。

解答(8),好,Guice提供了註解的方式完成關聯。我們需要在接口上指明此接口被哪個實現類關聯了。


1     @ImplementedBy(HelloWorldImpl.class)
2     public interface HelloWorld {
3
4         String sayHello();
5     }
6
Injector inj=  Guice.createInjector();
HelloWorld hw = inj.getInstance(HelloWorld.class);
System.out.println(hw.sayHello());


事實上對於一個已經被註解的接口我們仍然可以使用Module來關聯,這樣獲取的實例將是Module關聯的實例,而不是@ImplementedBy註解關聯的實例。這樣仍然遵循一個原則,手動優於自動。

問題(9)再回頭看問題(1)怎麼綁定一個單例?

 1     Injector inj = Guice.createInjector(new Module() {
 2
 3         @Override
 4         public void configure(Binder binder) {
 5             binder.bind(HelloWorld.class).to(HelloWorldImplAgain.class).in(Scopes.SINGLETON);
 6         }
 7     });
 8     HelloWorld hw = inj.getInstance(HelloWorld.class);
 9     HelloWorld hw2 = inj.getInstance(HelloWorld.class);
10     System.out.println(hw.hashCode() + "->" + hw2.hashCode());
11
可以看到現在獲取的實例已經是單例的,不再每次請求生成一個新的實例。事實上Guice提供兩種Scope,com.google.inject.Scopes.SINGLETON和com.google.inject.Scopes.NO_SCOPE,所謂沒有scope即是每次生成一個新的實例。

對於自動注入就非常簡單了,只需要在實現類加一個Singleton註解即可。

1     @Singleton
2     public class HelloWorldImpl implements HelloWorld {
3
4         @Override
5         public String sayHello() {
6             return "Hello, world!";
7         }
8     }
9

附:【前沿】本教程的依賴注入部分基於老菜鳥叮咚的教程,原文在此http://www.family168.com/tutorial/guice/html/。原文主要基於Google Guice 1.0版本的,本文基於Google Guice 2.0版本進行學習和討論。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章