如何使用具有多个类实现的CDI限定符?

pep*_*uch 9 jsf dependency-injection java-ee cdi jsf-2

我是Java EE/JSF的新手,现在阅读有关CDI限定符的内容 - 更改类实现的可能性.这很棒,但我有一个问题.据我所知,我可以使用限定符更改类实现,但我需要在使用此实现的任何地方更改它.在一个地方做这件事的最佳解决方案是什么?凭借我对Java EE的小知识,我发现了这一点.

让我们假设我们正在创建简单的Calculator应用程序.我们需要创建几个类:

  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();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用计算器的类

public class CalculateUserAge {
    @Calculator
    @Inject
    private Calculator calc;
}
Run Code Online (Sandbox Code Playgroud)

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

rdc*_*rng 15

这里有几个问题.

  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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

更新:

除了用于依赖性解析的类型之外,CDI还使用限定符.换句话说,只要只有一种类型与注入点的类型匹配,单独的类型就足够了,不需要限定符.当单独的类型不够时,限定词就可以消除歧义.

例如:

public class ImplOne implements MyInterface {
    ...
}

public class ImplTwo implements MyInterface {
    ...
}
Run Code Online (Sandbox Code Playgroud)

为了能够注入任一实现,您不需要任何限定符:

@Inject ImplOne bean;
Run Code Online (Sandbox Code Playgroud)

要么

@Inject ImplTwo bean;
Run Code Online (Sandbox Code Playgroud)

这就是我说@Calculator Calculator多余的原因.如果为每个实现定义一个限定符,那么你获得的数据并不多,也可以只使用该类型.说,两个限定符@QualOne@QualTwo:

@Inject @QualOne ImplOne bean;
Run Code Online (Sandbox Code Playgroud)

@Inject @QualTwo ImplTwo bean;
Run Code Online (Sandbox Code Playgroud)

上面的示例没有获得任何结果,因为在前面的示例中不存在任何歧义.

当然,您可以在无法访问特定实现类型的情况下执行此操作:

@Inject @QualOne MyInterface bean; // to inject TypeOne
Run Code Online (Sandbox Code Playgroud)

@Inject @QualTwo MyInterface bean; // to inject TypeTwo
Run Code Online (Sandbox Code Playgroud)

但是当他希望计算机实现被CDI管理时,OP不应该使用@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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

public class SomeProducer {

    @Produces
    public MyInterface get() {
        if (conditionOne()) {
            return new ImplOne();
        } else if (conditionTwo()) {
            return new ImplTwo();
        } else {
            return new ImplThree;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在第一个例子,CDI会管理的生命周期(即@PostConstruct@Inject的东西从生产者返回的支持),但在第二个它不会.

回到最初的问题 - 在不必修改源代码的情况下,在实现之间切换的最佳方法是什么?假设您希望更改适用于应用程序.

@Default
public class ImplOne implements MyInterface {
    ...
}

@Alternative
public class ImplTwo implements MyInterface {
    ...
}

@Alternative
public class ImplThree implements MyInterface {
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,任何对任何@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>
Run Code Online (Sandbox Code Playgroud)

指定,在这种情况下ImplTwo将被注入到处.

进一步更新

Java EE环境中确实存在不受CDI管理的事情,例如EJB和Web服务.

您如何将Web服务注入CDI托管bean?这很简单:

@WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;
Run Code Online (Sandbox Code Playgroud)

就是这样,您可以在CDI以外的地方管理付款服务.

但是,如果您不想在@WebServiceRef(lookup="java:app/service/PaymentService")任何需要的地方使用它,该怎么办?如果你只想按类型注入怎么办?然后你在某个地方这样做:

@Produces @WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;
Run Code Online (Sandbox Code Playgroud)

并且在需要引用该支付服务的任何CDI bean中,您可以简单地@Inject使用CDI,如下所示:

@Inject PaymentService paymentService;
Run Code Online (Sandbox Code Playgroud)

请注意,在定义生产者字段之前,PaymentService不能用于注入CDI方式.但它始终以旧方式提供.此外,在任何一种情况下,Web服务都不由CDI管理,但定义生产者字段只是使该Web服务引用可用于注入CDI方式.


Avi*_*ngh 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(){}
}
Run Code Online (Sandbox Code Playgroud)

但是, 如果您希望使用@PostConstruct等对Caclulator bean进行CDI管理以进行注射和生命周期管理,那么您可以使用以下方法之一.

方法1:

优点:您可以避免使用创建注释@Named("miniCalculator")

缺点:如果有一个名称的改变,从说编译器不会给这种方法的错误miniCalculatorxyzCalculator.

@Named("miniCalculator")
class MiniCalculator implements Calculator{ ... }

@ApplicationScoped
public class CalculatorFactory implements Serializable {
    private calc;

    @Inject 
    void setCalculator(@Named("miniCalculator") Caclulator calc) {
        this.calc = calc;
    }
}
Run Code Online (Sandbox Code Playgroud)

方法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;
    }
}
Run Code Online (Sandbox Code Playgroud)

方法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;
}
Run Code Online (Sandbox Code Playgroud)

所有这三种方法都可用于测试,比如你有一个名为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);
      ....
    } 
    }
Run Code Online (Sandbox Code Playgroud)

如果你想在你的测试中使用模拟实现,那么做这样的事情,

   class CalculatorTest{        
        Caclulator calc;        
        @PostConstruct 
                init() {
                    this.calc = createMockCaclulator();
                }
        @Test
        public void testAddition(int a,int b){
          calc.add(a,b);
          .....
        }
        }
Run Code Online (Sandbox Code Playgroud)