1. Guice是什么
Guice(当前最新版本是4.1)是Google开发的轻量级依赖注入框架,目前需要的jdk版本为1.6及以上。Guice实现依赖注入,首先在Module中通过bind方法绑定对象实例、或者绑定映射到对应接口对象实例(这一步可以理解为在Spring配置文件中配置让容器托管的@Bean对象),然后在需要其他对象的地方增加@Inject的注释,就可以自动将对应对象实例注入进来。关于Inject和bind,后面的例子中再慢慢理解。
2. 依赖注入
既然Guice是轻量级的依赖注入框架,那么还是先简单的回顾一下控制反转和依赖注入的概念。控制反转(Inversion of control)是一种软件设计原则,通过控制反转,可以让计算机程序的自定义业务代码,接收来自通用框架的控制流程。控制反转是相对于传统的程序开发来讲的:在传统的程序开发中,自定义业务代码调用可重用的框架类库,来实现自定义业务代码的功能。通过控制反转,将由框架来调用自定义业务代码。也就是说,这个调用的过程原本是自定义业务代码调用框架,现在反转了,反过来由框架调用自定义业务代码来实现功能,最终实现高内聚,低耦合,可扩展的系统。
依赖注入是控制反转的一种实现方式。通俗一点来说,假如要实现一个功能,A类需要调用B类的方法,那么,我们就可以说,此时A类依赖于B类。在传统方式下,需要在A类代码中手动new 一个B类的实例,赋值给A类的某个字段。项目大了,各种依赖错综复杂,耦合度太高,不利于项目的维护。就需要通过程序框架,将B类的实例注入给A类,达到自动装配各类间实例依赖的效果。在Spring中,通过容器来管理各个类实例,实现自动注入,那么Guice如何实现呢?我们后面慢慢来讲。
3. Guice使用方法
3.1 依赖注入之类实例托管给容器
上面我们已经说过,Spring中,依赖注入是将类实例交给容器来进行依赖注入的,Guice却没有容器的概念。无论是否有容器这个概念,根据控制反转的定义,总归我们的自定义业务编码,将要接受通用框架的控制,我们自定义业务编码依赖的实例对象,也将要接受通用框架的注入。在Guice中,可以将Module理解为实施控制和注入的通用框架,我们业务代码所依赖的各个类实例,将有Module来注入。既然由Module来注入我们的类实例,首先,我们需要将所有的类实例注册到Module里面,这样,Module才有资源进行注入。如下代码分别描述了将一个实例对象、绑定了接口的实例对象注册到Module中的过程。其中的in里面的代码表示该实例对象的类型为单例实例。
通过以上的代码,我们就可以将InstanceServiceImpl类实例和DemoServiceImpl类的实例交给Module来管理。所以Module类,就可以抽象的理解为一个小容器。同一模块或者同种类型的类,可以交给同一个Module类管理,这样更能实现清晰的业务编码逻辑。
Guice在项目启动的时候,可以通过如下代码,创建很多个Module实例
通过injector对象,我们也可以显式的获取到注册到Module的类实例:
3.2 绑定方式
- 实例绑定
bind(String.class)
.annotatedWith(Names.named("jdbc_url"))
.toInstance("jdbc:mysql://localhost/pizza");
如上代码,实例绑定,可以理解为,在Module管理的容器中,实例化一个String字符串,命名为JDBC URL,赋值jdbc:mysql://localhost/pizza 在需要注入的地方,使用
即可将绑定的实例对象注入。
- 链式绑定
// 将DemoServiceImpl的类实例,绑定到接口IDemoService上
bind(IDemoService.class).to(DemoServiceImpl.class).in(Scopes.SINGLETON);
链式绑定将实现和接口链接起来,当使用injector.getInstance(IDemoService.class)方法来获取实例,或者通过构造函数注入IDemoService类型的时候,系统将实例化一个DemoServiceImpl对象,将对象注入。
- 注释绑定
有时候,一个接口,我们可能会有多种实现。那么就可以通过这种注释绑定的方式,来标明绑定哪种类型,进而注入明确的类型。拿Guice github wiki上面的例子来做个说明。首先需要使用Guice的@BindingAnnotation来创建一个注释类型:
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface PayPal {}
然后,在Module注册的时候,通过如下代码,将PayPalCreditCardProcessor绑定到CreditCardProcessor类型,同时,使用PayPal 这个注释:
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
这样,在需要注入CreditCardProcessor类型的地方,使用@ PayPal注释一下,就会自动注入PayPalCreditCardProcessor实例。
- @Provides Methods/ ProviderBindings
在链式绑定的时候,我们通过bind(IDemoService.class).to(DemoServiceImpl.class)样的形式,将IDemoService和其实现DemoServiceImpl绑定并注册到Module。如果没有必要专门写一个实现类或者想明确制定一个Provider,就可以通过在Module中,使用@Provides注释,进行标记返回服务提供者:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
// .....
}
@Provides
IDemoService provideDemoService() {
return new IDemoService() {
@Override
public void doSomeThing() {
}
}
}
}
通过这样@Provides的方式,就可以提供IDemoService类型注入的实例。但是这样的@Provides写的多了,在Module类中将难以维护,所以,类似这样的@Provides可以统一实现Guice提供的
public interface Provider<T> {
T get();
}
接口,然后通过如下代码进行绑定:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(IDemoService.class)
.toProvider(DemoServiceProvider.class);
}
}
即为ProviderBindings绑定。
3.3 注入方式
通过以上绑定方式,将各种类型的类实例注册到Module之后,就可以用@Inject注释进行注入管理了。注入使用起来也比较简单,包括构造函数注入,属性注入,函数注入等,在需要注入的地方,加上@Inject注释即可自动注入。
- 构造注入
class BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
@Inject
BillingService(CreditCardProcessor processor,
TransactionLog transactionLog) {
this.processor = processor;
this.transactionLog = transactionLog;
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
...
}
}
- 属性注入
public class DemoServiceImpl implements IDemoService {
// 通过@Inject注入instanceServiceImpl实例
@Inject
private InstanceServiceImpl instanceServiceImpl;
@Override
public void doSomeThing() {
//调用instanceServiceImpl方法
instanceServiceImpl.doFirstThing();
// ....其他逻辑
}
}
- 函数注入
public class DemoServiceImpl implements IDemoService {
private InstanceServiceImpl instanceServiceImpl;
@Inject
public void setInstanceServiceImpl(InstanceServiceImpl instanceServiceImpl) {
this.instanceServiceImpl = instanceServiceImpl;
}
@Override
public void doSomeThing() {
instanceServiceImpl.doFirstThing();
}
public InstanceServiceImpl getInstanceServiceImpl() {
return instanceServiceImpl;
}
}
3.4 加载配置文件并实现静态字段注入
一般我们在项目中,会写很多如下静态字段,便于方法调用。像这样的静态字段,也可以通过@Inject注入。但是一般情况下,这样的属性都是配置在配置文件中,该如何进行注入呢?
如上,我们可以通过@Inject@Named组合进行注入。同时,Guice也提供了将Properties对象转化为Named实例等方法。
public class CommandModule implements Module {
private static final Logger logger = LoggerFactory.getLogger(CommandModule.class);
@Override
public void configure(Binder binder) {
// 加载配置文件
try {
loadProperties(binder);
} catch (FroadException e) {
logger.error("加载配置文件app.properties失败", e);
return;
}
// 给静态字段注入配置文件中配置常量
binder.requestStaticInjection(URLCommand.class);
}
/**
* 加载配置文件
* @param binder
* @throws FroadException
*/
private void loadProperties(Binder binder) throws FroadException {
InputStream inputStream = null;
Properties properties = new Properties();
try {
logger.info("开始加载配置文件app.properties");
String configPath = System.getProperty("PROJECTDIR") + File.separator + "config";
File configFile = new File(configPath);
if (!configFile.exists()) {
logger.error("配置文件路径config不存在");
throw new FroadException("配置文件路径config不存在!");
}
String appPropertiesPath = configPath + File.separator + "app.properties";
File appPropertiesFile = new File(appPropertiesPath);
if (!appPropertiesFile.exists()) {
logger.error("配置文件app.properties不存在");
throw new FroadException("配置文件app.properties不存在!");
}
logger.info("读取配置文件app.properties");
inputStream = new FileInputStream(appPropertiesFile);
properties.load(inputStream);
Names.bindProperties(binder, properties);
logger.info("加载配置文件app.properties完毕");
} catch (IOException e) {
logger.error("解析app.properties文件失败", e);
binder.addError(e);
throw new FroadException("加载配置文件app.properties失败!");
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
logger.error("关闭app文件IO流失败", e);
}
}
}
}
4.总结
通过以上的方法,我们可以将不同类型的对象实例注册给Module对象,在需要的地方使用@Inject@Named,即可非常方便的将实例注入到依赖该对象的类中。下一节将讲解如何在Guice中整合Mybatis。