CDI Extensions can share data!
CDI Extensions are still a bit scary for several people but they are actually very powerful. One not that known feature is their ability to share data.
CDI extensions are observers by nature, we often see:
public MyExtension implements Extension {
void start(@Observes BeforeBeanDiscovery bbd, final BeanManager bm) {
bbd.addAnnotatedType(bm.createAnnotatedType(MyType.class));
}
}
This extension just adds a not scanned type as a CDI bean but why I shared this snippet was to remind you the programming model of extensions: @Observes.
A very unknown feature - Mark Struberg reminded it to me last week ;) - of CDI Extension is that they can fire events between them. During bootstrap it is very precious since you can share the event this way.
Here is a trivial example:
public Extension1 implements Extension {
void start(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
bm.fireEvent(new MyEvent1());
}
}
public Extension2 implements Extension {
void startAfterExtension1(@Observes MyEvent1 event1) {
// ...
}
}
This trivial example shows that both extensions shared some data through MyEvent1 and also ordered some of their execution this way.
Of course events being able to hold any kind of data you can use that on very advanced cases.
A nice one is when you need to share the same data between the container bootstrap and the runtime. Since you write an extension you can fire the data for the bootstrap case but then at runtime you prefer to use @Inject directly. To make it possible just add the event (or the data) as a bean:
public Extension1 implements Extension {
private MyService service;
void createAndShareBean(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
service = new MyService();
bm.fireEvent(service);
}
void vetoScannedImpl(@Observes ProcessAnnotatedType<MyService> pat) {
pat.veto(); // depending the app/scanning it is or not needed
}
void addBean(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
// I assume you have a Bean<T> implementation for provided instances,
// if not deltaspike-core does it very well with its BeanBuilder
bm.fireEvent(new ExistingInstance<MyService>(service));
}
}
This small extension will guarantee startup and runtime will use the same instance of MyService.
The limitation is that MyService needs to be instantiated in this extension and not through CDI (so no @Inject, @Observes, etc... for this instance). This is however the case for all instances created before the bootstrap of the container is done.
At runtime the usage of our service will simply be:
@Inject
private MyService service;
Small limitation: BeforeBeanDiscovery event
The issue is CDI extensions are not ordered so you don't know which one will be first until you use some vendor API. Issue of previous solution is around BeforeBeanDiscovery event which is the one we use to instantiate and share our service: how other extensions can use our service in this particular event?
The solution is to fire an event containing the service and the BeforeBeanDiscovery instance and the BeanManager:
public Extension1 implements Extension {
private MyService service;
void createAndShareBean(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
service = new MyService();
bm.fireEvent(new ServiceCreated(service, bbd, bm));
}
// like before
}
With this new event consumers can do their work without issues:
public Extension2 implements Extension {
void startAfterExtension1(@Observes ServiceCreated start) {
start.getService().doSomething();
start.getBdd().addAnnotatedType(
start.getBm().createAnnotatedType(MyType.class));
}
}
The only side effect is if you create an annotated type through this new API then it will reference the bad extension but this is rarely (never?) used so not a big pitfall.
Why is it useful?
Sharing data has a lot of use cases if you build an enterprise friendly solution (which includes microservices trend if it means anything). In such applications you will need to build some specific services for:
- the configuration
- the passwords/secrets handling (ciphering/crypto)
- the logging
- ...
It is tempting to use these services in extensions to read a flag in the configuration to activate or not a feature for instance, to read a resource password if your resources are managed by an extension etc...
Another tip before concluding
This post was mainly about sending data with loose coupling between producers and consumers but if you don't care to depend on class XExtension and know it is an extension then you can abuse of CDI 1.1 API: BeanManager.getExtension(Class<?>) which will allow you to retrieve another extension from an extension since extension can get the BeanManager in event methods and get the extension at runtime either by a direct injection (@Inject) or through CDI.current().getBeanManager().getExtension(MyExtension.class), even from a not managed bean.
This solution has an advantage over previous one: it doesn't corrupt BeforeBeanDiscovery.
Conclusion
Sharing data between extensions is not the first solution to think about because it adds some indirection (like any event based solution) to the code. However it can provide, for specific and well selected instances, a high added value to your stack and avoid you to rely on plain old singletons pattern which has its own pitfalls (classloader leakage, uncontrolled lifecycle, custom context/look-up handling, etc...).
From the same author:
In the same category: