Creating a Java EE extension

This is again an article about Java Enterprise. Today I will introduce the portable extensions of the CDI framework and give you a simple sample application where you can base your own extensions on.

Are you ready? Why not.

What are extensions?

Extensions are custom event handlers which can observe the life-cycle events of the Java EE CDI container. These life-cycle events are only delivered to extensions.

With extensions you can manage some odd use-cases which require execution / configuration when the container starts. For example you can create an extension, which logs every life-cycle event fired by the container (by start-up and tear-down), or you want to load configuration stored in beans when the server gets up and running.

You know, that CDI beans are only then instantiated just before the first method call executes — even if they are @ApplicationScoped. For this you can create an extension which calls a method on every CDI bean when the CDI container gets up and running. As for a good advice I’d call the toString() method, because they should exist in any type of CDI bean you want to initialize. However this can be only achieved through listening to the proper life-cycle event fired by the container.

Injecting CDI beans into extensions is out of the question because you would get null. So if you want to do something with your bean, make it in the method annotated with @PreDestroy.

Life-cycle events

These events are fired parallel to the container life-cycle. You can register to observe these events in any CDI bean, however they are only delivered to extensions. This is because the CDI beans get created and managed during the initialization phase of the life-cycle. When the container stops, it tears down the created objects and fires some life-cycle events too — again available to extensions.

How to implement an extension?

This is very easy, you only have to implement the interface

javax.enterprise.inject.spi.Extension

With this we have an extension.

However, our extension does nothing and is not active.

To activate extensions, you have to include the fully qualified names of the extensions into the javax.enterprise.inject.spi.Extension file located under META-INF/services.

If you do not implement the Extension interface and start the service with activated extensions, you will get an exception similar to this one:

Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: JBAS016053: Service class hu.japy.dev.extension.LifeCycleLogger didn't implement the javax.enterprise.inject.spi.Extension interface
    at org.jboss.as.weld.deployment.WeldPortableExtensions.tryRegisterExtension(WeldPortableExtensions.java:48)
    at org.jboss.as.weld.deployment.processors.WeldPortableExtensionProcessor.loadAttachments(WeldPortableExtensionProcessor.java:121)
    at org.jboss.as.weld.deployment.processors.WeldPortableExtensionProcessor.deploy(WeldPortableExtensionProcessor.java:81)
    at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:159) [wildfly-server-8.2.0.Final.jar:8.2.0.Final]
    ... 5 more

Now that we have the knowledge how to activate custom extensions, let me show you, how to implement one.

This example will cover a life-cycle event logger extension which listens to all life-cycle events fired by the Java EE container.

To listen to every event, I defined a general approach for the argument: I observe every Object so I get every life-cycle event fired.

public class LifeCycleLogger implements Extension {

    private static final Logger LOGGER = LoggerFactory.getLogger(LifeCycleLogger.class);
    public void eventObserver(@Observes Object event) {
        if (event.getClass().getInterfaces() != null && event.getClass().getInterfaces().length > 0) {
            LOGGER.info(event.getClass().getInterfaces()[0].getCanonicalName());
        }
    }
}

If you create a detailed method for each of the events, you can get more data about the event being fired.

If you run the application with the general logger, you can see the following events are fired when the CDI container starts.

12:18:23,338 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.BeforeBeanDiscovery
12:18:23,348 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.ProcessSyntheticAnnotatedType
12:18:23,348 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.ProcessSyntheticAnnotatedType
12:18:23,348 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.ProcessSyntheticAnnotatedType
12:18:23,348 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.ProcessAnnotatedType
12:18:23,348 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.AfterTypeDiscovery
12:18:23,518 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.AfterBeanDiscovery
12:18:23,569 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-12) javax.enterprise.inject.spi.AfterDeploymentValidation

You might wonder, why there are so few life-cycle events. This is because of the anonymous classes. They do not have interfaces directly, you have to tweak a little to get the things, you really look after:

Class<?>[] interfaces = event.getClass().getInterfaces();
if (event.getClass().isAnonymousClass()) {
    interfaces = event.getClass().getSuperclass().getInterfaces();
}
if (interfaces != null) {
    Arrays.stream(interfaces).forEach(e -> LOGGER.info(e.getCanonicalName()));
}

If you stop the container, you can see some events like this in the log:

12:21:00,883 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-1) javax.servlet.ServletContext
12:21:00,903 INFO  [hu.japy.dev.extension.LifeCycleLogger] (MSC service thread 1-15) javax.enterprise.inject.spi.BeforeShutdown

If you know which events you are interested in, you can listen to that concrete event and get more information about it:

<T, X> void logLifecycleEvent(@Observes ProcessInjectionPoint<T, X> event) {
    LOGGER.info("ProcessInjectionPoint: injectionPoint=" + event.getInjectionPoint());
}

As you see, it is a good practice to create an own method for the concrete event listeners. So you make your code more readable instead of

Conclusion and sources

I introduced the extensions through the logging of life-cycle events in a Java EE container. Now you can write your own extension and create container-based logic in your application which starts or tears down with the container. As for me, I use extensions to load configuration files into the memory of the application.

As always, you can find the sources at my GitHub account.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s