Dynamic CDI producers

CDI has the well known concept of producers. Simply put a producer is a kind of general factory method for some type. It's defined by annotating a method with @Produces. An alternative "factory" for a type is simply a class itself; a class is a factory of objects of its own type.

In CDI both these factories are represented by the Bean type. The name may be somewhat confusing, but a Bean in CDI is thus not directly a bean itself but a type used to create instances (aka a factory). An interesting aspect of CDI is that those Bean instances are not just internally created by CDI after encountering class definitions and producer methods, but can be added manually by user code as well.

Via this mechanism we can thus dynamically register factories, or in CDI terms producers. This can be handy in a variety of cases, for instance when a lot of similar producer methods would have to be defined statically, or when generic producers are needed. Unfortunately, generics are not particularly well supported in CDI. Instead of trying to create a somewhat generic producer an alternative strategy could be to actually scan which types an application is using and then dynamically create a producer for each such type.

The following code gives a very bare bones example using the plain CDI API:

public class DynamicIntegerProducer implements Bean<Integer> {

    @SuppressWarnings("all")
    public static class DefaultAnnotationLiteral extends AnnotationLiteral<Default> implements Default {
        private static final long serialVersionUID = 1L;
    }

    @Override
    public Class<?> getBeanClass() {
        return Integer.class;
    }

    @Override
    public Set<Type> getTypes() {
        return new HashSet<Type>(asList(Integer.class, Object.class));
    }

    @Override
    public Integer create(CreationalContext<Integer> creationalContext) {
        return new Integer(5);
    }

    @Override
    public Set<Annotation> getQualifiers() {
        return singleton((Annotation) new DefaultAnnotationLiteral());
    }

    @Override
    public Class<? extends Annotation> getScope() {
        return Dependent.class;
    }

    @Override
    public Set<Class<? extends Annotation>> getStereotypes() {
        return emptySet();
    }

    @Override
    public Set<InjectionPoint> getInjectionPoints() {
        return emptySet();
    }

    @Override
    public boolean isAlternative() {
        return false;
    }

    @Override
    public boolean isNullable() {
        return false;
    }

    @Override
    public String getName() {
        return null;
    }

    @Override
    public void destroy(Integer instance, CreationalContext<Integer> creationalContext) {

    }
}
There are a few things to remark here. First of all the actual producer method is create. This one does nothing fancy and just returns a new Integer instance (normally not a good idea to do it this way, but it's just an example). The getTypes method is used to indicate the range of types for which this dynamic producer produces types. In this example it could have been deducted from the generic class parameter as well, but CDI still wants it to be defined explicitly.

The getQualifiers method is somewhat nasty. Normally if no explicit qualifiers are used in CDI then the Default one applies. This default is however not implemented in the core CDI system it seems, but is done by virtue of what this method returns. In our case it means we have to explicitly return the default qualifier here via an AnnotationLiteral instance. These are a tad nasty to create, as they require a new class definition that extends AnnotationLiteral and the actual annotation needs to be present as both a (super) interface AND as a generic parameter. To add insult to injury, Eclipse in particular doesn't like us doing this (even though it's the documented approach in the CDI documentation) and cries hard about this. We silenced Eclipse here by using the @SuppressWarnings("all") annotation. To make the code even more nasty, due to the way generics and type inference work in Java we have to add an explicit cast here (alternatively we could have used Collections.<Annotation>singleton).

For the scope we can't return a null either, but have to return the CDI default as well if we want that default. This time it's an easy return. For the scope and stereo types we can't return a null if we don't use them, but have to return an empty set. The isNullable method (deprecated since CDI 1.1) can return false. Finally, getName is the only method that can return a null.

Dynamic producers like this have to be added via a CDI extension observing the AfterBeanDiscovery event:

public class DynamicProducerExtension implements Extension {
    public void afterBean(final @Observes AfterBeanDiscovery afterBeanDiscovery) {
        afterBeanDiscovery.addBean(new DynamicIntegerProdcuer());
    }
}

As with all CDI extensions the extension class has to be registered by putting its FQN in META-INF/services/javax.enterprise.inject.spi.Extension.

After doing this, injection can be done as usual, e.g.:

@Singleton
@Startup
public class TestBean {

    @Inject
    private Integer integer;

    @PostConstruct
    public void init() {
        out.println(integer);
    }
}

Deploying an application with only the code shown in this post will print 5 in the logs ;)

Arjan Tijms

Comments

Popular posts from this blog

Implementing container authentication in Java EE with JASPIC

What’s new in Jakarta Security 3?

Jakarta EE Survey 2022