Introduction
This text will tell you how you can use CDI itself to configure some portable extension of CDI you write yourself. So first a little bit of introduction what a portable extension is and why you should use it.
When using CDI in your application, almost all the time you can just use what is available, like @Inject, @Named, the scopes and events to name the most important ones.
But sometimes, you must be able to programmatically define new CDI artefacts like scopes or beans. This can be done by using the portable extension. The required steps are
- Create a class which implements the marker interface javax.enterprise.inject.spi.Extension
- Define the (full name) of the class in a file javax.enterprise.inject.spi.Extension defined in the META-INF/services directory of your application (or jar file)
- Define some method which has a CDI event parameter and has the @Observes annotation.
An example of this can be
public class ModuleConfigExtension implements Extension {
void configModule(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
System.out.println("Startup extension");
}
}
This method is then called when the CDI container is initialising.
The following image shows the different steps in the container initialisation.
Configuration
So, now that we know how we can extend the CDI system, you can start creating additional beans and scopes for instances. But also define some interceptors that applied to every bean like logging without the need to configure it in XML.
So far the introduction, now we can go to the configuration of your extension.
You have created the extension and has put it into a jar which you can reuse for all your projects you develop.
And in most situations, the default you have defined are good enough, except for some applications that need a little bit of tweaked values.
The first question you can ask yourself is why you should CDI for the configuration here. Well because it is very convenient to override some defaults you specify yourself. And most of all, it is done in a type safe way. You define the values in code and not as strings in XML, plain text or JSON.
Of course, if you want to be able to specify the values outside of your code, you have no other alternative then using some file with your values and reading that one in.
Lets have a CDI bean which define the defaults
@ApplicationScopedpublic class BasicModuleConfig {
@PostConstruct public void init() {
System.out.println("PostConstruct of BasicModuleConfig"); }
@Override public void doConfig() {
System.out.println("Basic config for module"); }
}
(PS The idea is of course to have methods which return the default value. The above code is just to make it clear what is happening)
And when you need some other values as the default ones, just create an extends of this class which you annotate with @Specialized. This version will now be used.
@Specializespublic class CustomModuleConfig extends BasicModuleConfig {
@Override public void doConfig() {
System.out.println("Custom specialized config"); }
}
CDI 1.0
Well there is a catch. With CDI 1.0 (Java EE 6), you where allowed to use the supplied beanManager in the after bean discovery phase to retrieve the CDI bean.
But this is a bit awkward as you are using CDI features before the CDI container is completely initialised.
So therefor they did some additional clarifications for the CDI 1.1 spec and it is now no longer valid (you receive an exception) when you are trying to do this in a Java EE 7 container.
CDI 1.1 solution
But they came up with another solution, although partially, but you need to do some additional work manually in your code.
They have introduced the Unmanaged class where you can receive an instance of a CDI bean, but which is not maintained by the container.
The following code gives you an example:
void configModule(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
Unmanaged<BasicModuleConfig> unmanagedConfig = new Unmanaged<>(beanManager, BasicModuleConfig.class); Unmanaged.UnmanagedInstance<BasicModuleConfig> configInstance = unmanagedConfig.newInstance(); ModuleConfig config = configInstance.produce().inject().postConstruct().get();
config.doConfig();
configInstance.preDestroy().dispose();}
So you can use an initialisation method annotated with @PostConstruct, but you can’t use any injection into this bean. If you try this, you will receive some exception that a resolved bean resulted in null (since the container is not ready, the system doesn’t find the bean it needs to inject)
And there is another issue, the @Specialized isn’t picked up.
But that you can fix in your code. There is another event that is useful in this case. When the CDI container scans the class path, every bean which is eligible for CDI management, is handed to a method which observe the @ProcessAnnotatedType event (Process bean phase in the above image)
Here we can keep the bean which has our specialised configuration, and use that one in our configuration.
The code of the extension could look like this then:
public class ModuleConfigExtension implements Extension { private Class<? extends BasicModuleConfig> configClass = BasicModuleConfig.class; <T> void collectImplementations(@Observes ProcessAnnotatedType<T> pat, BeanManager beanManager) { AnnotatedType<T> annotatedType = pat.getAnnotatedType(); if (BasicModuleConfig.class.equals(annotatedType.getJavaClass().getSuperclass())) { configClass = (Class<? extends BasicModuleConfig>) annotatedType.getJavaClass(); } } void configModule(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { Unmanaged<? extends BasicModuleConfig> unmanagedConfig = new Unmanaged<>(beanManager, configClass); Unmanaged.UnmanagedInstance<? extends BasicModuleConfig> configInstance = unmanagedConfig.newInstance(); ModuleConfig config = configInstance.produce().inject().postConstruct().get(); config.doConfig(); configInstance.preDestroy().dispose(); } }
Conclusion
There is a very convenient way of using CDI itself for the configuration of your portable CDI extension, because all your values are typesafe. When the default configuration values are defined in a CDI bean, you can easily specify custom values in a specialised bean when needed.
Nice post.
ReplyDeleteLet me bring a small precision. With CDI 1.2 you don't need to use your trick with Unmanaged since we officially authorised usage of most BeanManager methods in a AfterBeanDiscovery observer. CDI 1.2 is available with Weld 2.2.x and is included in Glassfish 4.1 or WildFly 8.2.
Thx for the additional info, good to know.
Delete