Adding legacy beans to CDI context: a CDI extension sample


Adding a bean which is not a CDI bean into the CDI context can easily be done through a CDI extension. However in some cases (typically when these beans are configuration aware) you need to extract their creation from the code (= configure them without updating the code. A common sample is an URL to configure.).

As a Proof Of Concept (PoC) i pushed on github cdi-light-config extension: https://github.com/rmannibucau/cdi-light-config.

It mainly relies on Apache DeltaSpike to add to CDI context configured beans.

The way it works is described in the README on the github project so I’ll deal with the internals here and more generally how to add your own beans to CDI.

The CDI extension

First we need to add a src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension file (i use maven) containing the qualified name of our extension to register it automatically as a CDI extension. In our case it just contains:

com.github.rmannibucau.cdi.configuration.LightConfigurationExtension

Then we need to make this class implementing javax.enterprise.inject.spi.Extension:

public class LightConfigurationExtension implements Extension {
...
}

This interface is just a marking interface (to be able to rely on ServiceLoader to let the CDI container load it). The method needs to be CDI observers (@Observes) matching startup/shutdown events.

In our case we just want to read a XML file containing a list of beans then register them as CDI beans. So the event we need to observe is the one after the scanning and before the validation: AfterBeanDiscovery.

public class LightConfigurationExtension implements Extension {
    void readAllConfigurations(final @Observes AfterBeanDiscovery abd, final BeanManager bm) {
        // ...
    }
}

As you can see the bean manager can be injected automatically as second parameter. It will be useful to create our beans.

Registering custom beans

I’ll not detail the configuration reading since it depends your goal (you can even imagine reading spring xml configuration if you are a bit vicious ;)). However i’ll suppose you have the following information for each bean you want to register:

  • the bean name (if one)
  • the qualifier(s) of the bean
  • the bean class and bean types
  • the way to create/destroy the bean

Once you get these information it is quite trivial to register the beans in the CDI context since Apache DeltaSpike provides a BeanBuilder utility class to simplify it.

Basically creating a CDI bean looks like:

// clazz is the bean class, types the matching types, qualifiers the corresponding set of qualifiers
final Bean<Object> bean = new BeanBuilder<Object>(beanManager)
                    .passivationCapable(true) // you can add some logic it to check it or configure it
                    .beanClass(clazz)
                    .name(name)
                    .types(types)
                    .scope(ApplicationScoped.class) // can be configurable
                    .qualifiers(qualifiers)
                    .beanLifecycle(new InstanceFactory<Object>(...))
                    .create();

Then the question is what is InstanceFactory? It is simply the hook asking you to create/destroy the bean respecting its scope:

public class InstanceFactory<T> implements ContextualLifecycle<T> {
    @Override
    public T create(final Bean<T> bean, final CreationalContext<T> creationalContext) {
        return ...; // here you really create the instance from the parameters you got when creating the bean
    }

    @Override
    public void destroy(final Bean<T> bean, final T instance, final CreationalContext<T> creationalContext) {
        // here you destroy the created instance if needed, can be calling close() if it is a Closeable for instance or nothing if not needed
    }
}

Now you have your bean you just need to register it in the CDI context. To do so simply use AfterBeanDiscovery API and addBean method:

afterBeanDiscovery.addBean(bean);

Next?

Now you know how to register an extension and how to create beans so you can add all the beans you want reading your own model. Apache DeltaSpike will propose a configuration for CDI in version 0.6 (we are working on 0.5 at the moment). However since it is really easy to read its own configuration you can already write your own extension (my PoC is about 30Ko once built and already supports some extensibility and advanced features). It allows you to integrate very quickly legacy libraries (without beans.xml) and export configuration (but take care to not export your whole IoC in configuration files which will make your application hard to maintain).

Want more content ? Keep up-to-date on my new blog

Or stay in touch on twitter @rmannibucau

Advertisements

5 thoughts on “Adding legacy beans to CDI context: a CDI extension sample

  1. Edgar

    Me and the rest of the world would greatly appreciate if you would dare to add comments into your source code samples. Thanks in advance!

    Reply
    1. rmannibucau Post author

      well if you speak about cdi-light-config project then this is not a sample for this post but a project I used in some real projects.

      This post used it to show what we can do in real.

      Which part is not clear for you? I’ll try to add few details in the post itself.

      Reply
      1. Edgar

        1) Do I understand correctly that principal flow of logic is as follows?:
        a) Use ShrinkWrap(s) to pack a war file, that includes test class and “to be” bean classes + some dependencies
        b) When test is run in “CDI container” (Arquillian), the LightConfigurationExtension comes to life and reacts to BeforeBeanDiscovery and AfterBeanDiscovery events, which are fired by container upon its startup.

        2) In your example, why are the 2 CDI container events (BeforeBeanDiscovery and AfterBeanDiscovery) used? It seems to me, that upon BeforeBeanDiscovery you are not interacting with container, but just building up a configuration information which is to be later used in method fired by AfterBeanDiscovery..

        3) In general the problem I am trying to solve is about dynamic loading of beans into CDI context. For example, there is a war file running on GlassFish server and there is a piece of software in it which monitors a certain directory on disk. Then someone drops a jar file into that directory. The software should pick up the jar, find classes, load them using ClassLoader and introduce them as beans in CDI context. Do you think such scenario is feasible?

        4) If it is feasible, then could you give (an approximate) hint, how to achieve it?
        Few directions that I have considered:
        a) use addBean() method in the middle of program:

        // clazz is the class definition from my arbitrary JAR file
        Class clazz = urlClassLoader.loadClass(className);

        // hack to get to the addBean() method; most probably works only on Weld
        BeanManagerImpl beanManager = ((BeanManagerProxy) cdiContainer.getBeanManager()).delegate();

        BeanBuilder beanBuilder = new BeanBuilder(beanManager)
        .beanClass(clazz);

        Bean bean = beanBuilder.create();
        beanManager.addBean(bean); // throws NullPointerException; looks like beanSet is missing inside BeanManagerImpl

        b) I assume that AfterBeanDiscovery event is only fired when CDI container starts up. If I could make it fire at an arbitrary point in time (when I dicover my new JAR file), then I could push the classes from that JAR as beans using addBean(). It is feasible and if yes – how would you do it?

      2. rmannibucau Post author

        1) shrinkwrap if you test with arquillian but otherwise it just uses cdi lifecycle yes
        2) a single event could be used right but here it will fail fast if something is highly wrong – that said I wouldnt fight if sby would have merged both methods
        3/4) in TomEE you can deploy “in war” jar outside of the war phisically, no idea in glassfish but to do it properly with interception etc it needs container integration

Leave a Reply

Fill in your details below or click an icon to log in:

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