Next Presso (CDI, Java EE and friends)

Nobody expects the CDI portable extensions

Posted by Antoine Sabot-Durand on Feb 06, 2017 | Comments
rainbow lego

Portable extensions are probably the coolest feature of CDI.

Unfortunately, this gem is a bit hidden in the spec and some developers totally missed it while others may wrongly think that using extension is too complicated.

With this article, I’ll try to show that everybody can use extension in their work as it can be used to provide simple feature or advanced integration mechanism.

But first things first, let’s answer the basic question "why would you need to develop a portable extension".

What can I do with an extension

At boot time CDI scans most of the class in the classpath to create its configuration and the bean graph. The configuration and meta data that are created at this moment are created from static content (class file) and may need some more dynamic content.

That’s where portable extension gets in.

A CDI portable extension allows you to hook on the CDI scanning process which occurs at boot time and modify or add information to the meta data created by the CDI container.

That includes add Beans, remove classes from set of type that should become bean, add producers, observers and most of the SPI elements that exist in CDI.

So to make short, extensions are how developers can configure CDI and override default behaviour created from reading the classes.

Getting started with CDI extension

CDI portable extensions are based on Java SE service-provider.

Service interface is javax.enterprise.inject.spi.Extension, so to add an extension you’ll need to create a class implementing javax.enterprise.inject.spi.Extension interface and add this class qualified name to the META-INF/services/javax.enterprise.inject.spi.Extension service provider text file.

Extension feature is defined by adding observers on specific lifecycle event of the CDI container. At boot time, the CDI container will use service provider mechanism to discover all extensions and register these observers.

This approach allows you to hook on internal lifecycle steps of the container and modify their outcome.

Let’s check what are these step.

Extension step by step

To understand how to work with extensions, we can start by splitting container lifecycle in 4 Big steps:

The major steps of the CDI container lifecycle
The major steps of the CDI container lifecycle

Eah of these step (except "Application running") contains one or more events for which you can define one or more observers in your extension to hook on CDI elements discovery and meta data building.

Let’s focus on each of these steps and describe the events that you can use in each one.

Some examples given below will use new features coming from CDI 2.0. I’ll explain how same result can be obtained in CDI 1.2.

Types discovery

Type discovery can be illustrated like this

Types discovery
Types discovery
In this schema (and next ones), yellow boxes are the in which an extension can observes an event and perform actions, grey ones are simplification of internal container behaviour.

The goal of this step is to create a set of AnnotatedType which will be candidate to become beans.

Ths set can be filled explicitly in a BeforeTypeDiscovery or AfterDiscovery observers

It is also filled automatically by the container class scanning process on which developer can place a hook to alter the discovered by using a ProcessAnnotatedType observer.

Let’s see in detail how all this work.

Adding types before scanning (BeforeBeanDiscovery event)

Before CDI container start automatic types scan on the class path, it fires the BeforeBeanDiscovery event.

Observing this events allows addition of a specific type to the set of discovered types or addition of specific CDI annotations like qualifier, stereotype or interceptor binding.

public interface BeforeBeanDiscovery {
  void addQualifier(Class<? extends Annotation> qualifier); (1)
  void addQualifier(AnnotatedType<? extends Annotation> qualifier); (1)
  void addScope(Class<? extends Annotation> scopeType, boolean normal, boolean passivating); (2)
  void addStereotype(Class<? extends Annotation> stereotype, Annotation... stereotypeDef); (3)
  void addInterceptorBinding(AnnotatedType<? extends Annotation> bindingType); (4)
  void addInterceptorBinding(Class<? extends Annotation> bindingType, Annotation... bindingTypeDef); (4)
  void addAnnotatedType(AnnotatedType<?> type, String id); (5)

  /* New methods in CDI 2.0 */
  <T> AnnotatedTypeConfigurator<T> addAnnotatedType(Class<T> type, String id); (5)
  <T extends Annotation> AnnotatedTypeConfigurator<T> configureQualifier(Class<T> qualifier); (1)
  <T extends Annotation> AnnotatedTypeConfigurator<T> configureInterceptorBinding(Class<T> bt); (4)
}
1 Add a new qualifier with an Annotation, an AnnotatedType or by using the CDI 2.0 AnnotatedTypeConfigurator
2 Add a new scope Annotation
3 Define a new Stereotype by giving its Annotation and the Annotations collection it stands for
4 Add a new interceptor binding with an Annotation and its meta annotations, an AnnotatedType or by using the CDI 2.0 AnnotatedTypeConfigurator
5 Add a new AnnotatedType from a custom AnnotatedType or by using the CDI 2.0 AnnotatedTypeConfigurator

