Mute Java EE with CDI

Antoine Sabot-Durand

  • Red Hat
  • CDI spec lead
  • @antoine_sd
  • next-presso.com
  • github.com/antoinesd

Agenda

  • Meet CDI SPI
  • CDI Extensions
  • Fat entities
  • Async events in CDI 1.2
  • Metrics CDI

Tools used in the code 1/2

Apache Deltaspike

deltaspike
  1. Apache DeltaSpike is a great CDI toolbox
  2. Provide helpers to develop extensions
  3. And a collection of modules like:
    1. Security
    2. Data
    3. Scheduler
  4. More info on deltaspike.apache.org

Tools used in the code 2/2

Arquillian

arquillian
  1. Arquillian is an integration testing platform
  2. It integrates with JUnit
  3. Create your SUT in a dedicated method
  4. Run tests in the target containers of your choice
  5. We’ll use the weld-se-embedded and weld-ee-embedded container
  6. The proper solution to test Java EE code
  7. More info on arquillian.org

Meet CDI SPI

chess ==!

SPI can be split in 4 parts

CDI entry points
Type meta-model
CDI meta-model
SPI dedicated to extensions

SPI providing CDI entry points

entry points

Why having a type meta-model?

Because @Annotations are configuration
but they are also read-only
So to configure we need a mutable meta-model…​
…​for annotated types

SPI for type meta-model

type meta

SPI dedicated to CDI meta-model (1/2)

cdi meta1

SPI dedicated to CDI meta-model (2/2)

cdi meta2

SPI dedicated to extensions

spi extensions

All these SPI interfaces are events containing meta-model SPI

These events fired at boot time can only be observed in CDI extensions
For instance:

cart

A ProcessAnnotatedType<T> event is fired for each type being discovered at boot time
Observing ProcessAnnotatedType<Foo> allows you to prevent Foo to be deployed as a bean by calling ProcessAnnotatedType#veto()

if it was too fast

Read my SPI post: j.mp/spipost

CDI Extensions

Portable extensions

powerful
One of the most powerful feature of the CDI specification
Not really popularized, partly due to:
  1. Their high level of abstraction
  2. The pre-requisite knowledge about basic CDI and SPI
  3. Lack of information (CDI is often perceived as a basic DI solution)

Extensions, what for?

To integrate 3rd party libraries, frameworks or legacy components
To change existing configuration or behavior
To extend CDI and Java EE
Thanks to them, Java EE can evolve between major releases

Extensions, how?

rubik
Observing SPI events at boot time related to the bean manager lifecycle
Checking what meta-data are being created
Modifying these meta-data or creating new ones

More concretely

Service provider of the service javax.enterprise.inject.spi.Extension declared in META-INF/services
Just put the fully qualified name of your extension class in this file
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;

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

Fat Entities

Adding service in JPA entities with CDI

Fat entities? Really?

Not very used in Java EE (bad practice or difficult)
Some platform use them without questioning
CDI allows it so let’s test it !
Don’t do this at home without re-thinking your model and practices

Discover how to use CDI to make your JPA entities smarter

Usually JPA entities looks like

@Entity
public class Person {

    @Id
    public Long getId() { ...}

    public void setId(Long id) { ... }

    public String getName() { ... }

    public void setName(String name) { ... }

}

What we’d like to obtain

@Entity
public class Person {

    @Id
    public Long getId() { ...}

    public void setId(Long id) { ... }

    public String getName() { ... }

    public void setName(String name) { ... }

    public void persist() { ... }

    public void remove() { ... }

    public List<Person> getAllPersonWithMyName() { ... }

}

Our goals to make our entity Fat

Make the entity a @Dependent cdi bean
Define a service bean and inject it in the Entity
Create a JPA entity listener to perform injection in loaded entities

GOAL 1 Make the entity a @Dependent bean
and inject a service in it

The mixed entity & bean component

Fat entity should be dependent scope with no interceptor or decorator
Otherwise CDI container will creat client proxy for it
@Dependent (1)
@Entity
public class Person {
        @Id
        public Long getId() { ...}
        public void setId(Long id) { ... }
        public String getName() { ... }
        public void setName(String name) { ... }
}
1Fat entity can only be dependent scope

We still need a service to support @Transactional

This bean can have a normal scope and support interceptors
@ApplicationScoped
@Transactional
public class PersonService {

    @PersistenceContext
    EntityManager em;

    public List<Person> getAll() { ... }
    public void persist(Person person) { ... }
    public void remove(Person person) { ... }
    public List<Person> getPersonsWithName(String name) { ... }
    public Person getPersonById(Long id) { ... }
}

Using service in entity

@Dependent @Entity
public class Person {

        @Inject
        PersonService service;
        ...
        public void persist() { service.persist(this);}

        public void remove() { service.remove(this);}

        @Transient (1)
        public List<Person> getPersonWithMyName() {
            return service.getPersonsWithName(name);
        }
}
1required to avoid JPA taking this for a property to persist

Getting entities

public class MyService {

    @Inject
    Instance<Person> instPers; (1)

    Public void createPersonWithName(String name) {
        Person p = instPers.get();
        p.setName(name);
    }
}
1Instance<> allows you to request new dependent bean instances in code

Ok for new entities but what about entities loaded by EntityManager?

Goal 2 Create a JPA entity listener to perform injection in loaded entities

Creating an Injector

public class EntityInjector<T> {

    private final InjectionTarget<T> injectionTarget; (1)
    private final CreationalContext<T> ctx;

    public EntityInjector(BeanManager beanManager, Class<T> clazz) {
        AnnotatedType<T> type = beanManager.createAnnotatedType(clazz);
        this.injectionTarget = beanManager.getInjectionTargetFactory(type).createInjectionTarget(null); (2)
        this.ctx = beanManager.createCreationalContext(null); (2)
    }

    public T inject(T instance) {
        injectionTarget.inject(instance, ctx);
        injectionTarget.postConstruct(instance);
        return instance;
    }

}
1InjectionTarget is a CDI SPI class allowing you to perform injection in an unmanaged instance
(A bean instance is a managed InjectionTarget).
2As it is unmanaged, the context is empty

Creating the entity listener

public class ManagingListener {

    @Inject (1)
    BeanManager bm;

    @PostLoad (2)
    public void postLoad(Object entity) {
        Class<?> clazz = entity.getClass();
        EntityInjector manager = new EntityInjector(bm,clazz);
        manager.inject(entity); (3)
    }
}
1it supports injection (in fact it is a CDI unmanaged instance)
2JPA annotation to perform action on loaded entities
3Perform injection in the Entity

Apply listener on the entity

@Dependent
@Entity
@EntityListeners({ ManagingListener.class })
public class Person {

....

}
Goal achieved. Our entity is now Fat
When created or loaded with EntityManager

Asynchronous Event

Adding fire and forget event in CDI 1.2

What we’d like to obtain

We’d like to have the following bean method called asynchronously
when an event of the matching type is fired
@ApplicationScoped
public class MyBean {

    ...

    public void myObserver(@AsyncObs MyEventType evt) { (1)
     ...
    }
}
1@AsyncObs is a parameter annotation we introduce to add this feature

Step to add this feature

create annotation @AsyncObs
Define a new ObserverMethod implementation doing notification asynchronously
Create an extension to detect @AsyncObs occurrences and add observer for them

@AsyncObs annotation

It has nothing special:
@Target({PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface AsyncObs {

}

The new ObserverMethod implementation 1/2

public class AsyncObserverMethod<T> implements ObserverMethod<T> {

    public static int invoked = 0;
    private BeanManager bm;
    private Class<?> beanClass;
    private Method method;
    private Class<T> observedType;
    private Set<Annotation> observedQualifiers;

    public AsyncObserverMethod(BeanManager bm, Class<?> bc, Method m, Class<T> ot, Set<Annotation> oq) {
        this.bm = bm;
        this.beanClass = bc;
        this.method = m;
        this.observedType = ot;
        this.observedQualifiers = oq;
    }

    public Class<?> getBeanClass() { return beanClass; }

    public Type getObservedType() { return observedType; }

    public Set<Annotation> getObservedQualifiers() { return observedQualifiers; }

The new ObserverMethod implementation 2/2

    public Reception getReception() { return Reception.ALWAYS; } (1)

    public TransactionPhase getTransactionPhase() { return TransactionPhase.IN_PROGRESS; } (1)

    public void notify(T event) {
        Bean bean = bm.resolve(bm.getBeans(beanClass));
        Object beanInstance = bm.getReference(bean,beanClass,bm.createCreationalContext(bean));

        CompletableFuture.runAsync(() -> { (2)
            try {
               method.invoke(beanInstance,event);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        });
        invoked++;
    }
}
1Hard coded behaviour to simplify the example
2In true EE world runAsync should be called with a Managed Executor obtained with concurrent utilities API

Extension 1/2

extension collect all classes having methods containing @AsyncObs …​
public class AsyncEventExtension implements Extension {

    Map<Class<?>, Set<Method>> classWithAsyncObs = new HashMap<>();

    public void processAsyncObs(@Observes @WithAnnotations(AsyncObs.class) ProcessAnnotatedType<?> pat) {
        AnnotatedType<?> at = pat.getAnnotatedType();

        classWithAsyncObs.put(at.getJavaClass(), at.getMethods()
                .stream()
                .filter(m -> m.getJavaMember().getParameters()[0].isAnnotationPresent(AsyncObs.class))
                .map(AnnotatedMethod::getJavaMember)
                .collect(Collectors.toSet()));
    }

Extension 2/2

…​ and use this list to register AsyncObserverMethods if the type was eligible to be a bean
    public void addAsyncObservers(@Observes AfterBeanDiscovery abd, BeanManager bm) {
        for (Class<?> clazz : classWithAsyncObs.keySet()) {
            Set<Bean<?>> beans = bm.getBeans(clazz);
            if (!beans.isEmpty()) {
                for (Method m : classWithAsyncObs.get(clazz)) {
                    if (m.getParameterCount() != 1)
                        abd.addDefinitionError(new IllegalArgumentException("An Async Observer method can't have more than one arguments"));
                    else
                        abd.addObserverMethod(new AsyncObserverMethod(bm,clazz, m, m.getParameterTypes()[0], Collections.EMPTY_SET));
                }
            }
        }
    }
}

Goal achieved: we have now fire and forget event observers

Metrics CDI

Integrating Dropwizard Metrics in CDI

Dropwizard Metrics provides

Different metric types: Counter, Gauge, Meter, Timer, …​
Different reporter: JMX, console, SLF4J, CSV, servlet, …​
MetricRegistry object which collects all your app metrics
Annotations for AOP frameworks: @Counted, @Timed, …​
…​ but does not include integration with these frameworks
More at dropwizard.github.io/metrics

Discover how we created CDI integration module for Metrics

Metrics out of the box (without CDI)

class MetricsHelper {
    public static MetricRegistry REGISTRY = new MetricRegistry();
}
class TimedMethodClass {

    void timedMethod() {
        Timer timer = MetricsHelper.REGISTRY.timer("timer"); (1)
        Timer.Context time = timer.time();
        try {
            /*...*/
        } finally {
            time.stop();
        }
    }
}
1Note that if a Timer named "timer" doesn’t exist, MetricRegistry will create a default one and register it

Basic CDI integration

class MetricRegistryBean {
    @Produces
    @ApplicationScoped
    MetricRegistry registry = new MetricRegistry();
}
class TimedMethodBean {

    @Inject MetricRegistry registry;

    void timedMethod() {
        Timer timer = registry.timer("timer");
        Timer.Context time = timer.time();
        try {
            /*...*/
        } finally {
            time.stop();
        }
    }
}
We could have a lot more with advanced CDI features

Our goals to achieve full CDI integration

Produce and inject multiple metrics of the same type
Enable Metrics with the provided annotations
Access same Metric instances through @inject or MetricRegistry API

GOAL 1 Produce and inject
multiple metrics of the same type

What’s the problem with multiple Metrics of the same type?

This code throws a deployment exception (ambiguous dependency)
@Produces
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES)); (1)

@Produces
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, HOURS)); (2)

@Inject
Timer timer; (3)
1This timer that only keeps measurement of last minute is produced as a bean of type Timer
2This timer that only keeps measurement of last hour is produced as a bean of type Timer
3This injection point is ambiguous since 2 eligible beans exist

Solving the ambiguity

We could use the provided @Metric annotation to qualify our beans
@Produces
@Metric(name = "my_timer")
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES));

@Produces
@Metric(name = "my_other_timer")
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, HOURS));

@Inject
@Metric(name = "my_timer")
Timer timer;
That won’t work out of the box since @Metric is not a qualifier

How to declare @Metric as a qualifier?

