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

时间:2013-03-16 16:05:05

标签: jsf java-ee jsf-2 dependency-injection cdi

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

让我们想象一下,我们正在创建简单的Calculator应用程序。我们需要创建几个类:

  1. Calculator(计算器的基本实现)
  2. ScientificCalculator(计算器的科学实施)
  3. MiniCalculator(潜力最小)
  4. MockCalculator(单元测试)
  5. 限定符@Calculator(将指示计算器的实际实现;我应该为每个实现创建限定符吗?)
  6. 这是一个问题。我有四个计算器实现,我想在少数几个地方使用其中一个,但每次只使用一个(在初始项目阶段我会使用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;
    }
    

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

2 个答案:

答案 0 :(得分:11)

这里有几个问题。

  1. 在整个应用程序中更改所需实现的最佳方法是什么?查看@Alternatives
  2. 每次实施都需要限定符吗?不,请参阅this回答,以获得详尽而详细的解释。
  3. 我应该使用生产者来决定注入哪个实现?可能是你想要的解决方案,但我对此表示怀疑。生成器通常用于执行某些无法在构造函数/ @PostConstruct中完成的初始化。您还可以使用它来检查注入点并做出运行时决定注入的内容。请参阅链接2.了解一些线索。
  4. 此解决方案是否正确?这将有效,但你仍然需要弄乱代码来改变实现,所以首先考虑1. @Calculator Calculator似乎也很多余。再次,请参阅2的链接。

    @ApplicationScoped
    public class CalculatorFctory implements Serializable {
        private Calculator calc;
    
        @Produces @Calculator Calculator getCalculator() {
            return new Calculator();
        }
    }
    
  5. <强>更新

    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 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中,您只需@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);
          .....
        }
        }