如何在多个类实现中使用CDI限定符



我是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;
}

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

这里有几个问题。

  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不应该使用@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);
          .....
        }
        }

相关内容

  • 没有找到相关文章

最新更新