By observing BeforeBeanDiscovery lifecycle event in an extension
javax.enterprise.inject.spi.BeforeBeanDiscovery
public interface BeforeBeanDiscovery {
    addQualifier(Class<? extends Annotation> qualifier);  (1)
    addQualifier(AnnotatedType<? extends Annotation> qualifier);
    addScope(Class<? extends Annotation> scopeType, boolean normal, boolean passivation);
    addStereotype(Class<? extends Annotation> stereotype, Annotation... stereotypeDef);
    addInterceptorBinding(AnnotatedType<? extends Annotation> bindingType);
    addInterceptorBinding(Class<? extends Annotation> bindingType, Annotation... bindingTypeDef);
    addAnnotatedType(AnnotatedType<?> type);
    addAnnotatedType(AnnotatedType<?> type, String id);
}
1The method we need to declare the @Metric annotation as a CDI qualifier
And use addQualifier() method in the event

BeforeBeanDiscovery is first in lifecycle

lifecycle BBD lifecycle legend

Our first extension

A CDI extension is a class implementing the Extension tag interface
org.cdi.further.metrics.MetricsExtension
public class MetricsExtension implements Extension {

    void addMetricAsQualifier(@Observes BeforeBeanDiscovery bdd) {
        bdd.addQualifier(Metric.class);
    }
}
Extension is activated by adding this file to META-INF/services
javax.enterprise.inject.spi.Extension
org.cdi.further.metrics.MetricsExtension

Goal 1 achieved

We can now write:
@Produces
@Metric(name = "my_timer")
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES));

@Produces
@Metric(name = "my_other_timer")
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, HOURS));

@Inject
@Metric(name = "my_timer")
Timer timer;
And have the Timer injection points satisfied

GOAL 2 Apply Metrics with the provided annotations

Goal 2 in detail

We want to be able to write:
@Timed("timer")  (1)
void timedMethod() {
    // Business code
}
And have the timer "timer" activated during method invocation
The solution is to declare an interceptor and bind it to @Timed

Goal 2 step by step

Create an interceptor for the timer’s technical code
Make @Timed (provided by Metrics) a valid interceptor binding
Programmatically add @Timed as an interceptor binding

Preparing interceptor creation

We should find the technical code that will wrap the business code
class TimedMethodBean {

    @Inject
    MetricRegistry registry;

    void timedMethod() {
        Timer timer = registry.timer("timer");
        Timer.Context time = timer.time();
        try {
            // Business code
        } finally {
            time.stop();
        }
    }
}

Creating the interceptor

Interceptor code is highlighted below
@Interceptor
class TimedInterceptor {
    @Inject MetricRegistry registry; (1)

    @AroundInvoke
    Object timedMethod(InvocationContext context) throws Exception {
        Timer timer = registry.timer(context.getMethod().getAnnotation(Timed.class).name());
        Timer.Context time = timer.time();
        try {
            return context.proceed(); (2)
        } finally {
            time.stop();
        }
    }
}
1In CDI an interceptor is a bean, you can inject other beans in it
2Here the business code of the application is called. All the code around is the technical code.

Activating the interceptor

@Interceptor
@Priority(Interceptor.Priority.LIBRARY_BEFORE)  (1)
class TimedInterceptor {

    @Inject
    MetricRegistry registry;

    @AroundInvoke
    Object timedMethod(InvocationContext context) throws Exception {
        Timer timer = registry.timer(context.getMethod().getAnnotation(Timed.class).name());
        Timer.Context time = timer.time();
        try {
            return context.proceed();
        } finally {
            time.stop();
        }
    }
}
1Giving a @Priority to an interceptor activates and orders it

Add a binding to the interceptor

@Timed  (1)
@Interceptor
@Priority(Interceptor.Priority.LIBRARY_BEFORE)
class TimedInterceptor {

    @Inject
    MetricRegistry registry;

    @AroundInvoke
    Object timedMethod(InvocationContext context) throws Exception {
        Timer timer = registry.timer(context.getMethod().getAnnotation(Timed.class).name());
        Timer.Context time = timer.time();
        try {
            return context.proceed();
        } finally {
            time.stop();
        }
    }
}
1We’ll use Metrics @Timed annotation as interceptor binding

Back on interceptor binding

An interceptor binding is an annotation used in 2 places:
  1. On the interceptor class to bind it to this annotation
  2. On the methods or classes to be intercepted by this interceptor
