Search code examples
javacdiweld

CDI Scope and Producers


Can someone explain the role of CDI Scope annotations when it comes to Producers? They don't seem to accomplish anything.

@Produces
public Thing thingMaker() {
    System.out.println("Making thingmaker");
    return new ThingBean("thingMaker");
}

@Produces
@RequestScoped
public Thing thingMakerReq() {
    System.out.println("Making thingmakerReq");
    return new ThingBean("thingMakerReq");
}

These, naturally, give this (elided) error on startup.

WELD-001409: Ambiguous dependencies for type Thing. Possible dependencies:

  • Producer Method [Thing] with qualifiers [@Any @Default] declared as [[BackedAnnotatedMethod] @Produces public pkg.test.ThingProducer.thingMaker()],
  • Producer Method [Thing] with qualifiers [@Any @Default] declared as [[BackedAnnotatedMethod] @Produces @RequestScoped public pkg.test.ThingProducer.thingMakerReq()]

So, even though the `RequestScoped is noted as part of the producer method, they're not qualifiers.

So I'm just not sure what their role is when it comes to producer methods.


Solution

  • CDI scope annotations on producer methods define the scope of the produced bean; so:

    @Produces // produces Thing in the default scope, i.e. @Dependent
    public Thing thingMaker() {
        System.out.println("Making thingmaker");
        return new ThingBean("thingMaker");
    }
    
    @Produces // produces Thing in request scope
    @RequestScoped
    public Thing thingMakerReq() {
        System.out.println("Making thingmakerReq");
        return new ThingBean("thingMakerReq");
    }
    

    If it was only these two methods, they would be able to coexist peacefully. The problem arises when you want to inject a Thing, as:

        @Inject
        private Thing thing;
    

    CDI searches its namespace and finds more than one bean that can satisfy this injection point; not knowing what to do, it fails (with WELD-001409 in the specific case where Weld is the CDI implementation). The following would be perfectly legal for example:

        @Inject
        private Instance<Thing> things;
    

    Instance can give you a collection of beans that satisfy the injection point and you can pick any of them to work with.

    Now, qualifiers are a different thing, related to how CDI finds beans that satisfy an injection point.

    First, to get the misunderstanding out of the way: scope annotations are not qualifiers. You can notice that in CDI's message, "with qualifiers [@Any @Default] declared as [[... @RequestScoped ...]". This also means that you cannot request a bean from a specific scope in an injection point. And the scope of the bean containing the injection point does not play any role in the selection of the injected bean either. Scope is an implementation detail of the bean: let's say you have an @ApplicationScoped bean, then at some point you realize that it needs request-level information to implement some functionality. You can change its scope and the beans using it should not care, they will continue working without change.

    Qualifiers are a way to disambiguate dependencies, when the type is not enough, like in your case. A frequent example is when having many configuration properties, represented as strings. Let's say a DB user name and password, both of type String: (WARNING: naive example, qualifier with binding attributes would be more appropriate, see e.g. Microprofile config)

        @Inject
        @DbUsername // this is the qualifier
        private String dbUsername;
    
        @Inject
        @DbPassword // this is the qualifier
        private String dbPassword;