DI | Guice教程篇
Google Guice 框架是一个基于Java 6以上的轻量级依赖注入框架,由Google开发。它相比于Spring IOC 来说,如果你只需在应用程序中实现依赖注入,那么你无需使用Spring容器。 Spring不仅仅是一个依赖注入框架,二期大多数Spring应用都使用了XML作为依赖注入的方式。而Guice却是一个相对更轻量级的框架,它的集成更少,有Java实例配置和运行时绑定。 通过使用Java绑定,可以获得编译时的类型检查和IDE自动完成功能。
Guice框架的运行速度也很快。默认情况下,Guice为每一个依赖(对应Spring的“prototype”范围)注入一个新的、单独的对象实例,而Spring则默认提供单态实例。 Guice框架将依赖注入提升到一个新的水平,充分利用Java类型的所有功能,特别是注释和泛型,使得构建的DI应用程序更加模块化,更容易编写,并且错误更少、易于维护。
GOOGLE GUICE 依赖:
1
2
3
4
5
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
入门案例:
Guice 使用起来也很轻量化,不必要太多明显的配置方式。
只需要使用注解,或者几个依赖配置类就可以完成。
如下一个简单的使用Guice的案例: StartServer有两个字段依赖外部类:Service service
, Configuration configuration
, 我们通过 @Inject
标识依赖使用动态注入方式。
在Configuration
中,我们使用@Singleton
把类交给 guice维护为一个单例类,使用时注入给 StartServer类。
在StartModule
中,我们通过显式方式指名对于Service
的依赖使用PrintService
注入。
直接类依赖
直接绑定:
被依赖基类:
1
2
public class DatabaseTransactionLog extends TransactionLog{}
public class MySqlDatabaseTransactionLog extends DatabaseTransactionLog{}
直接依赖配置
1
2
3
4
5
6
7
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
如果请求 TransactionLog
时, 将返回 DatabaseTransactionLog
1
2
@Inject
private TransactionLog log;
@ImplementedBy
1
2
public class PayPalCreditCardProcessor implements CreditCardProcessor {
}
1
2
3
4
5
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
等效与
1
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
注释依赖:
Guice附带一个 @Named
带有字符串的内置绑定注释:
1
2
3
@Inject
@Named('dataLog')
private TransactionLog log;
要绑定特定名称,请使用Names.named()
以创建要传递给的实例 annotatedWith
1
2
3
bind(TransactionLog.class)
.annotatedWith(Names.named("dataLog"))
.to(DatabaseTransactionLog.class);
注解依赖:
定义注解:
1
2
3
4
5
6
7
8
9
10
11
12
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
@BindingAnnotation
public @interface PayPal {}
说明:
@BindingAnnotation
告诉Guice这是一个绑定注释。如果多个绑定注释适用于同一成员,Guice将产生错误。
@Target({FIELD, PARAMETER, METHOD})
对您的用户是礼貌的。它可以防止@PayPal在没有任何用途的情况下意外使用。
@Retention(RUNTIME)
使注释在运行时可用。
依赖:
1
2
3
@Inject
@PayPal
private CreditCardProcessor processor;
绑定配置:
1
2
3
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
通过方法构造的类依赖
当我们需要代码来创建对象时,则需要基于方法构造类依赖方式。
@Provides
注解
如果构造类的方法在 AbstractModule
派生类中,我们可以使用 @Provides
注解把返回对象托管给 Guice.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
...
}
@Provides
TransactionLog provideTransactionLog() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
transactionLog.setThreadPoolSize(30);
return transactionLog;
}
}
当然,@Provides
注解可以配合@Named
, 或自定义依赖注解一起使用.
1
2
3
4
5
6
7
8
@Provides
@PayPal
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
processor.setApiKey(apiKey);
return processor;
}
Provider<T>
派生类
当我们的 @Provides
方法开始变得复杂时,就可以定义类来进行封装。 提供程序类实现了Guice的Provider接口,这是一个用于提供值的简单通用接口:
1
2
3
public interface Provider<T> {
T get();
}
我们实现了Provider接口,已提供自定义实体对象,以定义完整类型安全返回的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
private final Connection connection;
@Inject
public DatabaseTransactionLogProvider(Connection connection) {
this.connection = connection;
}
public TransactionLog get() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setConnection(connection);
return transactionLog;
}
}
这个时候我们需要在 AbstractModule
派生类中绑定我们的类。
1
2
3
4
5
@Override
protected void configure() {
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
}
@ProvidedBy
注解
@ProvidedBy
告诉注入器一个Provider
生成实例的类:
1
2
3
4
5
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
注释等同于 toProvider()
绑定:
1
2
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
Scopes
默认情况下,Guice
每次提供一个值时都会返回一个新实例。
不过我们也可以通过注解来显式的配置依赖的生命周期。
注解 | 范围 |
---|---|
@Singleton | 整个应用程序生命周期范围(单例) |
@SessionScoped | 基于session的会话范围 |
@RequestScoped | 基于每次请求的范围 |
应用范围
Guice使用注释来标识范围。通过将范围注释应用于实现类来指定类型的范围。除了功能性之外,此注释还可用作文档。例如,@Singleton
表示该类旨在是线程安全的。
1
2
3
4
@Singleton
public class InMemoryTransactionLog implements TransactionLog {
/* everything here should be threadsafe! */
}
Scopes
也可以在bind
语句中配置:
1
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
也可以通过@Provides
注释方法:
1
2
3
4
5
@Provides
@Singleton
TransactionLog provideTransactionLog(){
...
}
Injections :
依赖注入模式将行为与依赖性解析分开。 该模式不是直接查找依赖项或从工厂查找依赖项,而是建议传入依赖项。 将依赖项设置为对象的过程称为注入。
构造器函数注入
构造函数注入将实例化与注入相结合。要使用它,请使用注释注释构造函数@Inject
。 此构造函数应接受类依赖项作为参数。然后,大多数构造函数将参数分配给最终字段。
1
2
3
4
5
6
7
8
9
10
public class RealBillingService implements BillingService {
private final CreditCardProcessor processorProvider;
private final TransactionLog transactionLogProvider;
@Inject
public RealBillingService(CreditCardProcessor processorProvider,
TransactionLog transactionLogProvider) {
this.processorProvider = processorProvider;
this.transactionLogProvider = transactionLogProvider;
}
如果您的类没有@Inject
注释构造函数,Guice将使用 public no-arguments 构造函数
(如果存在)。 首选注释,该类型参与依赖注入的文档。
函数注入
Guice可以注入具有@Inject
注释的方法。 依赖关系采用参数的形式,在调用方法之前,注入器会解析这些参数。 注入的方法可以具有任意数量的参数,并且方法名称不会影响注入。
1
2
3
4
5
6
7
8
9
10
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private static final String DEFAULT_API_KEY = "development-use-only";
private String apiKey = DEFAULT_API_KEY;
@Inject
public void setApiKey(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
字段注入
Guice使用@Inject
注释注入字段。这是最简洁的注射剂,但是最不可测试的。
1
2
3
4
5
6
7
8
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
@Inject
Connection connection;
public TransactionLog get() {
return new DatabaseTransactionLog(connection);
}
}
参考内容: