CDI Ambiguous dependency with @Produces - why?
Asked Answered
I

3

13

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

Internationale answered 10/4, 2014 at 8:16 Comment(0)
I
16

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.

Ipoh answered 10/4, 2014 at 10:12 Comment(1)
@Alternative allow you to propose different version for the same bean (type) and activate it by configuration in a config file. It can be use to have a different ben in dev/test and in production for instance, changing it only in the beans.xml file.Ipoh
A
11

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.

Aramen answered 17/3, 2016 at 9:52 Comment(0)
P
5

Another possibility would be to create non default constructor in the Car class like this:

public Car {
    private String name = "NormalCar";

    public Car(String name) {
        this.name = name;
    }
    ...
}

by removing default constructor, Car class can no longer be used to create instances used by injection.

And change your producer method to

@Produces
public Car getCar() {
    if(isBatmanCar) {
        return new Car("BatmanCar");
    }
    return new Car("NormalCar");
}

then producer method would be the only way to create your Cars.

This way can be used, when you know that you would always need customized instance and you dont need default constructor. But usually Antoine solution is more useful.

Pilate answered 6/9, 2014 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.