Guice是谷歌推出的一个轻量级依赖注入框架,帮助我们解决Java项目中的依赖注入问题。如果使用过Spring的话,会了解到依赖注入是个非常方便的功能。不过假如只想在项目中使用依赖注入,那么引入Spring未免大材小用了。这时候我们可以考虑使用Guice。本文参考了Guice官方文档,详细信息可以直接查看Guice文档。
基本使用
引入依赖
如果使用Maven的话,添加下面的依赖项。
com.google.inject guice 4.1.0
首先我们来假设一个简单的项目框架。首先我们需要一个业务接口,简单的包含一个方法用于执行业务逻辑。它的实现也非常简单。
public interface UserService {
void process(); } public class UserServiceImpl implements UserService { @Override public void process() { System.out.println("我需要做一些业务逻辑"); } }
然后我们需要一个日志接口,它和它的实现也非常简单。
public interface LogService {
void log(String msg); } public class LogServiceImpl implements LogService { @Override public void log(String msg) { System.out.println("------LOG:" + msg); }
}
最后是一个系统接口和相应的实现。在实现中我们使用了业务接口和日志接口处理业务逻辑和打印日志信息。
public interface Application {
void work(); } public class MyApp implements Application { private UserService userService; private LogService logService; @Inject public MyApp(UserService userService, LogService logService) { this.userService = userService; this.logService = logService; } @Override public void work() { userService.process(); logService.log("程序正常运行"); }
}
简单的依赖注入
首先来配置依赖关系。我们继承AbstractModule类,并重写configure方法即可。在configure方法中,我们可以调用AbstractModule类提供的一些方法来配置依赖关系。最常用的方式就是bind(接口或父类).to(实现类或子类)的方式来设置依赖关系。
public class MyAppModule extends AbstractModule {
@Override protected void configure() { bind(LogService.class).to(LogServiceImpl.class); bind(UserService.class).to(UserServiceImpl.class); bind(Application.class).to(MyApp.class); }
}
这样一来,当Guice遇到接口或父类需要注入具体实现的时候,就会使用这里配置的实现类或子类来注入。如果希望在构造器中注入依赖的话,只需要添加@Inject注解即可。
Guice配置完之后,我们需要调用Guice.createInjector方法传入配置类来创建一个注入器,然后使用注入器的getInstance方法获取目标类,Guice会按照配置帮我们注入所有依赖。我们使用单元测试来看看效果。
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(); }
}
//程序结果
//我需要做一些业务逻辑
//------LOG:程序正常运行
依赖绑定
下面这些例子都是Guice文档上的例子。我算是简单的翻译了一下。
链式绑定
我们在绑定依赖的时候不仅可以将父类和子类绑定,还可以将子类和更具体的子类绑定。下面的例子中,当我们需要TransactionLog的时候,Guice最后会为我们注入MySqlDatabaseTransactionLog对象。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
注解绑定
当我们需要将多个同一类型的对象注入不同对象的时候,就需要使用注解区分这些依赖了。最简单的办法就是使用@Named注解进行区分。
首先需要在要注入的地方添加@Named注解。
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
TransactionLog transactionLog) { ...
}
然后在绑定中添加annotatedWith方法指定@Named中指定的名称。由于编译器无法检查字符串,所以Guice官方建议我们保守地使用这种方式。
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) public @interface PayPal {}
实例绑定
有时候需要直接注入一个对象的实例,而不是从依赖关系中解析。如果我们要注入基本类型的话只能这么做。
bind(String.class)
.annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
如果使用toInstance方法注入的实例比较复杂的话,可能会影响程序启动。这时候可以使用@Provides方法代替。
@Provides方法
当一个对象很复杂,无法使用简单的构造器来生成的时候,我们可以使用@Provides方法,也就是在配置类中生成一个注解了@Provides的方法。在该方法中我们可以编写任意代码来构造对象。
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和自定义注解,还可以注入其他依赖,Guice会在调用方法之前注入需要的对象。
@Provides
@PayPal
CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor(); processor.setApiKey(apiKey); return processor;
}
Provider绑定
如果项目中存在多个比较复杂的对象需要构建,使用@Provide方法会让配置类变得比较乱。我们可以使用Guice提供的Provider接口将复杂的代码放到单独的类中。办法很简单,实现Provider接口的get方法即可。在Provider类中,我们可以使用@Inject任意注入对象。
public class DatabaseTransactionLogProvider implements Provider
{ private final Connection connection;
@Inject
public DatabaseTransactionLogProvider(Connection connection) {
this.connection = connection;
}
public TransactionLog get() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog(); transactionLog.setConnection(connection); return transactionLog;
}
}
在配置类中使用toProvider方法绑定到Provider上即可。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class);
}
作用域
默认情况下Guice会在每次注入的时候创建一个新对象。如果希望创建一个单例依赖的话,可以在实现类上应用@Singleton注解。
@Singleton
public class InMemoryTransactionLog implements TransactionLog {
/* everything here should be threadsafe! */
}
或者也可以在配置类中指定。
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
在@Provides方法中也可以指定单例。
@Provides @Singleton
TransactionLog provideTransactionLog() {
...
}
如果一个类型上存在多个冲突的作用域,Guice会使用bind()方法中指定的作用域。如果不想使用注解的作用域,可以在bind()方法中将对象绑定为Scopes.NO_SCOPE。
Guice和它的扩展提供了很多作用域,有单例Singleton,Session作用域SessionScoped,Request请求作用域RequestScoped等等。我们可以根据需要选择合适的作用域。