An interceptor binding should have the @InterceptorBinding annotation or should be declared programmatically
If the interceptor binding annotation has members:
  1. Their values are taken into account to resolve interceptor
  2. Unless members are annotated with @NonBinding

@Timed annotation is not an interceptor binding

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) (1)
public @interface Timed {

    String name() default ""; (2)

    boolean absolute() default false; (2)
}
1Lack the @InterceptorBinding annotation
2None of the members have the @NonBinding annotation, so @Timed(name = "timer1") and @Timed(name = "timer2") will be 2 different interceptor bindings

The required @Timed source code to make it an interceptor binding

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@InterceptorBinding
public @interface Timed {

    @NonBinding String name() default "";

    @NonBinding boolean absolute() default false;
}

How to achieve the required @Timed declaration?

We cannot touch the component source / binary!

Using the AnnotatedType SPI

Thanks to DeltaSpike we can easily create the required AnnotatedType
AnnotatedType createTimedAnnotatedType() throws Exception {
    Annotation nonBinding = new AnnotationLiteral<Nonbinding>() {}; (1)

    return new AnnotatedTypeBuilder().readFromType(Timed.class) (2)
        .addToMethod(Timed.class.getMethod("name"), nonBinding) (3)
        .addToMethod(Timed.class.getMethod("absolute"), nonBinding) (3)
        .create();
}
1This creates an instance of @NonBinding annotation
2It would have been possible but far more verbose to create this AnnotatedType without the help of DeltaSpike. The AnnotatedTypeBuilder is initialized from the Metrics @Timed annotation.
3@NonBinding is added to both members of the @Timed annotation

This extension will do the job

We observe BeforeBeanDiscovery to add a new interceptor binding
public class MetricsExtension implements Extension {

    void addTimedBinding(@Observes BeforeBeanDiscovery bdd) throws Exception {
        Annotation nonBinding = new AnnotationLiteral<Nonbinding>() {};

        bdd.addInterceptorBinding(new AnnotatedTypeBuilder<Timed>()
            .readFromType(Timed.class)
            .addToMethod(Timed.class.getMethod("name"), nonBinding)
            .addToMethod(Timed.class.getMethod("absolute"), nonBinding)
            .create());
    }
}

Goal 2 achieved

We can now write:
@Timed("timer")
void timedMethod() {
    // Business code
}

And have a Metrics Timer applied to the method

  1. Interceptor code should be enhanced to support @Timed on classes
  2. Other interceptors should be developed for other metric types

Our goals

  1. Apply a metric with the provided annotation in AOP style
    @Timed("timer") (1)
    void timedMethod() {
        // Business code
    }
  2. Register automatically produced custom metrics
    @Produces
    @Metric(name = "my_timer") (1)
    Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES));
    // ...
    @Timed("my_timer") (1)
    void timedMethod() { /*...*/ }
    1Annotations provided by Metrics

GOAL 3 Access same Metric instances through @Inject or MetricRegistry API

Goal 3 in detail

When writing:
@Inject
@Metric(name = "my_timer")
Timer timer1;

@Inject
MetricRegistry registry;
Timer timer2 = registry.timer("my_timer");
…​ We want that timer1 == timer2

Goal 3 in detail

@Produces
@Metric(name = "my_timer") (1)
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, TimeUnit.MINUTES));

@Inject
@Metric(name = "my_timer")
Timer timer;

@Inject
MetricRegistry registry;
Timer timer = registry.timer("my_timer"); (2)
1Produced Timer should be added to the Metrics registry when produced
2When retrieved from the registry, a Metric should be identical to the produced instance and vice versa
There are 2 Metric classes, the com.codahale.metrics.Metric interface and the com.codahale.metrics.annotation.Metric annotation

Goal 3 step by step

We need to write an extension that will:
  1. Change how a Metric instance is produced by looking it up in the registry first and producing (and registering) it only if it’s not found. We’ll do this by:
    1. observing the ProcessProducer lifecycle event
    2. decorating Metric Producer to add this new behavior
  2. Produce all Metric instances at the end of bootstrap to have them in registry for runtime
    1. we’ll do this by observing AfterDeploymentValidation event

So we will @Observes these 2 events to add our features

lifecycle BBD PP ADV lifecycle legend

Customizing Metric producing process

We first need to create our implementation of the Producer<X> SPI
class MetricProducer<X extends Metric> implements Producer<X> {

    final Producer<X> decorate;

    final String metricName;

