Guice是一個輕量級的Java依賴注入(DI)框架。
使用依賴注入有很多優點,但是手動操作常常會導致編寫大量樣板代碼。Guice是一個框架,用於編寫使用依賴注入的代碼,而不需要編寫大量樣板代碼,有關動機的更多細節,請參閱本頁面。
簡單地說,Guice減少了對工廠的需求和Java代碼中new的使用。把Guice的@Inject看作是新的。在某些情況下,您仍然需要編寫工廠,但是您的代碼不會直接依賴於它們。您的代碼將更容易更改、單元測試和在其他上下文中重用。
Guice支持Java的類型安全特性,特別是涉及到Java 5中引入的泛型和註釋等特性時。您可能認爲Guice填補了核心Java所缺少的特性。理想情況下,該語言本身將提供大部分相同的功能,但是在出現這種語言之前,我們還有Guice。
Guice 幫助您設計更好的API,而Guice API本身就是一個很好的例子。Guice不是廚房的水槽。我們用至少三個用例來證明每個特性。當我們有疑問時,我們就忽略它。我們構建的通用功能使您能夠擴展Guice,而不是將每個特性都添加到核心框架中。
Guice 的目標是使開發和調試更容易、更快,而不是更困難、更慢。在這方面,Guice 避開了驚喜和魔法。您應該能夠理解帶有或不帶有工具的代碼,儘管工具可以使事情變得更簡單。當錯誤確實發生時,Guice會加倍努力以生成有用的消息。
在很多框架中都使用到了 Guice,比如攜程的配置中心 Apollo、Elastic-Seach Java 客戶端。下面就距離說明 Guice 中幾種常見的依賴注入方式:
1、簡單依賴注入
Guice 簡單的依賴注入包含三個部分:
- 服務接口及實現類
- 服務調用方類構造器和
@Inject
依賴注入 - 依賴注入配置
Module
1.1 服務接口及實現
LogService 定義了接口 log ,然後調用方傳入 msg 就可以打印傳入的日誌信息
public interface LogService {
void log(String msg);
}
LogServiceImpl 實現了 LogService 定義的 log 接口。
public class LogServiceImpl implements LogService {
@Override
public void log(String msg) {
System.out.println("------LOG:" + msg);
}
}
1.2 服務調用方接口及實現
Application 是服務調用方定義方法work
public interface Application {
void work();
}
MyApp 實現了 Application,通過構造器以及註解 @Inject
注入 LogService 的實現 LogServiceImpl 並且調用了 LogService#log
日誌打印服務。
public class MyApp implements Application {
private LogService logService;
@Inject
public MyApp(LogService logService) {
this.logService = logService;
}
@Override
public void work() {
logService.log("程序正常運行");
}
}
1.3 依賴注入配置類
MyAppModule 實現了 Guice 內部的 AbstractModule 類,通過重寫方法 configure
把對象添加到 Guice 這個對象容器當中。下面的意思是從容器當中獲取 LogService 接口實例會得到 LogServiceImpl,獲取 Application 接口實例會得到 MyApp 對象實例。
public class MyAppModule extends AbstractModule {
@Override
protected void configure() {
bind(LogService.class).to(LogServiceImpl.class);
bind(Application.class).to(MyApp.class);
}
}
1.4 單元測試
public class MyAppTest {
private static Injector injector;
@BeforeClass
public static void init() {
injector = Guice.createInjector(new MyAppModule());
}
@Test
public void testMyApp() {
Application myApp = injector.getInstance(Application.class);
myApp.work();
}
}
上面的代碼邏輯是在通過 Guice#createInjector
傳入配置的依賴注入配置類獲取依賴注入容器 Injector。然後通過 injector.getInstance
方法獲取 Application 的對象實現 MyApp。在 MyApp 中通過構造器與註解 @Inject
注入接口 LogService 的對象實現 LogServiceImpl。然後調用 Application#work 方法,其實就是調用 Application 的實現類 MyApp#work 它會調用依賴注入的 LogServiceImpl#log 方法,完成整個依賴注入以及方法的調用。
上面代碼如果能夠正常運行,說明可以使用 Guice 進行依賴注入。運行上面的 TestCase 發現並沒有異常。
2、註解限定依賴注入
使用 Guice 進行依賴注入的時候,其實還可以通過註解來限定依賴注入,主要應用場景是一個接口有多個實現。下面就舉例一個簡單的例子。
2.1 定義限定註解
限定註解 @Message 註解與 @Count 來限定注入,需要使用 Guice 框架中的 @Qualifier 註解標註到限定註解上。
@Qualifier
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Message {
}
@Qualifier
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Count {
}
2.2 定義依賴配置
public class DemoModule extends AbstractModule {
@Override
protected void configure() {
bind(Key.get(String.class, Message.class)).toInstance("hello world");
}
@Provides
@Count
public Integer provideCount() {
return 3;
}
}
2.3 需要依賴注入的類
public class Greeter {
private final String message;
private final int count;
@Inject
public Greeter(@Message String message, @Count int count) {
this.message = message;
this.count = count;
}
public void sayHello() {
for (int i=0; i < count; i++) {
System.out.println(message);
}
}
}
2.4 測試用例
public class AnnotationTest {
private static Injector injector;
@BeforeClass
public static void init() {
injector = Guice.createInjector(new DemoModule());
}
@Test
public void testGreeter(){
Greeter greeter = injector.getInstance(Greeter.class);
greeter.sayHello();
}
}
運行上面的測試用例會打印 3 次 hello world
,沒有問題。
3、實例綁定依賴注入
當需要注入的對象是 String 或者 8 種基本對象類型及其包裝類型時,可以使用 Guice 框架提供的 @Named 註解或者自定義命名註解來進行屬性綁定。
3.1 綁定簡單對象的類
@Data
public class Config {
private String jdbcUrl;
private int loginTimeoutSeconds;
private int port;
@Inject
public Config(@Named("JDBC URL") String jdbcUrl,@Named("login timeout seconds") int loginTimeoutSeconds,@HttpPort int port) {
this.jdbcUrl = jdbcUrl;
this.loginTimeoutSeconds = loginTimeoutSeconds;
this.port = port;
}
}
3.2 自定義命名註解
同要的自定義的命名註解也需要使用 Guice 框架提供的 @Qualifier 註解。
@Qualifier
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HttpPort {
}
3.3 依賴注入配置
public class InstanceBindingsModule extends AbstractModule {
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("JDBC URL")).toInstance("jdbc:mysql://localhost/pizza");
bind(Integer.class).annotatedWith(Names.named("login timeout seconds")).toInstance(10);
bindConstant().annotatedWith(HttpPort.class).to(8080);
}
}
3.4 測試用例
public class InstanceBindingsTest {
private static Injector injector;
@BeforeClass
public static void init() {
injector = Guice.createInjector(new InstanceBindingsModule());
}
@Test
public void testMyApp() {
Config config = injector.getInstance(Config.class);
Assert.assertTrue("jdbc:mysql://localhost/pizza".equals(config.getJdbcUrl()));
Assert.assertTrue(config.getLoginTimeoutSeconds() == 10);
Assert.assertTrue(config.getPort() == 8080);
}
}
4、對象 Scope
在 Guice 框架定義對象的時候還可以指定對象的 Scop,默認是單例模式。
4.1 服務接口及實現
public interface HelloService {
String sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
}
4.2 依賴注入配置類
public class UserModule extends AbstractModule {
@Override
protected void configure() {
bind(HelloService.class).to(HelloServiceImpl.class).in(Singleton.class);
// bind(HelloService.class).to(HelloServiceImpl.class).asEagerSingleton();;
// bind(HelloService.class).to(HelloServiceImpl.class).in(Scopes.SINGLETON);
// bind(HelloService.class).to(HelloServiceImpl.class).in(RequestScoped.class);
// bind(HelloService.class).to(HelloServiceImpl.class).in(SessionScoped.class);
}
}
可以通過 4 種方式來指定配置類是單例對象。除了上面配置類的前面三種方式指定這個對象是單例模式,還可以在實現類的上面標註 Guice 框架的 @Singleton 註解來表示這個對象是單例。下面就是 Guice 框架與 Servlet 規範整合的時候可以指定對象爲 Servlet 容器中的 Request 範圍以及和 Session 綁定的 SessionScoped。
4.3 需要依賴注入的類
public class User {
private HelloService helloService;
@Inject
public User(HelloService helloService) {
this.helloService = helloService;
}
public String sayHello(String name) {
return helloService.sayHello(name);
}
}
4.4 測試用例
public class UserTest {
private static Injector injector;
@BeforeClass
public static void init() {
injector = Guice.createInjector(new UserModule());
}
@Test
public void testGreeter(){
User user = injector.getInstance(User.class);
Assert.assertTrue("hello, carl".equals(user.sayHello("carl")));
}
}
5、鏈式綁定對象
在 Guice 當中,普通情況是一個接口綁定一個實現類。如果這個接口綁定的實現類再去綁定它的繼承類,那麼通過 Guice 容器依賴注入的類就是這個接口的實現類的繼承類。
5.1 接口定義及實現
public interface TransactionLog {
}
public class DatabaseTransactionLog implements TransactionLog {
}
public class MySqlDatabaseTransactionLog extends DatabaseTransactionLog {
}
5.2 接口依賴類
@Data
public class LinkedBindingService {
private TransactionLog transactionLog;
@Inject
public LinkedBindingService(TransactionLog transactionLog) {
this.transactionLog = transactionLog;
}
}
5.3 依賴配置類
下面就是鏈式依賴配置。
public class LinkedBindingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
5.4 測試用例
public class LinkedBindingTest {
private static Injector injector;
@BeforeClass
public static void init() {
injector = Guice.createInjector(new LinkedBindingModule());
}
@Test
public void testGreeter(){
LinkedBindingService linkedBindingService = injector.getInstance(LinkedBindingService.class);
Assert.assertTrue(linkedBindingService.getTransactionLog() instanceof MySqlDatabaseTransactionLog);
}
}
6、 @Provides 註解定義對象
@Provides 註解可以在依賴注入配置類中定義對象,如果有同一個對象實例需要定義多個,就需要我們上面第二小節說到的通過註解來限定依賴注入。
6.1 限定依賴註解
@Qualifier
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface One {
}
@Qualifier
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Two {
}
6.2 @Provides 註解定義對象
通過 @Provides 註解定義來對象分爲兩種情況,第一種只定義聲明一個這個對象的一個實例。
public class ProvidesMethodsSingleModule extends AbstractModule {
@Provides
public ProviderMethod provideMethod() {
ProviderMethod providerMethod = new ProviderMethod("default");
return providerMethod;
}
}
另一種情況就是定義聲明一個對象的多個實例。
public class ProvidesMethodsAnnotationModule extends AbstractModule {
@Override
protected void configure() {
bind(ProviderMethod.class).to(Key.get(ProviderMethod.class, One.class));
}
@Provides
@One
public ProviderMethod provideMethodOne() {
ProviderMethod providerMethod = new ProviderMethod("one");
return providerMethod;
}
@Provides
@Two
public ProviderMethod provideMethodTwo() {
ProviderMethod providerMethod = new ProviderMethod("two");
return providerMethod;
}
}
這種情況下就需要使用註解來限定注入對象實例。
6.3 需要納入 Guice 管理的類
@RequiredArgsConstructor
@Getter
public class ProviderMethod {
private final String name;
}
6.4 測試用例
public class ProvidesMethodsTest {
@Test
public void testDefault() {
Injector injector = Guice.createInjector(new ProvidesMethodsAnnotationModule());
ProviderMethod providerMethod = injector.getInstance(ProviderMethod.class);
Assert.assertTrue("one".equals(providerMethod.getName()));
}
@Test
public void testOne() {
Injector injector = Guice.createInjector(new ProvidesMethodsAnnotationModule());
ProviderMethod providerMethod = injector.getInstance(Key.get(ProviderMethod.class, One.class));
Assert.assertTrue("one".equals(providerMethod.getName()));
}
@Test
public void testTwo() {
Injector injector = Guice.createInjector(new ProvidesMethodsAnnotationModule());
ProviderMethod providerMethod = injector.getInstance(Key.get(ProviderMethod.class, Two.class));
Assert.assertTrue("two".equals(providerMethod.getName()));
}
@Test
public void testProvider() {
Injector injector = Guice.createInjector(new ProvidesMethodsSingleModule());
ProviderMethod providerMethod = injector.getInstance(ProviderMethod.class);
Assert.assertTrue("default".equals(providerMethod.getName()));
}
}
上面只是列舉了 Guice 框架比較常用的依賴注入方式。還有其它的方式大家可以去 Guice Github 官網上面去查詢
參考信息: