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 服务调用方
需要依赖注入的类即可以是一个实现了接口的类,也可以是普通的类。这里 MyApp 就是一个普通的类。它通过构造器以及注解 @Inject
注入 LogService 的实现 LogServiceImpl 并且调用了 LogService#log
日志打印服务。
public class MyApp {
private LogService logService;
@Inject
public MyApp(LogService logService) {
this.logService = logService;
}
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(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() {
MyApp myApp = injector.getInstance(MyApp.class);
myApp.work();
}
}
上面的代码逻辑是在通过 Guice#createInjector
传入配置的依赖注入配置类获取依赖注入容器 Injector。然后通过 injector.getInstance
方法获取对象实例 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 官网上面去查询
参考信息: