我是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;
}
这是正确的解决方案吗?如果我错了,或者有更好的解决方案,请纠正我。谢谢!。
这里有几个问题。
- 在整个应用程序中更改所需实现的最佳方法是什么?查看
@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
但是OP不应该使用@Produces当他想要CDI管理计算器实现的时候。
@Avinash Singh - CDI管理@Produces
以及他们返回的任何东西,只要它是CDI调用该方法。如果您愿意,请参阅规范的这一部分。这包括返回' @…有作用域的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中,您可以简单地使用CDI @Inject
它,像这样:
@Inject PaymentService paymentService;
注意,在定义生产者字段之前,PaymentService
不能以CDI方式注入。但它总是可用的老方法。而且,在这两种情况下,web服务都不是由CDI管理的,但定义生产者字段只是使web服务引用可以通过CDI方式注入。
如果您想使用工厂方法交换代码中的实现,那么您的工厂方法是管理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(){}
}
然而如果你想让你的计算器bean被CDI管理用于注入和使用@PostConstruct等的生命周期管理,那么你可以使用以下方法之一:
方法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;
}
所有三种方法都可以用于测试,假设您有一个名为calculatortest的类,
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);
.....
}
}