如何将 CDI 限定符与多个类实现一起使用?
Posted
技术标签:
【中文标题】如何将 CDI 限定符与多个类实现一起使用?【英文标题】:How to use CDI qualifiers with multiple class implementations? 【发布时间】:2013-03-05 06:57:40 【问题描述】:我是 Java EE/JSF 的新手,现在阅读 CDI 限定符 - 更改类实现的可能性。这很好,但我有一个问题。据我了解,我可以使用限定符更改类实现,但我需要在使用此实现的任何地方更改它。在一个地方进行此操作的最佳解决方案是什么?凭借我对 Java EE 的一点了解,我发现了这一点。
假设我们正在创建简单的计算器应用程序。我们需要创建几个类:
Calculator
(计算器的基本实现)
ScientificCalculator
(计算器的科学实现)
MiniCalculator
(潜力最小)
MockCalculator
(用于单元测试)
限定符@Calculator
(将指示计算器的实际实现;我应该为每个实现创建限定符吗?)
问题来了。我有四种计算器实现,我想在少数地方使用其中一种,但每次只使用一种(在最初的项目阶段,我将使用MiniCalculator
,然后使用Calculator
等等)。如何在注入对象的每个地方更改实现而不更改代码?我应该创建负责注入并以method injector
工作的工厂吗?我的解决方案正确且有意义吗?
工厂
@ApplicationScoped
public class CalculatorFctory implements Serializable
private Calculator calc;
@Produces @Calculator Calculator getCalculator()
return new Calculator();
使用计算器的类
public class CalculateUserAge
@Calculator
@Inject
private Calculator calc;
这是正确的解决方案吗?如果我错了或者有更好的解决方案,请纠正我。谢谢!。
【问题讨论】:
【参考方案1】:这里有几个问题。
-
在整个应用程序中更改所需实现的最佳方法是什么?查看
@Alternatives
。
每个实现都需要一个限定符吗?不,请参阅this 答案以获得冗长而详细的解释。
我应该使用生产者来决定注入哪个实现吗?可能是您想要的解决方案,但我对此表示怀疑。生产者通常用于执行某种无法在构造函数/@PostConstruct
中完成的初始化。您还可以使用它来检查注入点并在运行时决定注入什么。有关一些线索,请参阅链接 2。
这个解决方案正确吗?这会起作用,但是您仍然必须弄乱代码才能更改实现,因此请首先考虑 1.。 @Calculator Calculator
也似乎非常多余。同样,请参见 2 处的链接。
@ApplicationScoped
public class CalculatorFctory implements Serializable
private Calculator calc;
@Produces @Calculator Calculator getCalculator()
return new Calculator();
更新:
CDI 使用限定符除了 到依赖解析的类型。换句话说,只要只有一种类型与注入点的类型匹配,单独的类型就足够了,不需要限定符。当仅使用类型还不够时,可以使用限定符来消除歧义。
例如:
public class ImplOne implements MyInterface
...
public class ImplTwo implements MyInterface
...
为了能够注入任一实现,您不需要任何限定符:
@Inject ImplOne bean;
或
@Inject ImplTwo bean;
这就是为什么我说@Calculator Calculator
是多余的。如果您为每个实现定义一个限定符,您不会获得太多,还不如只使用类型。比如说,两个限定符@QualOne
和@QualTwo
:
@Inject @QualOne ImplOne bean;
和
@Inject @QualTwo ImplTwo bean;
上面的例子没有任何收获,因为在前面的例子中已经不存在消除歧义了。
当然,在您无权访问特定实现类型的情况下,您可以这样做:
@Inject @QualOne MyInterface bean; // to inject TypeOne
和
@Inject @QualTwo MyInterface bean; // to inject TypeTwo
但是,当 OP 希望 Calculator 实现由 CDI 管理时,他不应该使用 @Produces。
@Avinash Singh - CDI 管理 @Produces
以及它们返回的任何内容,只要调用该方法的是 CDI。请参阅this section of the spec。这包括返回 `@...Scoped beans,它将支持依赖注入、生命周期回调等。
我在这里忽略了一些细节,所以考虑以下两个:
public class SomeProducer
@Inject ImplOne implOne;
@Inject ImplTwo implTwo;
@Inject ImplThree implThree;
@Produces
public MyInterface get()
if (conditionOne())
return implOne;
else if (conditionTwo())
return implTwo;
else
return implThree;
和
public class SomeProducer
@Produces
public MyInterface get()
if (conditionOne())
return new ImplOne();
else if (conditionTwo())
return new ImplTwo();
else
return new ImplThree;
然后,在第一个示例中,CDI 将管理从生产者返回的内容的生命周期(即@PostConstruct
和@Inject
支持),但在第二个示例中它不会。
回到最初的问题 - 在不修改源代码的情况下切换实现的最佳方式是什么?假设您希望更改适用于整个应用程序。
@Default
public class ImplOne implements MyInterface
...
@Alternative
public class ImplTwo implements MyInterface
...
@Alternative
public class ImplThree implements MyInterface
...
然后,任何@Inject MyInterface instance
、ImplOne
都会被注入,除非
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<class>ImplTwo</class>
</alternatives>
</beans>
被指定,在这种情况下ImplTwo
将被注入到任何地方。
进一步更新
Java EE 环境中确实有一些东西不受 CDI 管理,例如 EJB 和 Web 服务。
如何将 Web 服务注入 CDI 托管 bean?真的很简单:
@WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;
就是这样,您将获得对在 CDI 外部管理的支付服务的有效引用。
但是,如果您不想在任何需要的地方使用完整的 @WebServiceRef(lookup="java:app/service/PaymentService")
怎么办?如果你只想按类型注入呢?然后你在某个地方这样做:
@Produces @WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;
在任何需要引用该支付服务的 CDI bean 中,您可以像这样使用 CDI 简单地 @Inject
它:
@Inject PaymentService paymentService;
请注意,在定义生产者字段之前,PaymentService
将无法通过 CDI 方式进行注入。但它始终以旧方式可用。此外,在任何一种情况下,Web 服务都不是由 CDI 管理,但定义生产者字段只是使该 Web 服务引用可用于注入 CDI 方式。
【讨论】:
您似乎将普通注释与 CDI 限定符混淆了。是的,CDI 限定符是注释,但并非所有注释都是 CDI 限定符。要在依赖关系解析过程中被 CDI 考虑,注释必须继承自 docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html。因此,使用 @WebServiceRef 注释生产者方法对 CDI 没有影响,因为它不继承自Qualifier
。
因此,就 CDI 而言,@Produces @WebServiceRef(lookup="java:app/service/PaymentService") MyWebServiceImpl get()
等同于 @Produces MyWebServiceImpl get()
。
如何将此服务注入到 CDI bean 中?我们将使用@Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") @Inject @myservice MyWebService;
在这种情况下容器不管理 MyWebServiceImpl 生命周期,它只是注入它
感谢您的回答。还有一种情况,假设我们有两个 Webservice 的 PamentService,我们想使用 @Produces @service1 @WebServiceRef(lookup="java:app/service/PaymentService1") PaymentService paymentService;
,那么我们需要在变量声明中使用限定符 @service1
,否则它会抛出错误,因为它无法解析正确的实现。无论哪种方式,在我们使用注释 @Named
声明 bean 之前,都不会管理此 Web 服务或任何 bean 的生命周期
是的,我认为在这种情况下它会抱怨不满足的依赖关系。这就是为什么我强烈建议不要使用限定符,以防按类型解析就足够了(关于冗余的一点)。这并不是说不应该使用限定词,有很多实例它们非常有用。 :) 至于由 CDI 管理的内容 - 它与命名无关。默认情况下,CDI 会扫描整个部署,并且能够注入它找到的所有有效 bean 类型,请参阅docs.jboss.org/cdi/spec/1.0/html_single/#whatclassesarebeans。【参考方案2】:
如果您想使用工厂方法交换代码中的实现,那么您的工厂方法是管理 bean 而不是 CDI,因此实际上不需要@Calculator
。
@ApplicationScoped
public class CalculatorFactory implements Serializable
enum CalculatorTypeMiniCaculator,ScientificCaculator,MockCalculator;
Calculator getCalculator(CalculatorType calctype)
switch(calctype)
case MiniCaculator : return new MiniCalculator();
case ScientificCalculator : new ScientificCalculator();
case MockCalculator : new MockCalculator();
default:return null;
public class CalculatorScientificImpl
private Calculator calc =
CalculatorFactory.getCaclulator(CaclutorType.ScientificCalculator);
doStuff()
public class CalculatorTest
private Calculator calc =
CalculatorFactory.getCaclulator(CaclutorType.MockCalculator);
doStuff()
但是,如果您希望 Caclulator bean 由 CDI 管理以使用 @PostConstruct 等进行注入和生命周期管理,那么您可以使用以下方法之一。 p>
方法一:
优点:可以避免使用@Named("miniCalculator")
创建注解
缺点:如果名称从 miniCalculator
更改为 xyzCalculator
,编译器不会给出错误。
@Named("miniCalculator")
class MiniCalculator implements Calculator ...
@ApplicationScoped
public class CalculatorFactory implements Serializable
private calc;
@Inject
void setCalculator(@Named("miniCalculator") Caclulator calc)
this.calc = calc;
方法 2:推荐(如果任何注入失败,编译器会跟踪注入)
@Qualifier
@Retention(RUNTIME)
@Target(FIELD, TYPE, METHOD)
public @interface MiniCalculator
@ApplicationScoped
public class CalculatorFactory implements Serializable
private calc;
@Inject
void setCalculator(@MiniCalculator calc)
this.calc = calc;
方法 3: 如果您使用工厂方法生成对象。它的生命周期不会由 CDI 管理,但使用 @Inject 注入将正常工作。
@ApplicationScoped
public class CalculatorFactory implements Serializable
private Calculator calc;
@Produces Calculator getCalculator()
return new Calculator();
public class CalculateUserAge
@Inject
private Calculator calc;
这三种方法都适用于测试,假设您有一个名为 CaculatorTest 的类,
class ScientificCalculatorTest
Caclulator scientificCalculator;
@Inject
private void setScientificCalculator(@ScientificCalculator calc)
this.scientificCalculator = calc;
@Test
public void testScientificAddition(int a,int b)
scientificCalculator.add(a,b);
....
如果您想在测试中使用模拟实现,请执行以下操作,
class CalculatorTest
Caclulator calc;
@PostConstruct
init()
this.calc = createMockCaclulator();
@Test
public void testAddition(int a,int b)
calc.add(a,b);
.....
【讨论】:
请根据您的评论查看我的更新。此外,生产者和他们返回的任何东西都由 CDI 管理...... 如果您要自己调用@Produces
,则没有理由注释任何内容。
如何将此服务注入到 CDI bean 中?我们将使用@Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") @Inject @myservice MyWebService;
在这种情况下容器不管理 MyWebServiceImpl 生命周期,它只是注入它
@Produces
bean 仍由 CDI 管理,除非在方法中将其实例化为 new()
。我已经更正了我对何时使用方法 3 的定义。以上是关于如何将 CDI 限定符与多个类实现一起使用?的主要内容,如果未能解决你的问题,请参考以下文章