google guice hello world

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 官網上面去查詢

參考信息:

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