One of the nicest features of CDI is the portable extension SPI. According to the spec:
A portable extension may integrate with the container by:
- Providing its own beans, interceptors and decorators to the container
- Injecting dependencies into its own objects using the dependency injection service
- Providing a context implementation for a custom scope
- Augmenting or overriding the annotation-based metadata with metadata from some other source
We've got plenty of examples of CDI application code floating about, but not much in the way of examples for framework developers who want to integrate with CDI, or augment the built-in functionality. So here's a little taste of the what a CDI portable extension looks like.
Let's start with an example of an extension that provides support for the use of @Named at the package level. The package-level name is used to qualify the EL names of all beans defined in that package.
Portable extensions implement the marker interface Extension and observe container lifecycle events, in this case ProcessAnnotatedType, an event that is fired by the container when it discovers a class or interface in a bean archive. The portable extension wraps the AnnotatedType object and overrides the value() of the @Named annotation of the bean.
public class QualifiedNameExtension implements Extension {
<X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> pat) {
//wrap this to override the annotations of the class
final AnnotatedType<X> at = pat.getAnnotatedType();
AnnotatedType<X> wrapped = new AnnotatedType<X>() {
@Override
public Set<AnnotatedConstructor<X>> getConstructors() {
return at.getConstructors();
}
@Override
public Set<AnnotatedField<? super X>> getFields() {
return at.getFields();
}
@Override
public Class<X> getJavaClass() {
return at.getJavaClass();
}
@Override
public Set<AnnotatedMethod<? super X>> getMethods() {
return at.getMethods();
}
@Override
public <T extends Annotation> T getAnnotation(final Class<T> annType) {
if ( Named.class.equals(annType) ) {
class NamedLiteral
extends AnnotationLiteral<Named>
implements Named {
@Override
public String value() {
Package pkg = at.getClass().getPackage();
String unqualifiedName = at.getAnnotation(Named.class).value();
final String qualifiedName;
if ( pkg.isAnnotationPresent(Named.class) ) {
qualifiedName = pkg.getAnnotation(Named.class).value()
+ '.' + unqualifiedName;
}
else {
qualifiedName = unqualifiedName;
}
return qualifiedName;
}
}
return (T) new NamedLiteral();
}
else {
return at.getAnnotation(annType);
}
}
@Override
public Set<Annotation> getAnnotations() {
return at.getAnnotations();
}
@Override
public Type getBaseType() {
return at.getBaseType();
}
@Override
public Set<Type> getTypeClosure() {
return at.getTypeClosure();
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annType) {
return at.isAnnotationPresent(annType);
}
};
pat.setAnnotatedType(wrapped);
}
}
We need to deploy this portable extension in a jar containing a file named javax.enterprise.inject.spi.Extension in the META-INF/services directory. This file should contain the full name of our QualifiedNameExtension class.
Here's a second example, of a portable extension that creates Bean objects for each class in a set of classes and registers the beans with the container. This is similar to what the container does for classes it discovers in bean archives.
public class RegisterExtension implements Extension {
List<Class<?>> getBeanClasses() {
//get some classes from somewhere
}
void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
for ( final Class c: getBeanClasses() ) {
//use this to read annotations of the class
AnnotatedType at = bm.createAnnotatedType(c);
//use this to create the class and inject dependencies
final InjectionTarget it = bm.createInjectionTarget(at);
abd.addBean( new Bean() {
@Override
public Class<?> getBeanClass() {
return c;
}
@Override
public Set<InjectionPoint> getInjectionPoints() {
return it.getInjectionPoints();
}
@Override
public String getName() {
return null;
}
@Override
public Set<Annotation> getQualifiers() {
Set<Annotation> qualifiers = new HashSet<Annotation>();
qualifiers.add( new AnnotationLiteral<Default>() {} );
qualifiers.add( new AnnotationLiteral<Any>() {} );
return qualifiers;
}
@Override
public Class<? extends Annotation> getScope() {
return Dependent.class;
}
@Override
public Set<Class<? extends Annotation>> getStereotypes() {
return Collections.emptySet();
}
@Override
public Set<Type> getTypes() {
Set<Type> types = new HashSet<Type>();
types.add(c);
types.add(Object.class);
return types;
}
@Override
public boolean isAlternative() {
return false;
}
@Override
public boolean isNullable() {
return false;
}
@Override
public Object create(CreationalContext ctx) {
Object instance = it.produce(ctx);
it.inject(instance, ctx);
it.postConstruct(instance);
return instance;
}
@Override
public void destroy(Object instance, CreationalContext ctx) {
it.preDestroy(instance);
it.dispose(instance);
ctx.release();
}
} );
}
}
}
UPDATE: I've finally got a proper chapter on portable extensions written up in the Weld reference documentation. Check out chapter 16 of this version if this post piqued your interest.