在不同作用域间注入
你可以安全地将来自大作用域的对象注入到来自小作用域或相同作用域的对象中。例如,你可以将一个作用域为 HTTP 会话的对象注入到作用域为 HTTP 请求的对象中。但是,
向较大作用域的对象中注入就是另一件事了。例如,如果你把一个作用域为 HTTP 请求的对象注入到一个单件对象中,最好情况下,你会得到无法在 HTTP 请求中运行的错误信息,最坏情况下,你的单件对象会
总
是引用来自第一
个
HTTP
请求的对象。在这些时候,你应该注入一个
Provider<T>,然后在需要的时候使用它从较小的作用域中获取对象。这时,你必须确保,在 T 的作用域之外,永远不要调用这个提供者(例如,当目前没有 HTTP 请求且 T 的作用域为
HTTP
请求的时候)。
开发阶段
Guice 明白你的应用开发需要经历不同的阶段。你可以在创建容器时告诉它应用程序运行在哪一个阶段。Guice 目前支持“开发”和“产品”两个阶段。我们发现测试通常属于其中某一个阶段。
在开发阶段,Guice 会 根据需要加载单件对象。这样,你的应用 程序可以快速启动,只加载你正在测试的部分。
在产品阶段,Guice 会在启动时加载全部单件对象。这帮助你尽早捕获错误,提前优化性能。
你的模块也可以使用方法拦截和其他基于当前阶段的绑定。例如,一个拦截器可能会在开发阶段检查你是否在作用域之外使用对象。
在开发阶段,Guice 会 根据需要加载单件对象。这样,你的应用 程序可以快速启动,只加载你正在测试的部分。
在产品阶段,Guice 会在启动时加载全部单件对象。这帮助你尽早捕获错误,提前优化性能。
你的模块也可以使用方法拦截和其他基于当前阶段的绑定。例如,一个拦截器可能会在开发阶段检查你是否在作用域之外使用对象。
拦截方法
Guice 使用
AOP Alliance API 支持简单的方法拦截。你可以在模块中使用
Binder 绑定拦截器。例如,对标注有
@Transactional 的方法
应
用
事务
拦截器:
import static com.google.inject.matcher.Matchers.*;
...
binder.bindInterceptor(
any(), // Match classes.
annotatedWith(Transactional.class), // Match methods.
new TransactionInterceptor() // The interceptor.
);
...
binder.bindInterceptor(
any(), // Match classes.
annotatedWith(Transactional.class), // Match methods.
new TransactionInterceptor() // The interceptor.
);
尽量让匹配代码多做些过滤工作,而不是在拦截器中过滤。因为匹配代码只在启动时运行一次。
静态注入
静态字段和方法会增加测试和复用的难度,但有的时候你唯一的选择就是保留一个
Injector 的
静态
引用。
在这些情况下,Guice 支持注入可访问性较少的静态方法。例如,HTTP 会话对象经常需要被串行化,以支持复制机制。但是,如果你的会话对象依赖于一个作用域为容器生命周期的对象,该怎么办呢?我们可以保留一个该对象
的临时
引用,但在反串行化的时候,我们该如何再次找到该对象呢?
我们发现更实用的解决方案是使用静态注入:
@SessionScoped
class User {
@Inject
static AuthorizationService authorizationService;
...
}
class User {
@Inject
static AuthorizationService authorizationService;
...
}
Guice 从不自动实施静态注入。你必须使用
Binder 显式请求
Injector 在启动后注入你的静态成员:
binder.requestStaticInjection(User.class);
静态注入是一个
很难避免
的祸害,它会使测试难度加大。如果有办法避开它,你多半会很高兴的。
可选注入
有时你的代码应该在无论绑定是否存在的时候都能工作。在这些情况下,你可以使用
@Inject(optional=true),Guice 会在有绑定可用时,用一个绑定实现覆盖你的缺省实现。例如:
@Inject(optional=true) Formatter formatter = new DefaultFormatter();
如果谁为
Formatter 创建了一个绑定,Guice 会基于该绑定注入实例。否则,如果
Formatter 不能被注入(参见
隐式绑定),Guice 会忽略可选成员。
可选注入只能应用于字段和方法,而不能用于构造 函数。对于方法,如果一个参数的绑定找不到,Guice 就不会注入该方法,即便其他参数的绑定是可用的。
可选注入只能应用于字段和方法,而不能用于构造 函数。对于方法,如果一个参数的绑定找不到,Guice 就不会注入该方法,即便其他参数的绑定是可用的。
绑定到字符串
只要有可能,我们就尽量避免使用字符串,因为它们容易
被错误拼写,对工具不友好,等等。但使用字符串而不是创建定制的标注对于“快而脏”的代码来说仍是有用的。在这些情况下,Guice 提供了
@Named 和
Names。例如,一个到字符串名字的绑定:
import static com.google.inject.name.Names.*;
...
bind(named("bob")).to(10);
...
bind(named("bob")).to(10);
会匹配下面的注入点:
@Inject @Named("bob") int score;
Struts 2支持
要在 Struts 2.0.6 或更高版本中安装 Guice Struts 2 插件,只要将
guice-struts2-plugin-1.0.jar 包含在你的 Web 应用的 classpath 中,并在
struts.xml
文件
中选择 Guice 作为你的
ObjectFactory 实现即可:
<constant name="struts.objectFactory" value="guice" />
Guice 会注入所有你的 Struts 2 对象,包括动作和拦截器。你甚至可以设置动作类的作用域。你也可以在你的
struts.xml 文件中指定 Guice 的
Module:
<constant name="guice.module" value="mypackage.MyModule"/>
如果你的所有绑定都是隐式的,你就根本不用定义模块了。
一个计数器的例子
例如,我们试图统计一个会话中的请求数目。定义一个在会话中存活的
Counter 对象:
@SessionScoped
public class Counter {
int count = 0;
/** Increments the count and returns the new value. */
public synchronized int increment() {
return count++;
}
}
public class Counter {
int count = 0;
/** Increments the count and returns the new value. */
public synchronized int increment() {
return count++;
}
}
接下来,我们可以将我们的计数器注入到动作中:
public class Count {
final Counter counter;
@Inject
public Count(Counter counter) {
this.counter = counter;
}
public String execute() {
return SUCCESS;
}
public int getCount() {
return counter.increment();
}
}
final Counter counter;
@Inject
public Count(Counter counter) {
this.counter = counter;
}
public String execute() {
return SUCCESS;
}
public int getCount() {
return counter.increment();
}
}
然后在 struts.xml 文件中为动作类创建映射:
<action name="Count"
class="mypackage.Count">
<result>/WEB-INF/Counter.jsp</result>
</action>
class="mypackage.Count">
<result>/WEB-INF/Counter.jsp</result>
</action>
以及一个用于显示结果的 JSP 页面:
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<body>
<h1>Counter Example</h1>
<h3><b>Hits in this session:</b>
<s:property value="count"/></h3>
</body>
</html>
<html>
<body>
<h1>Counter Example</h1>
<h3><b>Hits in this session:</b>
<s:property value="count"/></h3>
</body>
</html>
我们实际上把这个例子做得比需求更复杂,以便展示更多的概念。在现实中,我们不需要使用单独的
Counter 对象,只要把
@SessionScoped
直接应用于我们的动作类即可。
JMX 集成
参见 com.google.inject.tools.jmx.
附录:注入器如何解决注入请求
注入器解决注入请求的过程依赖于已有的绑定和相关类型中的标注。这里是关于如何解决注入请求的一个概要描述:
- 观察被注入元素的 Java 类型和可选的“绑定标注”。如果类型是 com.google.inject.Provider<T>,就使用类型 T 解决注入请求。对于(类型,标注)对,寻找一个绑定。如果找不到,则跳到步骤4。
- 沿着绑定链检查。如果该绑定连接到另一个绑定,则沿着这条边继续检查,直到到达一个没有连接到任何后续绑定的绑定为止。现在我们就为该注入请求找到了最明确的显式绑定。
- 如果绑定指明一个实例或一个 Provider 实例,所有事情都做完了;使用这个实例来满足请求即可。
- 此时,如果注入请求使用了标注类型或值,我们就报告错误。
- 否则,检查绑定的 Java 类型;如果找到了 @ImplementedBy 标注,就实例化该类型。 如果找到了 @ProvidedBy 标注,就实例化提供者类并用它来获取想要的对象。否则试图实例化类型本身。