- Senior Software Engineer
- CDI co-spec lead
- Red Hat, Inc.
- @antoine_sd
- www.next-presso.com
- github.com/antoinesd
CDI as the productivity ecosystem to build connectivity interfaces |
If you know the most of these you can stay |
@Inject
@Produces
Event<T>
@Observes
@Qualifier
InjectionPoint
One of the most powerful feature of the CDI specification
Not really popularized, partly due to the high level of abstraction
Observer pattern to listen for container initialization lifecycle events
Comprehensive access to and modification of the container metadata model
Service provider of the service javax.enterprise.inject.spi.Extension declared in META-INF/services |
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
class CdiExtension implements Extension {
void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
}
...
void afterDeploymentValidation(@Observes AfterDeploymentValidation adv) {
}
}
The following extension prevents CDI to manage entities |
This is a commonly admitted good practice |
public class VetoEntity implements Extension {
void vetoEntity(@Observes @WithAnnotations(Entity.class)
ProcessAnnotatedType<?> pat) {
pat.veto();
}
}
Extensions are launched during
bootstrap and are based on CDI events
Once the application is bootstrapped,
the Bean Manager is in read-only mode (no runtime bean registration)
You only have to @Observes
built-in CDI events to create your extensions
Modernize a Legacy Component with the CDI Programming Model
/* Not So Fictional */
Legacy Component Improve the public API
and make it typesafe
Integrate the component into the CDI programming model
Without touching the component source / binary
A functional interface: |
public interface Transformer<I, O> {
O transform(I input);
}
And a CDI qualifier with configuration metadata: |
import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
@Qualifier
public @interface Legacy {
@Nonbinding
String value() default ""; // The transformation name
}
@Inject @Legacy("transformation")
Transformer<InputType, OutputType> transformer;
InputType input;
OutputType result = transformer.transform(input);
Distribute configuration closest to the code: |
@Inject
@Legacy("transformation_1")
Transformer<InputType1, OutputType1> transformer_1;
...
@Inject
@Legacy("transformation_N")
Transformer<InputTypeN, OutputTypeN> transformer_N;
@Produces
@Legacy
<I, O> Transformer<I, O> produceTransformer(LegacyComponent legacyComponent, (1)
InjectionPoint injectionPoint) {
Legacy legacy = injectionPoint.getAnnotated().getAnnotation(Legacy.class);
return new LegacyTransformerAdapter<I, O>(legacyComponent, legacy.value(),
injectionPoint.getType())); (2)
}
1 | Get the legacy component bean instance |
2 | Produce a typesafe Transformer facade |
Parameterized types are not erased by CDI, so that they can be used as metadata. For example for type check / conversion during container initialization |
ProcessInjectionPoint
EventCollect all the @Legacy metadata required to instantiate the legacy component |
ProcessInjectionPoint<T, X>
public interface ProcessInjectionPoint<T, X> {
public InjectionPoint getInjectionPoint();
public void setInjectionPoint(InjectionPoint injectionPoint);
public void addDefinitionError(Throwable t);
}
ProcessInjectionPoint
Event in Lifecycleclass LegacyComponentExtension implements Extension {
Set<String> configuration = new HashSet<>();
void collectConfiguration(@Observes ProcessInjectionPoint<?, Transformer> pit) { (1)
Annotated annotated = pit.getInjectionPoint().getAnnotated();
if (annotated.isAnnotationPresent(Legacy.class))
configuration.add(annotated.getAnnotation(Legacy.class).value()); (2)
}
}
1 | Observe every injection point of type Transformer on any declaring bean |
2 | Collect the injection point @Legacy metadata, if any |
Bean
InterfaceIntegrate the legacy component as a CDI bean |
public interface Bean<T> extends Contextual<T>, BeanAttributes<T> {
public Class<?> getBeanClass();
public Set<InjectionPoint> getInjectionPoints();
// Contextual<T>
public T create(CreationalContext<T> creationalContext);
public void destroy(T instance, CreationalContext<T> creationalContext);
// BeanAttributes<T>
public Set<Type> getTypes();
public Set<Annotation> getQualifiers();
public Class<? extends Annotation> getScope();
public String getName();
public Set<Class<? extends Annotation>> getStereotypes();
public boolean isAlternative();
}
class LegacyComponentBean implements Bean<LegacyComponent> {
Set<String> configuration = new HashSet<>();
LegacyComponentBean(Set<String> configuration) {
this.configuration = configuration;
}
public LegacyComponent create(CreationalContext<LegacyComponent> context) {
LegacyComponent legacyComponent = new LegacyComponent();
legacyComponent./*CENSORED!*/(configuration);
return legacyComponent;
}
public void destroy(LegacyComponent legacyComponent,
CreationalContext<LegacyComponent> context) {
legacyComponent./*CENSORED!*/();
}
public Class<? extends Annotation> getScope() {
return ApplicationScoped.class;
}
}
AfterBeanDiscovery
EventAdd the legacy component bean after bean discovery |
AfterBeanDiscovery
public interface AfterBeanDiscovery {
public void addDefinitionError(Throwable t);
public void addBean(Bean<?> bean);
public void addObserverMethod(ObserverMethod<?> observerMethod);
public void addContext(Context context);
public <T> AnnotatedType<T> getAnnotatedType(Class<T> type, String id);
public <T> Iterable<AnnotatedType<T>> getAnnotatedTypes(Class<T> type);
}
AfterBeanDiscovery
in Lifecycleclass LegacyComponentExtension implements Extension {
Set<String> configuration = new HashSet<>();
void collectConfiguration(@Observes ProcessInjectionPoint<?, Transformer> pit) {
Annotated annotated = pit.getInjectionPoint().getAnnotated();
if (annotated.isAnnotationPresent(Legacy.class))
configuration.add(annotated.getAnnotation(Legacy.class).value());
}
void addLegacyComponentBean(@Observes AfterBeanDiscovery abd) {
abd.addBean(new LegacyComponentBean(configuration));
}
}
Improve the public API
and make it typesafe
Integrate the component into the CDI programming model
Without touching the component source / binary
Integrate a Third-party Library into the CDI Programming Model
Open-source Java library providing monitoring primitives like Counter , Gauge , Histogram , Meter , Timer , … |
Provides a MetricRegistry that articulates application / module metrics and metrics reporters |
Defines annotations for AOP frameworks like Spring AOP, AspectJ, Guice (AOP Alliance) and CDI: @Counted , @ExceptionMetered , @Metered , @Timed , … |
Metrics annotations AOP
class TimedMethodBean {
@Timed
void timedMethod() {} // Timer name => TimedMethodBean.timedMethod
}
Inject from / produce metrics into the registry
@Inject Timer timedMethod;
@Produces @Metric(name = "timedMethod")
Timer t = new Timer(new SlidingTimeWindowReservoir(1L, TimeUnit.MINUTES));
Without introducing extra API for the end-user
Use Java interceptors for Metrics annotations AOP |
@Interceptor @TimedBinding
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
class TimedInterceptor {
@Inject MetricRegistry registry;
@AroundInvoke
Object timeMethod(InvocationContext context) throws Exception {
String name = context.getMethod().getAnnotation(Timed.class).name();
Timer timer = registry.timer(name);
Timer.Context time = timer.time();
try {
return context.proceed();
} finally {
time.stop();
}
}
}
Use Java interceptor bindings to associate the interceptor |
@InterceptorBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@interface TimedBinding {
}
Forces to add the binding manually :-( |
class TimedMethodBean {
@Timed
@TimedBinding
void timedMethod() {
}
}
ProcessAnnotatedType<X>
EventAdd the interceptor binding programmatically |
public interface ProcessAnnotatedType<X> {
public AnnotatedType<X> getAnnotatedType();
public void setAnnotatedType(AnnotatedType<X> type);
public void veto();
}
ProcessAnnotatedType
in Lifecycleclass MetricsExtension implements Extension {
<X> void bindTimedMethodInterceptor(@Observes @WithAnnotations(Timed.class)
ProcessAnnotatedType<X> pat) { (1)
Set<AnnotatedMethod<? super X>> decoratedMethods = new HashSet<>();
for (AnnotatedMethod<? super X> method : pat.getAnnotatedType().getMethods())
if (method.isAnnotationPresent(Timed.class))
decoratedMethods.add(
new AnnotatedMethodDecorator<>(method, new TimedBindingLiteral())); (2)
pat.setAnnotatedType(
new AnnotatedTypeDecorator<>(pat.getAnnotatedType(), decoratedMethods));
}
}
1 | Observe types containing the @Timed annotation |
2 | Add the interceptor binding to the annotated methods |
Timer
?@Interceptor
@TimedBinding
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
class TimedInterceptor {
@Inject MetricRegistry registry;
@AroundInvoke
Object timedMethod(InvocationContext context) throws Exception {
String name = context.getMethod().getAnnotation(Timed.class).name();
Timer timer = registry.timer(name); (1)
Timer.Context time = timer.time();
try {
return context.proceed();
} finally {
time.stop();
}
}
}
1 | Default Timer histogram is exponentially biased to the past 5 minutes of measurements |
ProcessBean<X>
EventsUse producer fields / methods to register custom metrics |
@Produces Timer t = new Timer(new SlidingTimeWindowReservoir(1L, TimeUnit.MINUTES));
ProcessProducerField<T, X>
public interface ProcessProducerField<T, X> extends ProcessBean<X> {
public AnnotatedField<T> getAnnotatedProducerField();
public AnnotatedParameter<T> getAnnotatedDisposedParameter();
// ProcessBean<X>
public Annotated getAnnotated();
public Bean<X> getBean();
}
ProcessManagedBean<X> and ProcessProducerMethod<T, X> are fired for managed beans and producer methods respectively |
AfterDeploymentValidation
Eventclass MetricsExtension implements Extension {
Map<Bean<?>, AnnotatedMember<?>> metrics = new HashMap<>();
void producerFields(@Observes ProcessProducerField<? extends Metric, ?> ppf) {
metrics.put(ppf.getBean(), ppf.getAnnotatedProducerField()); (1)
}
void producerMethods(@Observes ProcessProducerMethod<? extends Metric, ?> ppm) {
metrics.put(ppm.getBean(), ppm.getAnnotatedProducerMethod()); (1)
}
void customMetrics(@Observes AfterDeploymentValidation adv, BeanManager manager) {
for (Map.Entry<Bean<?>, AnnotatedMember<?>> metric : metrics.entrySet())
registry.register(metricName(member), manager.getReference(metric.getKey(), (2)
metric.getValue().getBaseType(), manager.createCreationalContext(null)));
}
}
1 | Collect the custom Metric producer fields and methods |
2 | Instantiate and register the custom metrics into the Metrics registry |
@Inject
private Meter hits; (1)
@Timed(name = "calls") (2)
public void cachedMethod() {
if (hit) hits.mark();
}
@Produces @Metric(name = "cache-hits") (3)
private Gauge<Double> cacheHitRatioGauge(Meter hits, Timer calls) {
return () -> calls.getOneMinuteRate() == 0 ? Double.NaN :
hits.getOneMinuteRate() / calls.getOneMinuteRate();
}
1 | Metric injection from the registry |
2 | Method instrumentation with interceptors |
3 | Produce a custom Metric instance by composing others |
CDI Specification - cdi-spec.org |
Metrics CDI sources - github.com/astefanutti/metrics-cdi |
Slides sources - github.com/astefanutti/further-cdi |
Slides generated with Asciidoctor, PlantUML and DZSlides backend |
Original slide template - Dan Allen & Sarah White |