    MetricProducer(Producer<X> decorate, String metricName) {
        this.decorate = decorate;
        this.metricName = metricName;
    }

    // ...

Customizing Metric producing process (continued)

    public X produce(CreationalContext<X> ctx) { (1)
        MetricRegistry reg = getContextualReference(MetricRegistry.class, false); (2)
        if (!reg.getMetrics().containsKey(metricName)) (3)
            reg.register(metricName, decorate.produce(ctx));
        return (X) reg.getMetrics().get(metricName);
    }

    public void dispose(X instance) {}

    public Set<InjectionPoint> getInjectionPoints() {
        return decorate.getInjectionPoints();
    }
}
1The produce method is used by the container at runtime to decorate declared producer with our logic
2BeanProvider.getContextualReference is helper class from DeltaSpike to easily retrieve a bean or bean instance
3If metric name is not in the registry, the original producer is called and its result is added to the registry

We’ll use our MetricProducer in a ProcessProducer observer

This event allow us to substitute the original producer with ours
javax.enterprise.inject.spi.ProcessProducer
public interface ProcessProducer<T, X> {
    AnnotatedMember<T> getAnnotatedMember(); (1)
    Producer<X> getProducer(); (2)
    void setProducer(Producer<X> producer);  (3)
    void addDefinitionError(Throwable t);
}
1Gets the AnnotatedMember associated to the @Produces field or method
2Gets the default producer (useful to decorate it)
3Overrides the producer

Customizing Metric producing process (end)

Here’s the extension code to do this producer decoration
public class MetricsExtension implements Extension {
    // ...
    <X extends Metric> void decorateMetricProducer(@Observes ProcessProducer<?, X> pp) {
        String name = pp.getAnnotatedMember().getAnnotation(Metric.class).name(); (1)
        new pp.setProducer(new MetricProducer<>(pp.getProducer(), name)); (2)
    }
    // ...
}
1We retrieve metric’s name by calling the name() member from @Metric
2We replace the original producer by our producer (which decorates the former)

Producing all the Metric instances at the end of boot time

We do that by observing the AfterDeploymentValidation event
public class MetricsExtension implements Extension {
    // ...
    void registerProducedMetrics(@Observes AfterDeploymentValidation adv) {
        BeanProvider.getContextualReferences(com.codahale.metrics.Metric.class, true); (1)
    }
    // ...
}
1BeanProvider.getContextualReferences() is a method from DeltaSpike BeanProvider helper class.
It creates the list of bean instances for a given bean type (ignoring qualifiers).

Goal 3 achieved

We can now write:
@Produces
@Metric(name = "my_timer")
Timer timer1 = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES));

@Inject
@Metric(name = "my_timer")
Timer timer2;

@Inject
MetricRegistry registry;
Timer timer3 = registry.timer("my_timer");
And make sure that timer1 == timer2 == timer3

Complete extension code

public class MetricsExtension implements Extension {

    void addMetricAsQualifier(@Observes BeforeBeanDiscovery bdd) {
        bdd.addQualifier(Metric.class);
    }

    void addTimedBinding(@Observes BeforeBeanDiscovery bdd) throws Exception {
        Annotation nonBinding = new AnnotationLiteral<Nonbinding>() {};
        bdd.addInterceptorBinding(new AnnotatedTypeBuilder<Timed>().readFromType(Timed.class)
            .addToMethod(Timed.class.getMethod("name"), nonBinding)
            .addToMethod(Timed.class.getMethod("absolute"),nonBinding).create());
    }

    <T extends com.codahale.metrics.Metric> void decorateMetricProducer(@Observes ProcessProducer<?, T> pp) {
        if (pp.getAnnotatedMember().isAnnotationPresent(Metric.class)) {
            String name = pp.getAnnotatedMember().getAnnotation(Metric.class).name();
            pp.setProducer(new MetricProducer(pp.getProducer(), name));
        }
    }

    void registerProducedMetrics(@Observes AfterDeploymentValidation adv) {
        BeanProvider.getContextualReferences(com.codahale.metrics.Metric.class, true);
    }
}

Conclusion

References

CDI Specification - cdi-spec.org
Code Repository - github.com/antoinesd/mute-javaee-with-cdi
Slides generated with Asciidoctor, PlantUML and DZSlides backend
Original slide template - Dan Allen & Sarah White

Antoine Sabot-Durand