我是Java EE / JSF的新手,现在阅读有关CDI限定符的内容 - 更改类实现的可能性。这很棒,但我有一个问题。据我所知,我可以使用限定符更改类实现,但我需要在使用此实现的任何地方更改它。在一个地方做这件事的最佳解决方案是什么?凭借我对Java EE的小知识,我发现了这个。
让我们想象一下,我们正在创建简单的Calculator应用程序。我们需要创建几个类:
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;
}
这是正确的解决方案吗?如果我错了或者有更好的解决方案,请纠正我。谢谢!
答案 0 :(得分:11)
这里有几个问题。
@Alternatives
。@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
但是,当他希望计算机实现被CDI管理时,OP不应该使用@Produces。
@Avinash Singh - CDI管理 @Produces
以及他们返回的任何内容,只要它是调用该方法的CDI即可。如果您愿意,请参阅this section of the spec。这包括返回`@ ... Scoped bean,它将支持依赖注入,生命周期回调等。
我在这里忽略了一些细节,所以请考虑以下两点:
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中,您只需@Inject
使用CDI,就像这样:
@Inject PaymentService paymentService;
请注意,在定义生产者字段之前,PaymentService
将无法注入 CDI方式。但它总是以旧方式可用。此外,在任何一种情况下,Web服务都不受CDI管理,但定义生产者字段只是使该Web服务引用可用于注入CDI方式。
答案 1 :(得分:11)
如果要使用工厂方法在代码中交换实现,那么工厂方法是管理bean而不是CDI,因此实际上不需要@Calculator
。
@ApplicationScoped
public class CalculatorFactory implements Serializable {
enum CalculatorType{MiniCaculator,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(){}
}
但是如果您希望使用@PostConstruct等进行注入和生命周期管理,您的Caclulator bean可以进行CDI管理,那么您可以使用以下方法之一。
方法1:
优势:您可以避免使用@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);
.....
}
}