The following example illustrate usage of this event.

public class MetricsExtension implements Extension { (1)

    public void addMetricAsQual(@Observes BeforeBeanDiscovery bbd) { (2)
        bbd.addQualifier(Metric.class); (3)
    }
}
1 defining the extension (remember to also add class FQN to META-INF/services/javax.enterprise.inject.spi.Extension text file
2 An observer for the BeforeBeanDiscovery lifecycle event
3 Declaring an annotation from a 3rd party non-CDI framework as a qualifier

The example above is a piece of the Dropwizard Metrics CDI integration extension. It declares a standard annotation (@Metrics) as a CDI qualifier.

You can also transform a non-CDI class to have it discovered as a managed bean by the container:

public class MyLegacyFrameworkService { (1)

    private Configurator config;

    public MyLegacyFrameworkService(Configurator config) {
        this.config = config;
    }
}

...

public class LegacyIntegrationExtension implements Extension {

    public void addLegacyServiceAsBean(@Observes BeforeBeanDiscovery bbd) {
        bbd.addAnnotatedType(MyLegacyFrameworkService.class,MyLegacyFrameworkService.class.getName()) (2)
                .add(ApplicationScoped.Literal.INSTANCE) (3)
                .filterConstructors(c -> c.getParameters().size() == 1)
                .findFirst().get().add(InjectLiteral.INSTANCE); (4)
    }
1 class from a legacy framework that we want to integrate into CDI programming model without changing its code
2 using an AnnotatedTypeConfigurator (new in CDI 2.0) based on the MyLegacyFrameworkService class
3 adding @ApplicationScoped scope on the AnnotatedTypeConfigurator
4 find the first constructor with one parameters and add the @Inject on it

The example above use new feature from CDI 2.0: the AnnotatedTypeConfigurator returned by one of the addAnnotatedType() methods of BeforeBeanDiscovery event. If you are in CDI 1.1 you can don the same but you’ll have to implement your own AnnotatedType to do the same in more verbose way. to configure a new AnnotatedType add a scope on it and an @Inject annotation on one of its constructors. At the end of observer invocation, the container will automatically build the matching AnnotatedType from this configurator and add it to the discovered type set.

Automatic types scanning process

After this first event, the container starts a process of type discovery in the application classpath.

This scanning can be configured differently for each bean archive (i.e. jar or module) in the classpath.

Each jar in the application path may (or may not) contain a beans.xml file defining how types will be scanned by the CDI container for this bean archive.

Remember that CDI doesn’t provide a global configuration file so each of your bean archive (including the war container others bean archive) must define its discovery mode.

There are 3 discovery mode:

  • none: no type will be discovered for this bean archive

  • annotated (default mode): only class having specific annotations (bean defining annotation^) will be discovered

  • all: all types will be discovered

Discovery mode is inferred by analyzing the bean archive beans.xml file

Table 1. what is my discovery mode?
beans.xml file state discovery mode

No beans.xml

annotated

empty beans.xml

all

beans.xml using CDI 1.0 xsd

all

beans.xml using CDI 1.1 xsd

value of bean-discovery-mode attribute

You can also fine grain type discovery by using exclusion filters

In CDI 2.0 when you are working on Java SE, jars without beans.xml file are ignored by default.

ProcessAnnotatedType event

After this scanning phase, the container creates an AnnotatedType and fire the ProcessAnnotatedType event for each type discovered (except for annotations).

public interface ProcessAnnotatedType<X> { (1)
    AnnotatedType<X> getAnnotatedType(); (2)
    void setAnnotatedType(AnnotatedType<X> type); (3)
    void veto(); (4)

    /* New in CDI 2.0 */
    AnnotatedTypeConfigurator<X> configureAnnotatedType(); (3)
}
1 the event is a parameterized type allowing user to only process AnnotatedType based on a given original type
2 returns the current processed AnnotatedType
3 replaces the processed AnnotatedType by a new one defined by implementing AnnotatedType interface or with the help of an AnnotatedTypeConfigurator (new in CDI 2.0)
4 remove the processed AnnotatedType from the set of discovered type: this type won’t become a bean

This event is often use to override configuration on an existing type.

For instance the example below remove adds transactional annotation on the StandardService class in a third party library.

public class AddTranscationalToServiceExtension implements Extension {

    public void addTransactional(@Observes ProcessAnnotatedType<StandardService> pat) { (1)
        pat.configureAnnotatedType().add(new AnnotationLiteral<Transactional>(){});
    }
1 observer will only be triggered for any AnnotatedType based on StandardService type

It can also be used to veto type implementing an interface or having a specific annotation (thanks to the @WithAnnotations filter).

public class VetEntitiesExtension implements Extension {

    public void vetoEntities(@Observes @WithAnnotations(Entity.class) ProcessAnnotatedType<?> pat) { (1)
        pat.veto();
    }
1 observer will be triggered for any AnnotatedType based on any type having @Entity annotation

This last example vetoes all JPA entities in the application to avoid using them as CDI beans.

AfterTypeDiscovery event

This event closes the type discovery process

public interface AfterTypeDiscovery {
    List<Class<?>> getAlternatives(); (1)
    List<Class<?>> getInterceptors(); (1)
    List<Class<?>> getDecorators(); (1)
    void addAnnotatedType(AnnotatedType<?> type, String id); (2)

    /* New in CDI 2.0 */
    <T> AnnotatedTypeConfigurator<T> addAnnotatedType(Class<T> type, String id); (2)
}
1 these methods give you access to classes list discovered as possible alternatives beans, interceptors or decorators. You can use these inventory list to check everything you need is here or add a new class to them since these lists are mutable
2 as in BeforeBeanDiscovery you can add a custom AnnotatedType to the set of discovered AnnotatedType

The following extension checks that if LastInterceptor class was discovered as an interceptor, this one will be invoked after all other interceptors.

public class lastInteceptorExtension implements Extension {

public void lastInterceptorCheck (@Observes AfterTypeDiscovery atd) {
        List<Class<?>> interceptors = atd.getInterceptors();
        if(interceptors.indexOf(LastInterceptor.class) < interceptors.size()) {
            interceptors.remove(LastInterceptor.class);
            interceptors.add(LastInterceptor.class);
        }
    }
}

Beans discovery phase

In this phase each discovered type is analyzed to check if they are eligible to become beans.

If it’s the case a series of events are fired to allow modification of the future bean.

If the bean was not vetoed by an extension, container launch producers and observers discovring processes.

At the end of this phase, extension has opportunity to register custom beans or observers with the AfterBeanDiscovery event.

The phase ends with the validation of all the element by the container and the AfterDeploymentValidation event.

The following schema illustrates all the phase steps. While it could looks complicated at first, this process is rather easy to understand.

Beans discovery
Beans discovery

ProcessInjectionPoint event

For each injection point encountered during this process, the container will fire a ProcessInjectionPoint event. Injection points are fired for managed beans, producer methods and observer methods.

public interface ProcessInjectionPoint<T, X> { (1)
    InjectionPoint getInjectionPoint(); (2)
    void setInjectionPoint(InjectionPoint injectionPoint); (3)
    void addDefinitionError(Throwable t); (4)

    /* New in CDI 2.0 */
    InjectionPointConfigurator configureInjectionPoint(); (3)
}
1 event is a parameterized type allowing observer to target a specific class T containig the injection point or a specific injection point type X
2 returns the InjectionPoint processed by this event
3 allow replacement of processed InjectionPoint either by implementing custom InjectionPoint or using and InjectionPointConfigurator (new CDI in 2.0)
4 allows observer to abort deployment by adding a definition error

An extension can observe this event for multiple reason. For instance it can be used to collect all types for a given qualifier and later create a bean to match these injection points

public class ConvertExtension implements Extension {

    Set<Type> convertTypes = new HashSet();

    public void captureConfigTypes(@Observes ProcessInjectionPoint<?, ?> pip) {
        InjectionPoint ip = pip.getInjectionPoint();
        if (ip.getQualifiers().contains(Convert.Literal.Instance)) {
            convertTypes.add(ip.getType());
        }
    }
}

The example above will create a set of types for all injection points in the application having the @Convert qualifier.

Later it could use this collection to create custom beans matching each types found for instance.

ProcessInjectionTarget event

An InjectionTarget can be seen as a non managed bean. It mainly provides dependency injection mechanism and some callback feature.

This event is fired for all elements supporting injection.

public interface ProcessInjectionTarget<X> { (1)
    public AnnotatedType<X> getAnnotatedType(); (2)
    public InjectionTarget<X> getInjectionTarget(); (3)
    public void setInjectionTarget(InjectionTarget<X> injectionTarget); (4)
    public void addDefinitionError(Throwable t); (5)
}
1 the event is a parameterized type to target a specific base type of the InjectionTarget to process
2 returns the AnnotatedType which defined the processed InjectionTarget
3 returns the InjectionTarget processed by this event
4 allows replacing the processed InjectionTarget
5 allows observer to abort deployment by adding a definition error

Observing this event allows an extension to override the default InjectionTarget behaviour and perform specific tasks during injection like calling specific feature on a 3rd party framework.

ProcessBeanAttributes event

This event is fired before registration of a discovered bean in the container.

Observing this event allows attributes modification or registration canceling.

This event is fired for all kind of beans:

  • Managed Beans

  • Session Beans

  • Producer Fields

  • Producer Method

  • Custom Beans

public interface ProcessBeanAttributes<T> { (1)
    public Annotated getAnnotated(); (2)
    public BeanAttributes<T> getBeanAttributes(); (3)
    public void setBeanAttributes(BeanAttributes<T> beanAttributes); (4)
    public void addDefinitionError(Throwable t); (5)
    public void veto(); (6)

    /* New in CDI 2.0 */
    public BeanAttributesConfigurator<T> configureBeanAttributes(); (4)
    public void ignoreFinalMethods(); (7)
}
1 The event being a parameterized type allows observing this event only for a given type
2 returns the Annotated defining the bean (i.e an AnnotatedType for managed Bean or a session bean, an AnnotatedField or AnnotatedMethod for a producer and null for a custom bean)
3 returns the processed BeanAttributes
4 allows replacement of processed BeanAttributes either by implementing the BeanAttributes interface or by using a BeanAttributesConfigurator (new in CDI 2.0)
5 allows observer to abort deployment by adding a definition error
6 requests the container to ignore the matching bean and skip its registration
7 new method in CDI 2.0 to explicitly skip some restriction in the spec regarding proxy creation

The following extension checks that no beans was added by developer for type SpecialClass and no qualifiers will be registered since it will register a custom bean for it

public class CheckExtension implements Extension {

public void filterSpecialClassBean(@Observes ProcessBeanAttributes<SpecialClass> pba) {
        if(pba.getBeanAttributes().getQualifiers().contains(Default.Literal.INSTANCE))
            pba.veto();
    }
}

ProcessBean event

This event is fired when a bean is registered in the container.

public interface ProcessBean<X> { (1)
    public Annotated getAnnotated(); (2)
    public Bean<X> getBean(); (3)
    public void addDefinitionError(Throwable t); (4)
}
1 Parameterized type for better observer filtering
2 returns the Annotated defining the bean (i.e an AnnotatedType for managed Bean or a session bean, an AnnotatedField or AnnotatedMethod for a producer and null for a custom bean)
3 returns the created Bean
4 allows observer to abort deployment by adding a definition error

This event is mainly here to check that a specific bean is created and sometimes capture its definition for further use.

An observer on ProcessBean for all kind of bean. If you want to be more specific, you can use a child of this event to only observe the event for a specific kind of bean.

processBean hierarchy

ProcessProducer event

This event is fired for all producers find in the application.

Remember that a producer is a kind of bean. But its definition and discovery depends on the bean that contains it. In other words, producer defined in a class that will not be discovered as bean will be ignored.

It mainly allows overriding of the producing code (i.e. you can override the code written to produce a specific bean instance in the application from an extension )

public interface ProcessProducer<T, X> { (1)
    AnnotatedMember<T> getAnnotatedMember(); (2)
    Producer<X> getProducer(); (3)
    void addDefinitionError(Throwable t); (4)
    void setProducer(Producer<X> producer); (5)

    /* New in CDI 2.0 */
    ProducerConfigurator<X> configureProducer(); (5)
}
1 Parameterized type for better observer filtering. T is the bean class of the bean containing the producer, X is the type of the producer
2 returns the AnnotatedMember defining the producer (i.e an AnnotatedField for a field producer or AnnotatedMethod for a method producer)
3 returns the producer being processed
4 allows observer to abort deployment by adding a definition error
5 Allow replacement of the processed producer, either by implementing the Producer interface or using the ProducerConfigurator helper (new in CDI 2.0)

The following example is inspired by Metrics-CDI extension.

When user will declare a producer for a metric in the app, we want to check in the metric registry that it doesn’t already exist. If it exist, instead of creating an new instance, we’ll return the one in the registry. If it doesn’t exist, we’ll use the producer code to instantiate the metric, add it to the registry and returns it to the application.

public class MetricsExtension implements Extension {

<T extends com.codahale.metrics.Metric> void processMetricProducer(@Observes ProcessProducer<?, T> pp, BeanManager bm) { (1)
        Metric m = pp.getAnnotatedMember().getAnnotation(Metric.class); (2)

        if (m != null) { (3)
            String name = m.name(); (4)
            Producer<T> prod = pp.getProducer(); (5)
            pp.configureProducer() (6)
                    .produceWith(ctx -> { (7)
                        MetricRegistry reg = bm.createInstance().select(MetricRegistry.class).get(); (8)
                        if (!reg.getMetrics().containsKey(name)) (9)
                            reg.register(name, prod.produce(ctx)); (10)
                        return (T) reg.getMetrics().get(name);  (11)
                    });
        }
    }
}
1 this observer needs BeanManager. This helper bean can be injected in any observer in an extension
2 retrieving @Metric annotation on the producer
3 treatment will be skip if no annotation found
4 retrieving name of the metric from the annotation
5 getting the initial producer to be able to use it in call back
6 we use the new ProducerConfigurator helpers. In CDI 1.2 we would have created our own implementation of Producer interface
7 we define a functional callback for producing the instance of the producer
8 retrieving the registry bean instance
9 looking for a metric with the matching name
10 if it doesn’t exist we create it by using the original producer code and it to the registry
11 we return the metric with the matching name from the registry

ProcessObserverMethod event

This event is fired for all observers declared in enabled beans.

Before CDI 2.0 it was mainly an event to check existence of an observer method. Since CDI 2.0, this gives more control by allowing ObserverMethod replacement or removing of it.

public interface ProcessObserverMethod<T, X> { (1)
    AnnotatedMethod<X> getAnnotatedMethod(); (2)
    ObserverMethod<T> getObserverMethod(); (3)
    void addDefinitionError(Throwable t); (4)

    /* new in CDI 2.0 */
    void setObserverMethod(ObserverMethod<T> observerMethod); (5)
    ObserverMethodConfigurator<T> configureObserverMethod(); (5)
    void veto(); (6)
}
1 Parameterized type for better observer filtering. T is the bean class of the bean containing the observer method, X is the type of the event
2 returns the AnnotatedMethod defining the ObserverMethod
3 returns the ObserverMethod
4 allows observer to abort deployment by adding a definition error
5 allow replacement or overriding of the ObserverMethod either by providing a custom ObserverMethod instance or by using an ObserverMethodConfigurator (new in CDI 2.0)

The example below show how an extension can switch all synchronous observer for MyClass event type to asynchronous behaviour.

public class SwitchExtension implements Extension {

   public void switchToAsync(@Observes ProcessObserverMethod<?, MyClass> pom) {
       pom.configureObserverMethod().async(true);
   }
}

AfterBeanDiscovery event

This event is fired after all beans, producers and observer discovery.

It is the last occasion to change or enhance discovered meta data.

public interface AfterBeanDiscovery {
    void addDefinitionError(Throwable t); (1)
    void addBean(Bean<?> bean); (2)
    void addObserverMethod(ObserverMethod<?> observerMethod); (3)
    void addContext(Context context); (4)
    <T> AnnotatedType<T> getAnnotatedType(Class<T> type, String id); (5)
    <T> Iterable<AnnotatedType<T>> getAnnotatedTypes(Class<T> type); (6)

    /* New in CDI 2.0 */
    <T> BeanConfigurator<T> addBean(); (2)
    <T> ObserverMethodConfigurator<T> addObserverMethod(); (3)
}
1 allows observer to abort deployment by adding a definition error
2 allows creation of a custom bean either by creating a custom implementation of Bean interface or by using the BeanConfigurator helper (new in CDI 2.0). registering a custom bean will trigger all the events linked to bean discovery and creation.
3 allows creation of an ObserverMethod either by creating a custom implementation of ObserverMethod interface or by using the ObserverMethodConfigurator helper (new in CDI 2.0).
4 add a nex context to the container
5 returns a discovered AnnotatedType for the given class and id.
6 returns an Iterable on all the discovered AnnotatedType in the application

AfterDeploymentValidation event

This last bootstrapping event is only a hook to check that everything is as expected in the meta data (remember that the observer can inject BeanManager to inspect these meta data).

When this event is fired, the meta data in the container are no more mutable and the application is ready to run

public interface AfterDeploymentValidation {
    void addDeploymentProblem(Throwable t); (1)
}
1 allows observer to abort deployment by adding a definition error

Application life and death

From the portable extension perspective we are nearly done.

After this rich phase of bootstrapping, the application runs until an event request its shutting down. It’s when the last portable extension event is fired.

BeforeShutdown Event

This event is a hook, to allow cleaning of specific resource created during application life

public interface BeforeShutdown {
}

Conclusion

Portable extension are a very powerful tool.

Mastering them may seems difficult, but once you understand most of the SPI and the container lifecycle shown in this post, it’s no more than a kind of big lego box only limited by your imagination.