/这是关于 Web Beans 规范当前状态的系列文章的第二篇。您可以在这里找到第一篇:这里./
Web Beans 支持三种主要的依赖注入机制
直接字段注入
@Component public class Checkout { @In ShoppingCart cart; }
方法注入
@Component public class Checkout { private ShoppingCart cart; @In void setShoppingCart(ShoppingCart cart) { this.cart = cart; } }
以及构造函数注入
@Component public class Checkout { private final ShoppingCart cart; public Checkout(ShoppingCart cart) { this.cart = cart; } }
此外,解析方法支持参数注入
@Resolves Checkout createCheckout(ShoppingCart cart) { return new Checkout(cart); }
依赖注入总是在组件实例首次实例化时发生。
Web Beans 规范定义了一个程序,称为“类型安全解析算法”,Web Beans 容器在识别要注入到注入点的组件时遵循此程序。这个算法一开始看起来很复杂,但一旦你理解了它,它就非常直观。类型安全解析是在系统初始化时执行的,这意味着如果组件的依赖项无法满足,容器将立即通知用户。
此算法的目的是允许多个组件实现相同的 API 类型,并且
- 允许客户端使用“绑定注解”选择它需要的实现,或者
- 允许在部署时使用“组件类型优先级”覆盖相同 API 的另一个实现,而无需更改客户端。
让我们探讨 Web Beans 容器如何确定要注入的组件。
绑定注解
如果我们有多个实现特定 API 类型的组件,注入点可以使用绑定注解指定确切要注入哪个组件。例如,可能有两个PaymentProcessor:
@Component @PayByCheque public class ChequePaymentProcessor implements PaymentProcessor { public void process(Payment payment) { ... } }
@Component @PayByCreditCard public class CreditCardPaymentProcessor implements PaymentProcessor { public void process(Payment payment) { ... } }
实现,其中@PayByCheque和@PayByCreditCard是绑定注解
@Retention(RUNTIME) @Target({TYPE, METHOD, FIELD}) @BindingType public @interface PayByCheque {}
@Retention(RUNTIME) @Target({TYPE, METHOD, FIELD}) @BindingType public @interface PayByCreditCard {}
客户端组件开发者使用绑定注解来指定确切要注入哪个组件
@In @PayByCheque PaymentProcessor chequePaymentProcessor; @In @PayByCreditCard PaymentProcessor creditCardPaymentProcessor;
同样,使用构造函数注入
public Checkout(@PayByCheque PaymentProcessor chequePaymentProcessor, @PayByCreditCard PaymentProcessor creditCardPaymentProcessor) { this.chequePaymentProcessor = chequePaymentProcessor; this.creditCardPaymentProcessor = creditCardPaymentProcessor; }
具有成员的绑定注解
绑定注解可以有成员
@Retention(RUNTIME) @Target({TYPE, METHOD, FIELD}) @BindingType public @interface PayBy { PaymentType value(); }
在这种情况下,成员值是重要的
@In @PayBy(CHEQUE) PaymentProcessor chequePaymentProcessor; @In @PayBy(CREDIT_CARD) PaymentProcessor creditCardPaymentProcessor;
绑定注解的组合
注入点甚至可以指定多个绑定注解
@In @Asynchronous @PayByCheque paymentProcessor
在这种情况下,只有支持/两者/绑定注解的组件才有资格进行注入。
绑定注解和解析器方法
当然,解析器方法也可能指定绑定注解
@Resolves @Asynchronous @PayByCheque PaymentProcessorService createAsyncPaymentProcessor(@PayByCheque PaymentProcessor processor) { return new AsynchronousPaymentProcessorService(processor); }
此方法将被调用以创建一个实例,用于注入到以下注入点
@In @Asynchronous @PayByCheque PaymentProcessorService service;
组件类型
所有Web Bean组件都有一个/组件类型/。每个组件类型标识了一组组件,该组组件应在系统的一些部署中条件性地安装。
例如,我们可以定义一个名为@Mock的组件类型,它将标识仅当系统在集成测试环境中执行时才应安装的组件
@Retention(RUNTIME) @Target({TYPE, METHOD}) @ComponentType public @interface Mock {}
假设我们有一些与外部系统交互以处理支付的组件
@Component public class PaymentProcessor { public void process(Payment p) { ... } }
对于集成或单元测试,外部系统可能很慢或不可用。因此,我们会创建一个模拟对象
@Mock public class MockPaymentProcessor extends PaymentProcessor { @Override public void process(Payment p) { p.setSuccessful(true); } }
安装组件类型
Web Beans定义了两种内置组件类型@Component和@Standard。默认情况下,当系统部署时,仅安装具有内置组件类型的组件。我们可以通过在web-beans.xml.
中列出它们来标识特定部署中要安装的其他组件类型@Mock现在,当我们部署我们的集成测试时,我们希望安装所有我们的
<web-beans> <component-types> <component-type>javax.webbeans.Standard</component-type> <component-type>javax.webbeans.Component</component-type> <component-type>org.jboss.test.Mock</component-type> </component-types> </web-beans>
对象@Component, @Standard现在,Web Beans容器将识别和安装所有在部署时间标记的组件@Mock或
在部署时间。
将此功能与今天的流行容器架构进行比较很有趣。各种轻量级
容器也允许条件部署类路径中存在的组件,但要部署的组件必须在配置代码或某些XML配置文件中显式、单独地列出。Web Beans支持通过XML进行组件定义和配置,但在不需要复杂配置的常见情况下,组件类型允许通过单行XML启用一组组件。同时,开发者可以轻松地识别组件将在什么上下文中使用。
组件类型优先级
如果你一直在关注,你可能想知道容器如何决定哪个实现PaymentProcessor现在,Web Beans容器将识别和安装所有在部署时间标记的组件MockPaymentProcessor来选择。考虑容器遇到此注入点时会发生什么
@In PaymentProcessor pp;
现在有两个组件满足PaymentProcessor合同。当然,我们不能使用绑定注解来消除歧义,因为绑定注解是硬编码在注入点源中的,而我们希望容器能在部署时做出决定!
解决这个问题的方法是每个组件类型都有一个不同的/优先级/。组件类型的优先级由它们在web-beans.xml中出现的顺序决定。在我们的例子中,@Mock出现在@Component之后,因此具有更高的优先级。
每当容器发现多个组件可以满足注入点指定的合同(API类型加绑定注解)时,它都会考虑组件的相对优先级。如果其中一个比其他的高,它就会选择优先级高的组件进行注入。因此,在我们的例子中,容器将在集成测试环境中注入MockPaymentProcessor,并在执行生产时注入PaymentProcessor(这正是我们想要的)。
示例组件类型
组件类型在许多场合都很有用,以下是一些示例
- @Mock和@Staging用于测试的组件类型
- @AustralianTaxLaw用于特定网站的组件
- @SeamFramework, @Guice用于构建在Web Beans之上的第三方框架
- @Standard用于Web Beans规范定义的标准组件
我相信你还能想到更多应用...
还有两个规则要记住吗?
正如我们所见,类型安全的解析算法考虑
- API类型
- 绑定注解
- 组件类型优先级
在解析要注入的组件时。如果这些规则无法产生唯一的组件(如果有多个组件具有相同的高优先级,实现所需的API类型并支持所需的绑定注解),容器将在系统初始化时抛出异常。
Web Beans小组讨论了在上述规则无法缩小组件列表到唯一组件的情况下引入两个额外规则的可能性。总的来说,我支持这些规则,因为我认为它们很有用。然而,整个小组担心额外的复杂性会让开发者感到困惑。因此,我们正在寻求社区对以下想法的反馈。
/注意:/ 如果你不习惯从绑定注解和组件优先级的角度思考,你可能认为组件解析已经足够复杂了!但我相信一旦你习惯了,解析算法将非常健壮,并且会变得完全直观。
规则1:/最少派生实现/规则
这个提议的规则表明,如果上述算法未能产生唯一的组件,并且候选集中有一个/最少派生/的组件——一个所有其他候选者(直接或间接)都扩展的组件,那么将选择这个最少派生的组件。
这个规则旨在确保可以轻松地将新组件引入系统,使用现有组件的实现继承,而不会破坏或更改现有客户端的行为。
例如,如果我有一个现有的组件
@Component @PayByCheque ChequePaymentProcessor implements PaymentProcessor { ... }
然后我引入一个新的组件,该组件扩展了此组件
@Component @PayByCheque AsynchronousChequePaymentProcessor extends ChequePaymentProcessor implements AsynchronousPaymentProcessor { ... }
那么以下注入点将继续解析为基ChequePaymentProcessor组件
@In @PayByCheque PaymentProcessor processor;
而新的注入点当然会收到一个AsynchronousChequePaymentProcessor:
@In @PayByCheque AsynchronousPaymentProcessor processor;
实例
规则2:/最少特定绑定/规则
例如,如果我有一个现有的组件
@Component @PayByCheque ChequePaymentProcessor implements PaymentProcessor { ... }
这个提议的规则与前面的规则类似,但指的是绑定注解而不是API类型。它可以与规则1一起采用,也可以作为规则1的替代。它表明,如果上述算法未能产生唯一的组件,并且候选集中恰好有一个组件在注入点具有/正好/相同的绑定注解,则将选择这个最少特定的组件。@PayByCheque然后我引入一个新的组件,也支持这个
@Component @PayByCheque @Asynchronous AsynchronousChequePaymentProcessor extends ChequePaymentProcessor { ... }
那么以下注入点将继续解析为基ChequePaymentProcessor组件
@In @PayByCheque PaymentProcessor paymentProcessor;
而新的注入点当然会收到一个绑定注解:
@In @PayByCheque @Asynchronous PaymentProcessor paymentProcessor;
AsynchronousPaymentProcessor
你害怕了吗?