How to use CDI qualifiers with multiple class implementations?

26,095

Solution 1

If you want to swap the implementation in your code using a factory method then your factory method is managing the beans and not CDI and so there is really no need for @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(){}
}

However if you want your Caclulator beans to be CDI managed for injections and life cycle management using @PostConstruct etc then you can use one of the below approaches.

Approach 1 :

Advantage :You can avoid creating annotation using @Named("miniCalculator")

Disadvantage : compiler will not give an error with this approach if there is a name change from say miniCalculator to 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;
    }
}

Approach 2 : Recommended (Compiler keeps track of injection if any injection fails)

@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;
    }
}

Approach 3: If you are using a factory method to generate your object.Its lifecycle wont be managed be CDI but the Injection will work fine using @Inject .

@ApplicationScoped
public class CalculatorFactory implements Serializable {
    private Calculator calc;    
    @Produces Calculator getCalculator() {
        return new Calculator();
    }
}    
public class CalculateUserAge {
    @Inject
    private Calculator calc;
}

All three approaches will work for testing , say you have a class named 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);
      ....
    } 
    }

if you want to use a mock implementation in your test then do something like this,

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

Solution 2

There are several issues here.

  1. What is the best way to change the desired implementation in the entire application? Look into @Alternatives.
  2. Do I need a qualifier for each implementation? No, see this answer for a lengthy and detailed explanation.
  3. Should I use a producer to decide which implementation is injected? Could be the solution you want, but I doubt it. Producers are generally used to perform some sort of initialization that can't be done in the constructor / @PostConstruct. You could also use it to inspect the injection point and make runtime decisions about what to inject. See the link 2. for some clues.
  4. Is this solution correct? This will work, but you'll still have to mess with the code to change the implementation, so consider 1. first. Also @Calculator Calculator seems highly redundant. Again, see the link at 2.

    @ApplicationScoped
    public class CalculatorFctory implements Serializable {
        private Calculator calc;
    
        @Produces @Calculator Calculator getCalculator() {
            return new Calculator();
        }
    }
    

Update:

CDI uses qualifiers in addition to types for dependency resolution. In other words, as long as there is only one type that matches the type of the injection point, types alone are enough and qualifiers are not needed. Qualifiers are there for disambiguation when types alone are not enough.

For example:

public class ImplOne implements MyInterface {
    ...
}

public class ImplTwo implements MyInterface {
    ...
}

To be able to inject either implementation, you don't need any qualifiers:

@Inject ImplOne bean;

or

@Inject ImplTwo bean;

That's why I say @Calculator Calculator is redundant. If you define a qualifier for each implementation, you're not gaining much, might as well just use the type. Say, two qualifiers @QualOne and @QualTwo:

@Inject @QualOne ImplOne bean;

and

@Inject @QualTwo ImplTwo bean;

The example directly above does not gain anything since in the previous example no dis-ambiguity existed already.

Sure, you can do this for cases where you don't have access to particular implementation types:

@Inject @QualOne MyInterface bean; // to inject TypeOne

and

@Inject @QualTwo MyInterface bean; // to inject TypeTwo

However OP shouldn't be using @Produces when he wants Calculator implementations to be CDI managed.

