Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CDI Ambiguous dependency with @Produces - why?

I am using code like below:

public Configuration {

    private boolean isBatmanCar = someMethod(...);

    @Produces
    public Car getCar(@New Car car) {
        if(isBatmanCar) {
            car.setName("BatmanCar");
        }
        return car;
    }
}

public Car {
    private String name = "NormalCar";

    public void setName(String name) {
        this.name = name;
    }
}

public Demo {
    @Inject
    Car car;

    // rest of code
}

When I deploy an application to glassfish (Java EE 6 btw) I get

AmbiguousResolutionException: WELD-001318 Cannot resolve an ambiguous dependency between (...) Car with qualifiers [@Any @Default] (...) Producer Method [Car] with qualifiers [@Any @Default]

I know that when I add @Alternative to Car class it will work, but I wonder if this is the proper way to do it, and why do I have to do it?

Can you tell me what is the correct usage of @Produces in such case?

I'm using Java EE 6, CDI 1.0, EJB 3.1, Glassfish 3.2

like image 885
Dariusz Mydlarz Avatar asked Apr 10 '14 08:04

Dariusz Mydlarz


People also ask

What is dependency ambiguity?

Ambiguous dependency. The dependency relation of certain adjuncts (expressed by adverbs or prepositional phrases) is not always unambiguous: they do not necessarily modifiy only one element within the sentence but they can have a relation to several elements at the same time.

What is CDI in JSF?

Contexts and Dependency Injection (CDI), specified by JSR-299, is an integral part of Java EE 6 and provides an architecture that allows Java EE components such as servlets, enterprise beans, and JavaBeans to exist within the lifecycle of an application with well-defined scopes.


2 Answers

The error comes from the fact that you have 2 beans of type Car, one being the class, the other being the producer. You have 2 obvious solutions to resolve the ambiguity:

First, you put the logic behind isBatmanCar field in the original class (in a constructor or a @PostConstruct method for instance) and remove your producer. That would left only one Car bean.

Or if you really want to have 2 bean or can't avoid it you should create a qualifier for your produced bean:

 @Target({ TYPE, METHOD, PARAMETER, FIELD })
 @Retention(RUNTIME)
 @Documented
 @Qualifier
 public @interface BatmanChecked {
 }

and use it on producer,

@Produces
@BatmanChecked
public Car getCar(Car car) {...}

to be able to inject the type of car

@Inject
Car stdCar;

@Inject
@BatmanChecked
Car batCheckedCar;

Qualifier is the natural option to resolve ambiguous injection. Using @Alternative also works but it's more a trick here than a good practice.

Last remark: @New is not necessary here, since your Car bean has no scope (so is @Dependent scoped). @New is only useful when a producer inject a bean with a scope that is not @Dependent. That said, this code is not very useful if your Car class is in scope @Dependent.

like image 51
Antoine Sabot-Durand Avatar answered Oct 21 '22 14:10

Antoine Sabot-Durand


Using @Alternative works but should only be used if you want to be able to activate it through beans.xml.

Suppressing the default constructor of your bean also works but you won't be able to use your bean in another scope than @RequestScoped.

Using your own qualifier works but isn't very useful if you have only one implementation and just want to be able to instantiate your bean with a producer rather than with its constructor.

The easiest way is to annotate your bean @Any :

@Any
public class Car {
}
...
@Produces
public Car getCar() {
    return new Car();
}
...
@Inject
Car car;

Things that you have to keep in mind :

  • All beans and producers are always implicitly qualified @Any
  • Beans and producers without explicit qualifiers are implicitly qualified @Default
  • Beans and producers with explicit qualifiers are no longer implicitly qualified @Default
  • Injection points without explicit qualifiers are implicitly qualified @Default, but not @Any

Regarding all this, the same code as above explicitly qualified looks like this :

@Any
public class Car {
}
...
@Produces
@Any
@Default
public Car getCar() {
    return new Car();
}
...
@Inject
@Default
Car car;

It becomes more obvious that the bean's default constructor isn't a valid possibility for the injection point and the producer is a valid possibility.

like image 39
Nicolas Lepage Avatar answered Oct 21 '22 13:10

Nicolas Lepage