SeamFramework.orgCommunity Documentation
Web Beans支持三种主要的依赖注入机制:
构造器参数注入:
public class Checkout {
private final ShoppingCart cart;
@Initializer
public Checkout(ShoppingCart cart) {
this.cart = cart;
}
}
初始化 方法参数注入:
public class Checkout {
private ShoppingCart cart;
@Initializer
void setShoppingCart(ShoppingCart cart) {
this.cart = cart;
}
}
和直接的域注入:
public class Checkout {
private @Current ShoppingCart cart;
}
当Web Bean实例被首次初始化时,依赖注入总是随之发生。
首先,Web Bean管理器调用Web Bean构造器来获得一个Web Bean的实例。
接下来,Web Bean管理器初始化这个Web bean的所有注入域的值。
然后,Web Bean管理器调用这个Web Bean的初始化方法。
最后, 如果有 @PostConstruct
方法的话,调用这个方法。
EJB Beans不支持构造器参数注入,因为EJB是由EJB容器负责实例化,而不是Web Bean管理器负责。
当应用默认绑定类型@Current
时,构造器的参数和初始化方法需要显式地注释。然而,即使应用了默认的绑定类型,注入域 也必须 指定一个绑定类型。如果一个域没有指定绑定类型,这个域将不会有任何注入发生。
生产者方法也支持参数注入:
@Produces Checkout createCheckout(ShoppingCart cart) {
return new Checkout(cart);
}
最后,观察者方法(我们将在 第 9 章 事件一章中讨论),清除(disposal)方法和解构(destructor)方法都只支持参数注入。
Web Beans规范定义了一个称为 类型安全解析算法的过程,当在注入点识别所注入的Web Bean时,Web Bean管理器会遵循这个过程。这个算法初看起来非常复杂,然而你一旦理解了它,它就相当直观。类型安全解析在系统初始化的时候执行,这意味着如果Web Bean的依赖无法被满足的时候,管理器将立刻通知用户,抛出一个 UnsatisfiedDependencyException
异常或者 AmbiguousDependencyException
异常。
这个算法的目的是允许多个Web Beans实现相同的API类型,并且:
允许客户通过 绑定注释 选择它需要的具体实现,
允许应用部署者激活或者关闭 部署类型 ,从而实现在特定的部署环境下选择适当的具体实现,而无需修改客户,或者
使用部署类型优先级 来允许一个API的实现在部署时重载相同API的另一个实现,而无需修改客户。
让我们看看Web Bean管理器如何决定注入哪个Web Bean。
如果我们拥有实现特定API类型的多个Web Bean,我们可以使用一个绑定注释来指定在注入点注入哪个Web Bean。例如,我们可能有两个 PaymentProcessor
的实现:
@PayByCheque
public class ChequePaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
@PayByCreditCard
public class CreditCardPaymentProcessor implements PaymentProcessor {
public void process(Payment payment) { ... }
}
其中 @PayByCheque
和 @PayByCreditCard
是绑定注释:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCheque {}
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayByCreditCard {}
一个客户Web Bean开发者使用绑定注释来指定到底哪个Web Bean应该被注入。
使用域注入:
@PayByCheque PaymentProcessor chequePaymentProcessor;
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor;
使用初始化方法注入:
@Initializer
public void setPaymentProcessors(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
或者使用构造器注入:
@Initializer
public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor,
@PayByCreditCard PaymentProcessor creditCardPaymentProcessor) {
this.chequePaymentProcessor = chequePaymentProcessor;
this.creditCardPaymentProcessor = creditCardPaymentProcessor;
}
绑定注释可以拥有成员:
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
@BindingType
public @interface PayBy {
PaymentType value();
}
在这个例子,成员值是有意义的:
@PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor;
@PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
你可以告诉Web Bean管理器忽略一个绑定注释的成员,只需在这个成员上使用 @NonBinding
注释。
一个注入点甚至可以指定多个绑定注释:
@Asynchronous @PayByCheque PaymentProcessor paymentProcessor
在这个情况下,只有拥有两个绑定注释的Web Bean才有资格被注入。
甚至生产者方法可以指定绑定注释:
@Produces
@Asynchronous @PayByCheque
PaymentProcessor createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) {
return new AsynchronousPaymentProcessor(processor);
}
所有的Web Beans都有一个 部署类型 。每个部署类型标识一套Web Beans,这套Web Beans会有条件地在某些系统部署下面被安装。
例如,我们可以定义一套为名为 @Mock
的部署类型,这种部署类型用来标识只应该在整合测试环境下才在系统中安装的Web Beans:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@DeploymentType
public @interface Mock {}
假定我们有一些和外部系统交互的Web Beans来处理付费:
public class ExternalPaymentProcessor {
public void process(Payment p) {
...
}
}
因为这个Web Bean并不显式地指定一个部署类型,因此它有一个默认的 @Production
部署类型。
在整合测试或者单元测试期间,外部系统可能会很慢或者无法获得,所以我们可能会创建一个模拟对象:
@Mock
public class MockPaymentProcessor implements PaymentProcessor {
@Override
public void process(Payment p) {
p.setSuccessful(true);
}
}
但是Web Bean管理器如何决定在一个特定部署中使用哪一个实现?
Web Beans定义了两个内置的部署类型: @Production
和 @Standard
。默认情况下,当系统被部署时,只有使用内置部署类型的Web Beans才被激活。我们可以在 web-beans.xml
文件中列出其他的部署类型以便在某个特定的部署中激活它们。
回到我们的例子中,当我们部署我们的整合测试时,我们希望我们标识的所有的 @Mock
对象被安装:
<WebBeans>
<Deploy>
<Standard/>
<Production/>
<test:Mock/>
</Deploy>
</WebBeans
>
现在,Web Bean管理器可以识别并且在部署期间安装所有拥有 @Production
, @Standard
和 @Mock
注释的Web Beans。
@Standard
只是Web Beans规范中为特定的Web Bean使用的部署类型。我们无法在我们自己的Web Bean中使用它,并且我们不能关闭它。
@Production
是没有显式声明部署类型的Web Beans的默认部署类型,它可以被关闭。
如果你注意的话,你可能想知道Web Bean如何决定使用哪个实现 ExternalPaymentProcessor
还是 MockPaymentProcessor
。思考一下当管理器遇到这个注入点时会发生什么:
@Current PaymentProcessor paymentProcessor
现在有两个Web Bean满足 PaymentProcessor
合约。当然,我们无法使用一个绑定注释来消除这个歧义,因为绑定注释在注入点被硬编码到了源代码中,并且我们希望管理器在部署时能够决定注入哪一个Web Bean!
这个问题的解决方案是每个不同的部署类型都拥有不同的 优先级 。部署类型的优先级由它们在 web-beans.xml
中出现的顺序决定。在我们这个例子中, @Mock
比 @Production
出现的晚,所以它拥有更高的优先级。
无论何时管理器发现有多个Web Bean能够满足一个注入点指定的合约(API类型加上绑定注释),它都会考虑Web Beans的相对优先级。它将注入拥有更高优先级的Web Bean。在我们这个例子中,当系统运行在整合测试环境中(这是我们想要的),Web Bean管理器将注入 MockPaymentProcessor
对象。
将其和当今流行的管理器体系比较是很有意思的。各种 "轻量级"的容器也许也可以支持条件化部署在类路径中的类,但是这些需要部署的类必须显式地,逐个地列在配置代码或某个XML配置文件中。Web Bean当然支持通过XML来定义和配置Web Bean,但是多数情况下,只要不需要复杂的配置,部署类型可以使用一行XML就能配置一整套Web Beans。同时,浏览代码的开发者可以很容易识别这些Web Bean应该部署在哪些场景中。
考虑到所有实现一个注入点API类型的Web Bean的绑定注释和部署类型类型,如果解析算法失败,那么Web Bean管理器无法识别究竟应该注入哪个Web Bean。
通常我们很容易修正一个UnsatisfiedDependencyException
或者 AmbiguousDependencyException
。
要修正一个 UnsatisfiedDependencyException
,只须简单的提供一个实现API类型的Web Bean并且拥有注入点的绑定类型 或者激活一个已经实现API类型并且拥有绑定类型的Web Bean的部署类型。
要修正一个 AmbiguousDependencyException
,我们需要引入一个绑定类型来区分API类型的两个不同的实现,或者改变其中一个实现的部署类型以便Web Bean管理器可以使用部署类型优先级来决定究竟部署哪一个实现。只有两个Web Bean共享一个绑定类型并且拥有相同部署类型的时候才会抛出 AmbiguousDependencyException
。
使用Web Bean依赖注入的时候还需要注意一个问题。
注入的Web Bean的客户通常不会直接拥有这个Web bean实例的引用。
想象一下如果一个应用范围的Web Bean能够拥有一个请求范围的Web Bean的直接引用。应用范围的Web Bean是被很多不同的请求共享的。但是不同的请求应该看到不同的请求范围的Web Bean实例!
现在再想象一个会话范围的Web Bean拥有一个应用范围的Web Bean的直接引用,会话上下文常常被序列化到硬盘以便更高效的使用内存。但是,应用范围的Web Bean实例不能和会话范围的Web Bean一起被序列化!
因此,除非Web Bean使用默认的 @Dependent
的范围,Web Bean管理器必须通过一个代理对象来间接地拥有所有注入的Web Bean引用。这个 客户代理 负责确保收到方法调用的Web Bean实例就是当前上下文相关联的实例。客户代理也允许诸如绑定到会话上下文的Web Bean可以序列化到硬盘中,而无需递归地序列化注入到这个Web Bean中的其他的Web Bean。
不幸的是,由于Java语言的限制,一些Java类型无法被Web Bean管理器代理。因此,如果注入点的类型无法被代理的话,Web Bean管理器会抛出一个 UnproxyableDependencyException
异常。
下面的Java类型无法被Web Bean管理器代理:
声明为 final
的类或者拥有 final
方法的类,
没有无参非私有构造器的类,以及
数组和原始类型。
修正 UnproxyableDependencyException
很容易。只需简单的想注入类添加一个无参构造器,引入一个接口或者将注入的Web Bean的范围 @Dependent
即可。
应用可以通过注入获得一个 Manager
接口实例:
@Current Manager manager;
Manager
对象提供一套通过编程获得一个Web Bean实例的方法。
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class);
绑定注释可以通过编写一个帮助类 AnnotationLiteral
的子类来指定,否则很难在Java中实例化一个注释类型。
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new AnnotationLiteral<CreditCard
>(){});
如果绑定类型拥有一个注释成员,我们无法使用 AnnotationLiteral
的匿名子类 相反,我们需要创建一个具名子类:
abstract class CreditCardBinding
extends AnnotationLiteral<CreditCard
>
implements CreditCard {}
PaymentProcessor p = manager.getInstanceByType(PaymentProcessor.class,
new CreditCardBinding() {
public void value() { return paymentType; }
} );
企业级Web Beans支持所有EJB规范定义的生命周期回调方法:@PostConstruct
, @PreDestroy
, @PrePassivate
和 @PostActivate
。
简单的Web Bean只支持 @PostConstruct
和 @PreDestroy
回调。
企业级和简单的Web Bean都支持使用f @Resource
, @EJB
和 @PersistenceContext
来分别注入Java EE资源,EJB和JPA持久化上下文。简单的Web Bean不支持使用 @PersistenceContext(type=EXTENDED)
。
@PostConstruct
调用总是在所有依赖注入之后发生。
我们有一些依赖对象 @Dependent
范围的Web Bean 需要知道它们所注入的对象或者注入点的信息,以便能够实现其功能。例如:
Logger
的日志分类取决于拥有它的类。
一个HTTP参数和报头值的注入取决于注入点指定的参数或者报头名称。
表达式运算结果的注入取决于在注入点指定的表达式。
一个 @Dependent
范围的Web Bean可以注入一个 InjectionPoint
实例并且访问这个注入点相关的元数据。
我们看一个例子。下面的代码很冗长脆弱,有重构问题:
Logger log = Logger.getLogger(MyClass.class.getName());
这个生产者方法允许你注入一个JDK的 Logger
,没有显式的指定一个日志分类:
class LogFactory {
@Produces Logger createLogger(InjectionPoint injectionPoint) {
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}
我们现在可以编写:
@Current Logger log;
没被说服?我们还有第二个例子。要注入HTTP参数,我们需要定一个绑定类型:
@BindingType
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
@NonBinding public String value();
}
我们可以在注入点使用这个绑定类型:
@HttpParam("username") String username;
@HttpParam("password") String password;
下面的生产方法能够完成这个工作:
class HttpParams
@Produces @HttpParam("")
String getParamValue(ServletRequest request, InjectionPoint ip) {
return request.getParameter(ip.getAnnotation(HttpParam.class).value());
}
}
(注意 HttpParam
注释的成员 value()
将被Web Bean管理器忽略,因为它拥有 @NonBinding.
注释)
Web Bean管理器提供一个内置的实现 InjectionPoint
接口的Web Bean:
public interface InjectionPoint {
public Object getInstance();
public Bean<?> getBean();
public Member getMember():
public <T extends Annotation
> T getAnnotation(Class<T
> annotation);
public Set<T extends Annotation
> getAnnotations();
}