10

我是 Java EE/JSF 的新手,现在阅读 CDI 限定符 - 更改类实现的可能性。这很好,但我有一个问题。据我了解,我可以使用限定符更改类实现,但我需要在使用此实现的任何地方更改它。在一个地方进行此操作的最佳解决方案是什么?凭借我对 Java EE 的一点了解,我发现了这一点。

假设我们正在创建简单的计算器应用程序。我们需要创建几个类:

  1. Calculator(计算器的基本实现)
  2. ScientificCalculator(计算器的科学实现)
  3. MiniCalculator(潜力最小)
  4. MockCalculator(用于单元测试)
  5. 限定符@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;
}

这是正确的解决方案吗?如果我错了或者有更好的解决方案,请纠正我。谢谢!。

4

2 回答 2

16

这里有几个问题。

  1. 在整个应用程序中更改所需实现的最佳方法是什么?调查@Alternatives
  2. 每个实现都需要一个限定符吗?不,请参阅答案以获得冗长而详细的解释。
  3. 我应该使用生产者来决定注入哪个实现吗?可能是您想要的解决方案,但我对此表示怀疑。生产者通常用于执行某种无法在构造函数 / 中完成的初始化@PostConstruct。您还可以使用它来检查注入点并在运行时决定注入什么。有关一些线索,请参见链接 2。
  4. 这个解决方案正确吗?这会起作用,但是您仍然必须弄乱代码才能更改实现,因此请首先考虑 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。如果您愿意,请参阅规范的这一部分。这包括返回 `@...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 {
    ...
}

然后, any for any将被注入,@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 方式注入。

于 2013-03-16T16:58:17.233 回答
12

如果您想使用工厂方法交换代码中的实现,那么您的工厂方法是管理 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")

缺点:如果名称从 say 更改为 ,编译器不会给出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);
          .....
        }
        }
于 2013-03-16T18:12:56.733 回答