Going further with CDI 1.2

Antoine Sabot-Durand

  • Senior Software Engineer
  • CDI co-spec lead
  • Red Hat, Inc.
  • @antoine_sd
  • www.next-presso.com
  • github.com/antoinesd

Antonin Stefanutti

  • Software Engineer
  • Murex
  • www.murex.com
  • @astefanut
  • github.com/astefanutti

CDI @ Murex

murex
CDI as the productivity ecosystem to build connectivity interfaces

Should I stay or should I go?

  • A talk about advanced CDI
  • Might be hard for beginners
  • Don’t need to be a CDI guru

Should I stay or should I go ?

If you know the most of these you can stay

Agenda

  • CDI Extensions
  • Legacy Code
  • Metrics CDI

Introducing CDI Portable Extensions

CDI portable extensions

  • Extending CDI and Java EE
  • Change CDI meta data
  • Linked to BeanManager Lifecycle

Portable Extensions

One of the most powerful feature of the CDI specification

Not really popularized, partly due to the high level of abstraction

powerful

Container Metadata

Observer pattern to listen for container initialization lifecycle events

Comprehensive access to and modification of the container metadata model

rubik

Plugin Architecture

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) {
    }
}

Bean Manager Lifecycle

lifecycle simple lifecycle legend

Example: Ignoring JPA Entities

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();
    }
}

Remember

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

Legacy Code

Modernize a Legacy Component with the CDI Programming Model

A /* Not So Fictional */ Legacy Component

legacy

Objectives

Improve the public API
and make it typesafe

Integrate the component into the CDI programming model

Without touching the component source / binary

The CDI Way

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
}

Configuration at Injection Points

@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;

Parameterized Types as Metadata

@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)
}
1Get the legacy component bean instance
2Produce 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

The ProcessInjectionPoint Event

Collect 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);
}

The ProcessInjectionPoint Event in Lifecycle

lifecycle pit lifecycle legend light

Collect Injection Points Metadata

class 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)
  }
}
1Observe every injection point of type Transformer on any declaring bean
2Collect the injection point @Legacy metadata, if any

The Bean Interface

Integrate 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();
}

Define Beans Programmatically

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;
    }
}

The AfterBeanDiscovery Event

Add 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 Lifecycle

lifecycle abd lifecycle legend light

Add Beans Programmatically

class 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));
  }
}

Objectives

Improve the public API
and make it typesafe

Integrate the component into the CDI programming model

Without touching the component source / binary

Metrics CDI

Integrate a Third-party Library into the CDI Programming Model

Dropwizard Metrics

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, …​

Objectives

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

Bean Method Interceptors

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();
        }
    }
}

Java Interceptor Bindings

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() {
    }
}

The ProcessAnnotatedType<X> Event

Add the interceptor binding programmatically
public interface ProcessAnnotatedType<X> {
    public AnnotatedType<X> getAnnotatedType();
    public void setAnnotatedType(AnnotatedType<X> type);
    public void veto();
}

ProcessAnnotatedType in Lifecycle

lifecycle pat lifecycle legend light

Add Interceptor Bindings Programmatically

class 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));
  }
}
1Observe types containing the @Timed annotation
2Add the interceptor binding to the annotated methods

Custom 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();
        }
    }
}
1Default Timer histogram is exponentially biased to the past 5 minutes of measurements

The ProcessBean<X> Events

Use 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

The AfterDeploymentValidation Event

class 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)));
  }
}
1Collect the custom Metric producer fields and methods
2Instantiate and register the custom metrics into the Metrics registry

Example: Composition of Metrics

@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();
}
1Metric injection from the registry
2Method instrumentation with interceptors
3Produce a custom Metric instance by composing others

Conclusion

References

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

Antoine Sabot-Durand Antonin Stefanutti

Annexes

Complete Lifecycle Events

lifecycle complete