This is a proof of concept implementation of a translation layer between proposed Build Compatible Extensions and existing Portable Extensions. It includes:
impl
, the implementation based on Portable Extensionstest
, a playground based on JUnit Jupiter and Weld
It depends on:
- Jakarta CDI
4.0.0.Beta2
- Weld Core
5.0.0-SNAPSHOT
, from currentmaster
branch
The proposed Build Compatible Extension API has 2 parts: language model and the actual extension API. We'll describe them here and compare to Portable Extensions and other notable APIs such as Reflection.
The jakarta.enterprise.lang.model
API models the Java language from a high-level perspective.
This is crucial for extension to be able to inspect classes, methods, fields, annotations, etc.
The model is structured as a hierarchy of types.
The root in this hierarchy is AnnotationTarget
.
That is anything that can be annotated:
- declarations, such as classes, methods or fields;
- type parameters and type usages, such as reference types, type variables or wildcard types.
Annotations are represented by AnnotationInfo
.
Annotation attributes are represented by AnnotationAttribute
, which is basically a pair of String
name and annotation attribute value.
Annotation attribute values are represented by AnnotationAttributeValue
.
Build Compatible Extensions | Portable Extensions | Reflection | Annotation Processing | Jandex |
---|---|---|---|---|
AnnotationTarget |
jakarta.enterprise.inject.spi.Annotated |
java.lang.reflect.AnnotatedElement |
javax.lang.model.AnnotatedConstruct |
org.jboss.jandex.AnnotationTarget |
AnnotationInfo |
none, uses java.lang.Annotation |
none1 | javax.lang.model.AnnotationMirror |
org.jboss.jandex.AnnotationInstance |
AnnotationAttribute |
none1 | none1 | none | org.jboss.jandex.AnnotationValue |
AnnotationAttributeValue |
none1 | none1 | javax.lang.model.element.AnnotationValue |
none |
- Reflective access is possible through
java.lang.Annotation#annotationType
,java.lang.Class
andjava.lang.reflect.Method
, but usually not necessary.
As mentioned above, there are two kinds of AnnotationTarget
s: declarations and types.
Therefore, we have DeclarationInfo
as the top-level type for representing Java declarations, and Type
as the top-level type for representing Java types.
Declarations are:
- packages;
- classes;
- fields;
- methods, including constructors;
- method parameters.
Build Compatible Extensions | Portable Extensions | Reflection | Annotation Processing | Jandex |
---|---|---|---|---|
DeclarationInfo |
none, just jakarta.enterprise.inject.spi.Annotated |
none3 | javax.lang.model.element.Element |
none, just org.jboss.jandex.AnnotationTarget |
PackageInfo |
none1 | java.lang.Package |
javax.lang.model.element.PackageElement |
none4 |
ClassInfo |
jakarta.enterprise.inject.spi.AnnotatedType 2 |
java.lang.Class |
javax.lang.model.element.TypeElement |
org.jboss.jandex.ClassInfo |
FieldInfo |
jakarta.enterprise.inject.spi.AnnotatedField 2 |
java.lang.reflect.Field |
javax.lang.model.element.VariableElement |
org.jboss.jandex.FieldInfo |
MethodInfo |
jakarta.enterprise.inject.spi.AnnotatedMethod , jakarta.enterprise.inject.spi.AnnotatedConstructor 2 |
java.lang.reflect.Method , java.lang.reflect.Constructor |
javax.lang.model.element.ExecutableElement |
org.jboss.jandex.MethodInfo |
ParameterInfo |
jakarta.enterprise.inject.spi.AnnotatedParameter 2 |
java.lang.reflect.Parameter |
javax.lang.model.element.VariableElement |
org.jboss.jandex.MethodParameterInfo |
- Relies on Reflection.
- Provides access to the corresponding Reflection object.
- There are types such as
java.lang.reflect.GenericDeclaration
orjava.lang.reflect.AccessibleObject
, but nothing directly corresponding, - May use
ClassInfo
forpackage-info.class
,
Types are:
- void;
- primitive types, such as
int
; - class types, such as
String
; - array types, such as
int[]
orString[][]
; - parameterized types, such as
List<String>
; - type variables, such as
T
when used in a class or method that declares a type parameterT
; - wildcard types, such as
? extends Number
.
Build Compatible Extensions | Portable Extensions | Reflection | Annotation Processing | Jandex |
---|---|---|---|---|
Type |
none1 | java.lang.reflect.Type 2 |
javax.lang.model.type.TypeMirror |
org.jboss.jandex.Type |
VoidType |
none1 | java.lang.Class 2 |
javax.lang.model.type.NoType |
org.jboss.jandex.VoidType |
PrimitiveType |
none1 | java.lang.Class 2 |
javax.lang.model.type.PrimitiveType |
org.jboss.jandex.PrimitiveType |
ClassType |
none1 | java.lang.Class 2 |
javax.lang.model.type.DeclaredType |
org.jboss.jandex.ClassType |
ArrayType |
none1 | java.lang.Class , java.lang.reflect.GenericArrayType 2 |
javax.lang.model.type.ArrayType |
org.jboss.jandex.ArrayType |
ParameterizedType |
none1 | java.lang.reflect.ParameterizedType 2 |
javax.lang.model.type.DeclaredType |
org.jboss.jandex.ParameterizedType |
TypeVariable |
none1 | java.lang.reflect.TypeVariable 2 |
javax.lang.model.type.TypeVariable |
org.jboss.jandex.TypeVariable , org.jboss.jandex.UnresolvedTypeVariable |
WildcardType |
none1 | java.lang.reflect.WildcardType 2 |
javax.lang.model.type.WildcardType |
org.jboss.jandex.WildcardType |
- Relies on Reflection.
- There's also an alternative hierarchy rooted at
java.lang.reflect.AnnotatedType
.
Build Compatible Extensions, similarly to Portable Extensions, are service providers for the BuildCompatibleExtension
interface.
They can declare arbitrary methods annotated with one of the processing phases annotations.
These methods can declare arbitrary parameters out of a particular set of types supported for given processing phase.
The extension API proposes 5 processing phases, roughly corresponding to Portable Extensions processing phases.
@Discovery
, that allows adding types to be scanned during bean discovery, and allows registering custom meta-annotations;@Enhancement
, that allows modifying annotations;@Processing
, that allows looking at registered beans and observers;@Synthesis
, that allows registering synthetic beans and observers;@Validation
, that allows custom validation.
Build Compatible Extensions | Portable Extensions |
---|---|
@Discovery |
jakarta.enterprise.inject.spi.BeforeBeanDiscovery |
@Enhancement |
jakarta.enterprise.inject.spi.ProcessAnnotatedType |
@Processing |
jakarta.enterprise.inject.spi.ProcessBean , jakarta.enterprise.inject.spi.ProcessObserverMethod |
@Synthesis |
jakarta.enterprise.inject.spi.AfterBeanDiscovery |
@Validation |
jakarta.enterprise.inject.spi.AfterDeploymentValidation |
A simple Hello, world! extension would look like this:
// the corresponding META-INF/services/jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension file must also exist
public class MyExtension implements BuildCompatibleExtension {
@Discovery
public void hello(Messages msg) {
msg.info("Hello, world!");
}
}
Messages
is one of the types that extension methods can use as a parameter.
Let's take a look at what Build Compatible Extensions can do in all the phases.
Extension methods annotated @Discovery
can declare parameters of these types:
AppArchiveBuilder
: add types to be scanned during bean discoveryMetaAnnotations
: register custom meta-annotations (qualifiers, interceptor bindings, stereotypes, and scopes)Messages
: logging and validation
Build Compatible Extensions | Portable Extensions |
---|---|
AppArchiveBuilder |
jakarta.enterprise.inject.spi.BeforeBeanDiscovery#addAnnotatedType |
MetaAnnotations |
jakarta.enterprise.inject.spi.BeforeBeanDiscovery#configureQualifier , jakarta.enterprise.inject.spi.BeforeBeanDiscovery#configureInterceptorBinding , jakarta.enterprise.inject.spi.BeforeBeanDiscovery#addStereotype , jakarta.enterprise.inject.spi.BeforeBeanDiscovery#addScope + jakarta.enterprise.inject.spi.AfterBeanDiscovery#addContext |
Messages#error |
jakarta.enterprise.inject.spi.AfterDeploymentValidation#addDeploymentProblem |
Extension methods annotated @Enhancement
can declare parameters of these types:
ClassConfig
: transform annotations on classes that satisfy declaratively expressed criteria (see below)MethodConfig
: transform annotations on methods that satisfy declaratively expressed criteria (see below)FieldConfig
: transform annotations on fields that satisfy declaratively expressed criteria (see below)AppArchiveConfig
: transform annotations on classes/methods/fields that satisfy programmatically expressed criteria (see below)Types
: utility to create instances ofType
Annotations
: utility to create instances ofAnnotationAttribute
andAnnotationAttributeValue
Messages
: logging and validation
Each @Enhancement
method must declare exactly 1 parameter of type ClassConfig
, MethodConfig
, FieldConfig
or AppArchiveConfig
.
If the method declares a parameter of type ClassConfig
, MethodConfig
or FieldConfig
, it also has to have at least 1 annotation of type @ExactType
or @SubtypesOf
.
This is to constrain the set of types for which the method is used as an annotation transformation callback.
If the method declares a parameter of type AppArchiveConfig
, it registers annotation transformation callbacks explicitly.
See Javadoc for more.
As an example, let's move a qualifier from one class to another:
// 1. a qualifier annotation
@Qualifier
@Retention(RUNTIME)
public @interface MyQualifier {
}
// 2. a service interface
public interface MyService {
String hello();
}
// 3. two implementations, one with qualifier and the other unqualified
@Singleton
@MyQualifier
public class MyFooService implements MyService {
@Override
public String hello() {
return "foo";
}
}
@Singleton
public class MyBarService implements MyService {
@Override
public String hello() {
return "bar";
}
}
// 4. a class that uses the service
@Singleton
public class MyServiceUser {
@Inject
@MyQualifier
MyService myService;
}
// 5. the extension
public class MyExtension implements BuildCompatibleExtension {
@Enhancement
@ExactType(type = MyFooService.class)
public void foo(ClassConfig clazz) {
clazz.removeAnnotation(it -> it.name().equals(MyQualifier.class.getName()));
}
@Enhancement
@ExactType(type = MyBarService.class)
public void bar(ClassConfig clazz) {
clazz.addAnnotation(MyQualifier.class);
}
}
Build Compatible Extensions | Portable Extensions |
---|---|
ClassConfig |
jakarta.enterprise.inject.spi.ProcessAnnotatedType#configureAnnotatedType |
MethodConfig |
jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator#methods , jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator#constructors |
FieldConfig |
jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator#fields |
AppArchiveConfig |
jakarta.enterprise.inject.spi.ProcessAnnotatedType |
Types |
none, relies on Reflection |
Annotations |
none, relies on java.lang.Annotation and jakarta.enterprise.util.AnnotationLiteral |
Messages#error |
jakarta.enterprise.inject.spi.AfterDeploymentValidation#addDeploymentProblem |
Extension methods annotated @Processing
can declare parameters of these types:
BeanInfo
: look at registered beans that satisfy declaratively expressed criteria (see below)ObserverInfo
: look at registered observers that satisfy declaratively expressed criteria (see below)Types
: utility to create instances ofType
Messages
: logging and validation
Each @Processing
method must declare exactly 1 parameter of type BeanInfo
or ObserverInfo
.
It also has to have at least 1 annotation of type @ExactType
or @SubtypesOf
.
This is to constrain the set of types of beans/observers for which the method is called.
The annotatedWith
attribute of @ExactType
and @SubtypesOf
is ignored here; it's only relevant in @Enhancement
.
Build Compatible Extensions | Portable Extensions |
---|---|
BeanInfo |
read-only view on jakarta.enterprise.inject.spi.ProcessBean |
ObserverInfo |
read-only view on jakarta.enterprise.inject.spi.ProcessObserverMethod |
Types |
none, relies on Reflection |
Messages#error |
jakarta.enterprise.inject.spi.ProcessBean#addDefinitionError , jakarta.enterprise.inject.spi.ProcessObserverMethod#addDefinitionError |
Extension methods annotated @Synthesis
can declare parameters of these types:
AppArchive
: inspect classes in the applicationAppDeployment
: inspect beans and observers in the applicationSyntheticComponents
: register synthetic beans and observersTypes
: utility to create instances ofType
Messages
: logging and validation
As an example, let's create a synthetic bean. Synthetic observers are very similar.
// 1. a bean class
public class MyPojo {
public final String data;
public MyPojo(String data) {
this.data = data;
}
}
// 2. bean creation function
public class MyPojoCreator implements SyntheticBeanCreator<MyPojo> {
@Override
public MyPojo create(CreationalContext<MyPojo> creationalContext, InjectionPoint injectionPoint, Map<String, Object> params) {
String name = (String) params.get("name");
return new MyPojo("Hello " + name);
}
}
// 3. bean disposal function
public static class MyPojoDisposer implements SyntheticBeanDisposer<MyPojo> {
@Override
public void dispose(MyPojo instance, CreationalContext<MyPojo> creationalContext, Map<String, Object> params) {
System.out.println("disposing " + instance.data);
}
}
// 4. the extension
public static class MyExtension implements BuildCompatibleExtension {
@Synthesis
public void synthesise(SyntheticComponents syn) {
syn.addBean(MyPojo.class)
.type(MyPojo.class)
.withParam("name", "World")
.createWith(MyPojoCreator.class)
.disposeWith(MyPojoDisposer.class);
}
}
Build Compatible Extensions | Portable Extensions |
---|---|
AppArchive |
read-only view on jakarta.enterprise.inject.spi.ProcessAnnotatedType |
AppDeployment |
read-only view on jakarta.enterprise.inject.spi.ProcessBean and jakarta.enterprise.inject.spi.ProcessObserverMethod |
SyntheticComponents |
jakarta.enterprise.inject.spi.AfterBeanDiscovery#addBean + jakarta.enterprise.inject.spi.AfterBeanDiscovery#addObserverMethod |
Types |
none, relies on Reflection |
Messages#error |
jakarta.enterprise.inject.spi.AfterDeploymentValidation#addDeploymentProblem |
Extension methods annotated @Validation
can declare parameters of these types:
AppArchive
: inspect classes in the applicationAppDeployment
: inspect beans and observers in the applicationTypes
: utility to create instances ofType
Messages
: logging and validation
Build Compatible Extensions | Portable Extensions |
---|---|
AppArchive |
read-only view on jakarta.enterprise.inject.spi.ProcessAnnotatedType |
AppDeployment |
read-only view on jakarta.enterprise.inject.spi.ProcessBean and jakarta.enterprise.inject.spi.ProcessObserverMethod |
Types |
none, relies on Reflection |
Messages#error |
jakarta.enterprise.inject.spi.AfterDeploymentValidation#addDeploymentProblem |