@Avinash Singh - CDI manages @Produces as well as anything they return, as long as it is CDI that calls the method. See this section of the spec if you please. This includes returning `@...Scoped beans which will support dependency injection, life-cycle callbacks, etc.

I overlooked some details here, so consider the following two:

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

and

public class SomeProducer {

    @Produces
    public MyInterface get() {
        if (conditionOne()) {
            return new ImplOne();
        } else if (conditionTwo()) {
            return new ImplTwo();
        } else {
            return new ImplThree;
        }
    }
}

Then, in the first example, CDI will manage the life cycle (i.e. @PostConstruct and @Inject support) of what's returned from the producer, but in the second one it will not.

Back to the original question - what's the best way to switch between implementations without having to modify the source? The assumption is that you want the change to be application wide.

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

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

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

Then, any for any @Inject MyInterface instance, ImplOne will be injected, unless

<?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>

is specified, in which case ImplTwo will be injected everywhere.

Further Update

There are indeed things in the Java EE environment that are not managed by CDI, such as EJBs and web services.

How would you inject a web service into a CDI managed bean? It's simple really:

@WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;

That's it, there you'll have a valid reference to the payment service which is managed outside CDI.

But, what if you didn't want to use the full @WebServiceRef(lookup="java:app/service/PaymentService") everywhere you need it? What if you only want to inject it by type? Then you do this somewhere:

@Produces @WebServiceRef(lookup="java:app/service/PaymentService")
PaymentService paymentService;

and in any CDI bean that needs a reference to that payment service you can simply @Inject it using CDI like this:

@Inject PaymentService paymentService;

Note that before defining the producer field, PaymentService wouldn't be available for injection the CDI way. But it is always available the old way. Also, in either case the web service is not managed by CDI but defining the producer field simply makes that web service reference available for injection the CDI way.

Share:
26,095
pepuch
Author by

pepuch

Updated on July 19, 2022

Comments

  • pepuch
    pepuch almost 2 years

    I'm new in Java EE/JSF and now read about CDI qualifiers - the possibility to change class implementation. This is great but I have got one question. As far as I understand I can change class implementation using qualifier but I need to change it everywhere I use this implementation. What is the best solution to do it in one place? With my small knowledge about Java EE I figured out this one.

    Lets imagine that we are creating simple Calculator application. We need to create few classes:

    1. Calculator (basic implementation of calculator)
    2. ScientificCalculator (scientific implementation of calculator)
    3. MiniCalculator (with minimum potentiality)
    4. MockCalculator (for unit tests)
    5. Qualifier @Calculator (will indicate to the actual implementation of calculator; should I create qualifier for each implementation?)

    Here is the question. I've got four implementations of calculator and I want to use one of them in few places but only one at time (in the initial project phase I will use MiniCalculator, then Calculator and so on). How can I change implementation without change code in every place where object is injected? Should I create factory which will be responsible for injecting and will work as method injector? Is my solution correct and meaningful?

    Factory

    @ApplicationScoped
    public class CalculatorFctory implements Serializable {
        private Calculator calc;
    
        @Produces @Calculator Calculator getCalculator() {
            return new Calculator();
        }
    }
    

    Class which uses Calculator

    public class CalculateUserAge {
        @Calculator
        @Inject
        private Calculator calc;
    }
    

    Is this the correct solution? Please correct me if I'm wrong or if there is a better solution. Thanks!.

  • rdcrng
    rdcrng about 11 years
    Please see my update as per your comment. Also, producers and anything they return are managed by CDI...
  • rdcrng
    rdcrng about 11 years
    You seem to be confusing plain annotations with CDI qualifiers. Yes, CDI qualifiers are annotations, but not all annotations are CDI qualifiers. To be considered by CDI in the dependency resolution process, an annotation must inherit from docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html. As such, annotating a producer method with @WebServiceRef has no effect for CDI, as it does not inherit from Qualifier.
  • rdcrng
    rdcrng about 11 years
    So, @Produces @WebServiceRef(lookup="java:app/service/PaymentService") MyWebServiceImpl get() is equivalent to @Produces MyWebServiceImpl get() as far as CDI is concerned.
  • rdcrng
    rdcrng about 11 years
    There is no reason to annotate anything with @Produces if you're going to call it yourself.
  • Avinash Singh
    Avinash Singh about 11 years
    How would you inject this service to a CDI bean ? We would be using @Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") {} @Inject @myservice MyWebService; In this case container does not manage MyWebServiceImpl lifecycle , it is merely injecting it
  • Avinash Singh
    Avinash Singh about 11 years
    How would you inject this service to a CDI bean ? We would be using @Produces @myservice @WebServiceRef(lookup="java:app/service/PaymentService") {} @Inject @myservice MyWebService; In this case container does not manage MyWebServiceImpl lifecycle , it is merely injecting it
  • Avinash Singh
    Avinash Singh about 11 years
    Thanks for your answer. There is one more scenario , say we have two PamentService of Webservice and we want to use @Produces @service1 @WebServiceRef(lookup="java:app/service/PaymentService1") PaymentService paymentService; then we would be needing a qualifier @service1 in variable declaraion otherwise it would throw error as it wont be able to resolve the correct implementation. Either way the lifecycle of this web service or any bean is not managed until we declare the bean itself with annotation @Named
  • rdcrng
    rdcrng about 11 years
    Yes, I think in this case it will complain about unsatisfied dependencies. Which is why I was strongly advising against using qualifiers in case resolution by type is enough (the point about redundancy). That's not to say that qualifiers shouldn't be used, there are plenty of instances when they are very useful. :) As for what is managed by CDI - it doesn't have anything to do with named. CDI by default scans then entire deployment and is capable of injecting all the types it finds that are valid beans, see docs.jboss.org/cdi/spec/1.0/html_single/#whatclassesarebeans‌​.
  • rdcrng
    rdcrng about 11 years
    So you can inject anything CDI detects as a valid bean. So it could technically be a PaymentProcessor as well depending on the implementation, but it will not really be a JAX-WS web service since CDI won't be aware of how to properly manage it.
  • rdcrng
    rdcrng about 11 years
    Also, you were partly right initially as to whether things returned from a producer are CDI managed or not, see my update right below the strike-through text please.
  • Avinash Singh
    Avinash Singh about 11 years
    thanks for the detailed answer , I have corrected my answer . I was not very clear on what is the use of @Produces @xyz , but based on my understanding now it seems like it is used to differentiate two @Produces methods returning same Parent object type
  • rdcrng
    rdcrng about 11 years
    I think that between the two of us and the comment we provided enough details :)
  • Avinash Singh
    Avinash Singh about 11 years
    @Produces beans are still managed by CDI unless it is instantiated within the method as new(). I have corrected my definition of when to use approach 3.