如何将 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 instanceImplOne 都会被注入,除非

<?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 限定符与多个类实现一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

C++杂谈const限定符与const指针

限制 C 中的限定符与 LLVM IR 中的 noalias 属性

CDI - 生产者和限定符不在生产的对象上

我的指针导致“多次使用相同类型的限定符”警告?

跟我一起学C++之从C到C++(const限定符)

CDI Features