- Software Engineer
- Red Hat
- JBoss Fuse team
- @astefanut
- github.com/astefanutti
If you know most of these you can stay |
@Inject
@Produces
Event<T>
@Observes
@Qualifier
InjectionPoint
What’s included: |
What’s not included: |
Arquillian
arquillian-weld-embedded
container adapterSlides available at astefanutti.github.io/further-cdi |
==!
Type meta-model |
CDI meta-model |
CDI entry points |
SPI dedicated to extensions |
Because @Annotations are configuration |
but they are also read-only |
So to configure we need a mutable meta-model… |
…for annotated types |
InjectionPoint can be used to get info about what’s being injected |
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpParam {
@Nonbinding String value();
}
@Produces @HttpParam("")
String getParamValue(InjectionPoint ip, HttpServletRequest req) {
return req.getParameter(ip.getAnnotated().getAnnotation(HttpParam.class).value());
}
@Inject
@HttpParam("productId")
String productId;
InjectionPoint contains info about requested type at @Inject |
class MyMapProducer() {
@Produces
<K, V> Map<K, V> produceMap(InjectionPoint ip) {
if (valueIsNumber(((ParameterizedType) ip.getType())))
return new TreeMap<K, V>();
return new HashMap<K, V>();
}
boolean valueIsNumber(ParameterizedType type) {
Class<?> valueClass = (Class<?>) type.getActualTypeArguments()[1];
return Number.class.isAssignableFrom(valueClass)
}
}
These events fired at boot time can only be observed in CDI extensions |
For instance: |
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() |
One of the most powerful feature of the CDI specification |
Not really popularized, partly due to: |
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 |
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 |
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) {
}
}
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
Integrating Dropwizard Metrics in CDI
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
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();
}
}
}
1 | Note that if a Timer named "timer" doesn’t exist, MetricRegistry will create a default one and register it |
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 |
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
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)
1 | This timer that only keeps measurement of last minute is produced as a bean of type Timer |
2 | This timer that only keeps measurement of last hour is produced as a bean of type Timer |
3 | This injection point is ambiguous since 2 eligible beans exist |
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 |
@Metric
as a qualifier?By observing the BeforeBeanDiscovery lifecycle event in an extension |
javax.enterprise.inject.spi.BeforeBeanDiscovery
public interface BeforeBeanDiscovery {
void addQualifier(Class<? extends Annotation> qualifier); (1)
void addQualifier(AnnotatedType<? extends Annotation> qualifier);
void addScope(Class<? extends Annotation> scopeType, boolean normal, boolean passivation);
void addStereotype(Class<? extends Annotation> stereotype, Annotation... stereotypeDef);
void addInterceptorBinding(AnnotatedType<? extends Annotation> bindingType);
void addInterceptorBinding(Class<? extends Annotation> bindingType, Annotation... bindingTypeDef);
void addAnnotatedType(AnnotatedType<?> type);
void addAnnotatedType(AnnotatedType<?> type, String id);
<T> AnnotatedTypeConfigurator<T> addAnnotatedType(Class<T> type, String id);
<T extends Annotation> AnnotatedTypeConfigurator<T> configureQualifier(Class<T> qualifier);
<T extends Annotation> AnnotatedTypeConfigurator<T> configureInterceptorBinding(Class<T> bindingType);
}
1 | The method we need to declare the @Metric annotation as a CDI qualifier |
And use addQualifier() method in the event |
BeforeBeanDiscovery
is first in lifecycleA 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
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
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 |
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 |
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();
}
}
}
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();
}
}
}
1 | In CDI an interceptor is a bean, you can inject other beans in it |
2 | Here the business code of the application is called. All the code around is the technical code. |
@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();
}
}
}
1 | Giving a @Priority to an interceptor activates and orders it |
@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();
}
}
}
1 | We’ll use Metrics @Timed annotation as interceptor binding |
An interceptor binding is an annotation used in 2 places: |
An interceptor binding should have the @InterceptorBinding annotation or should be declared programmatically |
If the interceptor binding annotation has members: |
@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)
}
1 | Lack the @InterceptorBinding annotation |
2 | None of the members have the @NonBinding annotation, so @Timed(name = "timer1") and @Timed(name = "timer2") will be 2 different interceptor bindings |
@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!
AnnotatedTypeConfigurator
SPIWe observe BeforeBeanDiscovery to add a new interceptor binding |
Use the AnnotatedTypeConfigurator<T> API introduced in CDI 2.0 |
public class MetricsExtension implements Extension {
void addTimedInterceptorBinding(@Observes BeforeBeanDiscovery bdd) {
bbd.configureInterceptorBinding(Timed.class).methods()
.forEach(method -> method.add(Nonbinding.Literal.INSTANCE)); (1)
}
}
1 | @NonBinding is added to all members of the @Timed annotation |
We can now write: |
@Timed("timer")
void timedMethod() {
// Business code
}
And have a Metrics Timer
applied to the method
@Timed
on classes@Timed("timer") (1)
void timedMethod() {
// Business code
}
@Produces
@Metric(name = "my_timer") (1)
Timer timer = new Timer(new SlidingTimeWindowReservoir(1L, MINUTES));
// ...
@Timed("my_timer") (1)
void timedMethod() { /*...*/ }
1 | Annotations provided by Metrics |
GOAL 3 Access same Metric
instances through @Inject
or MetricRegistry
API
When writing: |
@Inject
@Metric(name = "my_timer")
Timer timer1;
@Inject
MetricRegistry registry;
Timer timer2 = registry.timer("my_timer");
… We want that timer1 == timer2 |
@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)
1 | Produced Timer should be added to the Metrics registry when produced |
2 | When 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 |
We need to write an extension that will: |
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:ProcessProducer
lifecycle eventProducer
to add this new behaviorMetric
instances at the end of bootstrap to have them in registry for runtimeAfterDeploymentValidation
event@Observes
these 2 events to add our featuresMetric
producing processObserve the ProcessProducer lifecycle event to override the original producer using the ProducerConfigurator<T> API introduced in CDI 2.0 |
javax.enterprise.inject.spi.ProcessProducer
public interface ProcessProducer<T, X> {
AnnotatedMember<T> getAnnotatedMember(); (1)
Producer<X> getProducer(); (2)
void setProducer(Producer<X> producer);
ProducerConfigurator<X> configureProducer(); (3)
void addDefinitionError(Throwable t);
}
1 | Gets the AnnotatedMember associated to the @Produces field or method |
2 | Gets the default producer (useful to decorate it) |
3 | Overrides the producer |
Metric
producing process (end)public class MetricsExtension implements Extension {
// ...
<T extends c.c.m.Metric> void decorateMetricProducer(@Observes ProcessProducer<?, T> pp, BeanManager manager) {
if (pp.getAnnotatedMember().isAnnotationPresent(Metric.class)) {
String name = pp.getAnnotatedMember().getAnnotation(Metric.class).name(); (1)
Producer<T> producer = pp.getProducer();
pp.configureProducer().produceWith(context -> { (2)
MetricRegistry registry = manager.createInstance().select(MetricRegistry.class).get();
if (registry.getMetrics().containsKey(name))
return (T) registry.getMetrics().get(name);
return registry.register(name, producer.produce(context)); (3)
});
}
}
// ...
}
1 | We retrieve metric’s name by calling the name() member from @Metric |
2 | The produceWith method is used by the container at runtime to decorate declared producer with our logic |
3 | If metric name is not in the registry, the original producer is called and its result is added to the registry |
Metric
instances at the end of boot timeWe do that by observing the AfterDeploymentValidation event |
public class MetricsExtension implements Extension {
// ...
void registerProducedMetrics(@Observes AfterDeploymentValidation adv, BeanManager manager) {
manager.createInstance().select(com.codahale.metrics.Metric.class, Any.Literal.INSTANCE)
.forEach(Object::toString);
}
// ...
}
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 |
public class MetricsExtension implements Extension {
void addMetricAsQualifier(@Observes BeforeBeanDiscovery bdd) {
bdd.addQualifier(Metric.class);
}
void addTimedInterceptorBinding(@Observes BeforeBeanDiscovery bbd) {
bbd.configureInterceptorBinding(Timed.class).methods().forEach(method -> method.add(Nonbinding.Literal.INSTANCE));
}
<T extends com.codahale.metrics.Metric> void decorateMetricProducer(@Observes ProcessProducer<?, T> pp, BeanManager manager) {
if (pp.getAnnotatedMember().isAnnotationPresent(Metric.class)) {
String name = pp.getAnnotatedMember().getAnnotation(Metric.class).name();
Producer<T> producer = pp.getProducer();
pp.configureProducer().produceWith(context -> {
MetricRegistry registry = manager.createInstance().select(MetricRegistry.class).get();
if (registry.getMetrics().containsKey(name))
return (T) registry.getMetrics().get(name);
return registry.register(name, producer.produce(context));
});
}
}
void registerProduceMetrics(@Observes AfterDeploymentValidation adv, BeanManager manager) {
manager.createInstance().select(com.codahale.metrics.Metric.class, Any.Literal.INSTANCE).forEach(Object::toString);
}
}
Test your CDI knowledge
class MySuperBean {
@Inject
Bean<MySuperBean> myMeta; // A [ ]
@Inject
Bean<MyService> serviceMeta; // B [ ]
public MySuperBean(@Inject MyService service) {/*...*/} // C [ ]
@Inject
private void myInitMethod(MyService service) {/*...*/} // D [ ]
@Inject
@PostConstruct
public void myInitMethod2(MyService service) {/*...*/} // E [ ]
}
class MySuperBean {
@Inject
Bean<MySuperBean> myMeta; // A [X]
@Inject
Bean<MyService> serviceMeta; // B [ ]
public MySuperBean(@Inject MyService service) {/*...*/} // C [ ]
@Inject
private void myInitMethod(MyService service) {/*...*/} // D [X]
@Inject
@PostConstruct
public void myInitMethod2(MyService service) {/*...*/} // E [ ]
}
beans.xml
in JAR (CDI 1.2)@Decorator
public abstract class MyDecorator implements MyService {/*...*/} // A [ ]
@Stateless
public class MyServiceImpl implements MyService {/*...*/} // B [ ]
public class MyBean {/*...*/} // C [ ]
@Model
public class MyBean {/*...*/} // D [ ]
@Singleton
public class MyBean {/*...*/} // E [ ]
@ConversationScoped
public class MyBean {/*...*/} // F [ ]
@Decorator
public abstract class MyDecorator implements MyService {/*...*/} // A [X]
@Stateless
public class MyServiceImpl implements MyService {/*...*/} // B [X]
public class MyBean {/*...*/} // C [ ]
@Model
public class MyBean {/*...*/} // D [X]
@Singleton
public class MyBean {/*...*/} // E [ ]
@ConversationScoped
public class MyBean {/*...*/} // F [X]
@ApplicationScoped
public class MyBean {
@Produces
Service produce1(InjectionPoint ip, Bean<Service> myMeta) {/*...*/} // A [ ]
@Produces
@SessionScoped
Service produce2(InjectionPoint ip) {/*...*/} // B [ ]
@Produces
Map<K, V> produceMap(InjectionPoint ip) {/*...*/} // C [ ]
@Produces
Map<String, ? extends Service> produceMap2() {/*...*/} // D [ ]
}
@ApplicationScoped
public class MyBean {
@Produces
Service produce1(InjectionPoint ip, Bean<Service> myMeta) {/*...*/} // A [X]
@Produces
@SessionScoped
Service produce2(InjectionPoint ip) {/*...*/} // B [ ]
@Produces
Map<K, V> produceMap(InjectionPoint ip) {/*...*/} // C [X]
@Produces
Map<String, ? extends Service> produceMap2() {/*...*/} // D [ ]
}
class FirstBean {
@Inject
Event<Post> postEvent;
public void saveNewPost(Post myPost) {
postEvent.select(new AnnotationLiteral()<French> {}).fire(myPost);
}
}
class SecondBean {
void listenFrPost(@Observes @French Post post) {/*...*/} // A [ ]
void listenPost(@Observes Post post) {/*...*/} // B [ ]
void listenEnPost(@Observes @English Post post) {/*...*/} // C [ ]
void listenObject(@Observes Object obj) {/*...*/} // D [ ]
}
class FirstBean {
@Inject
Event<Post> postEvent;
public void saveNewPost(Post myPost) {
postEvent.select(new AnnotationLiteral()<French> {}).fire(myPost);
}
}
class SecondBean {
void listenFrPost(@Observes @French Post post) {/*...*/} // A [X]
void listenPost(@Observes Post post) {/*...*/} // B [X]
void listenEnPost(@Observes @English Post post) {/*...*/} // C [ ]
void listenObject(@Observes Object obj) {/*...*/} // D [X]
}
How to use CDI as dependency injection container for an integration framework (Apache Camel)
Open-source integration framework based on known Enterprise Integration Patterns |
Provides a variety of DSLs to write routing and mediation rules |
Provides support for bean binding and seamless integration with DI frameworks |
Discover how we created CDI integration module for Camel
public static void main(String[] args) {
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
public void configure() {
from("file:target/input?delay=1s")
.log("Sending message [${body}] to JMS ...")
.to("sjms:queue:output"); (1)
}
});
PropertiesComponent properties = new PropertiesComponent();
properties.setLocation("classpath:camel.properties");
context.addComponent("properties", properties); // Registers the "properties" component
SjmsComponent component = new SjmsComponent();
component.setConnectionFactory(new ActiveMQConnectionFactory("vm://broker?broker.persistent=false"));
jms.setConnectionCount(Integer.valueOf(context.resolvePropertyPlaceholders("{{jms.maxConnections}}")));
context.addComponent("sjms", jms); // Registers the "sjms" component
context.start();
}
1 | This route watches a directory every second and sends new files content to a JMS queue |
class FileToJmsRouteBean extends RouteBuilder {
@Override
public void configure() {
from("file:target/input?delay=1s")
.log("Sending message [${body}] to JMS...")
.to("sjms:queue:output");
}
}
class PropertiesComponentFactoryBean {
@Produces @ApplicationScoped
PropertiesComponent propertiesComponent() {
PropertiesComponent properties = new PropertiesComponent();
properties.setLocation("classpath:camel.properties");
return properties;
}
}
class JmsComponentFactoryBean {
@Produces @ApplicationScoped
SjmsComponent sjmsComponent(PropertiesComponent properties) throws Exception {
SjmsComponent jms = new SjmsComponent();
jms.setConnectionFactory(new ActiveMQConnectionFactory("vm://broker?broker.persistent=false"));
jms.setConnectionCount(Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));
return component;
}
}
@ApplicationScoped
class CamelContextBean extends DefaultCamelContext {
@Inject
CamelContextBean(FileToJmsRouteBean route, SjmsComponent jms, PropertiesComponent properties) {
addComponent("properties", properties);
addComponent("sjms", jms);
addRoutes(route);
}
@PostConstruct
void startContext() {
super.start();
}
@PreDestroy
void preDestroy() {
super.stop();
}
}
We could have a lot more with advanced CDI features |
CamelContext
manually.to("sjms:queue:output"); // Lookup by name (sjms) and type (Component)
context.resolvePropertyPlaceholders("{{jms.maxConnections}}");
// Lookup by name (properties) and type (Component)
@PropertyInject(value = "jms.maxConnections", defaultValue = "10")
int maxConnections;
Manage the creation and the configuration of the CamelContext bean |
Bind the CamelContext lifecycle to that of the CDI container |
Implement the Camel registry SPI to look up CDI bean references |
Use a custom InjectionTarget for CDI beans containing Camel annotations |
We need to write an extension that will: |
CamelContext
bean by observing the AfterBeanDiscovery
lifecycle eventRouteBuilder
and add them to the Camel contextAfterDeploymentValidation
event is fired (resp. the bean destroy
method is called)BeanManager
to lookup CDI beans by name and typeProcessAnnotatedType
event and modify how they get injected by observing the ProcessInjectionTarget
lifecycle event@Observes
these 4 events to add our featuresCamelContext
beanAutomatically add a CamelContext bean in the deployment archive |
How to add a bean programmatically?
Use the BeanConfigurator<T> API introduced in CDI 2.0 |
javax.enterprise.inject.spi.configurator.BeanConfigurator<T>
public interface BeanConfigurator<T> {
BeanConfigurator<T> beanClass(Class<?> beanClass);
<U extends T> BeanConfigurator<U> createWith(Function<CreationalContext<U>, U> callback);
<U extends T> BeanConfigurator<U> produceWith(Function<Instance<Object>, U> callback);
BeanConfigurator<T> destroyWith(BiConsumer<T, CreationalContext<T>> callback);
BeanConfigurator<T> disposeWith(BiConsumer<T, Instance<Object>> callback);
<U extends T> BeanConfigurator<U> read(AnnotatedType<U> type);
BeanConfigurator<T> read(BeanAttributes<?> beanAttributes);
BeanConfigurator<T> addType(Type type);
BeanConfigurator<T> scope(Class<? extends Annotation> scope);
BeanConfigurator<T> addQualifier(Annotation qualifier);
BeanConfigurator<T> name(String name);
// ...
}
Access the BeanConfigurator<T> API by observing the AfterBeanDiscovery lifecycle event |
public class CamelExtension implements Extension {
void addCamelContextBean(@Observes AfterBeanDiscovery abd) {
abd.addBean()
.types(CamelContext.class)
.scope(ApplicationScoped.class)
.produceWith(instance -> new DefaultCamelContext());
}
}
Instantiate the CamelContext bean and the RouteBuilder beans in the AfterDeploymentValidation lifecycle event |
public class CamelExtension implements Extension {
// ...
void configureContext(@Observes AfterDeploymentValidation adv, BeanManager manager) {
CamelContext context = manager.createInstance().select(CamelContext.class).get();
manager.createInstance().select(RoutesBuilder.class).forEach(context::addRoutes);
}
}
Start the context when the AfterDeploymentValidation event is fired |
public class CamelExtension implements Extension {
// ...
void configureContext(@Observes AfterDeploymentValidation adv, BeanManager manager) {
CamelContext context = manager.createInstance().select(CamelContext.class).get();
manager.createInstance().select(RoutesBuilder.class).forEach(context::addRoutes);
context.start();
}
}
Stop the context when the associated bean is destroyed |
public class CamelExtension implements Extension {
// ...
void addCamelContextBean(@Observes AfterBeanDiscovery abd) {
abd.addBean()
.types(CamelContext.class)
.scope(ApplicationScoped.class)
.produceWith(instance -> new DefaultCamelContext())
.disposeWith((context, instance) -> context.stop());
}
}
We can get rid of the following code: |
@ApplicationScoped
class CamelContextBean extends DefaultCamelContext {
@Inject
CamelContextBean(FileToJmsRouteBean route, SjmsComponent jms, PropertiesComponent properties) {
addComponent("properties", propertiesComponent);
addComponent("sjms", sjmsComponent);
addRoutes(route);
}
@PostConstruct
void startContext() {
super.start();
}
@PreDestroy
void stopContext() {
super.stop();
}
}
How to retrieve CDI beans from the Camel DSL?
.to("sjms:queue:output"); // Lookup by name (sjms) and type (Component)
context.resolvePropertyPlaceholders("{{jms.maxConnections}}");
// Lookup by name (properties) and type (Component)
// And also...
.bean(MyBean.class); // Lookup by type and Default qualifier
.beanRef("beanName"); // Lookup by name
Implement the Camel registry SPI and use the BeanManager to lookup for CDI bean contextual references by name and type |
class CamelCdiRegistry implements Registry {
private final BeanManager manager;
CamelCdiRegistry(BeanManager manager) {
this.manager = manager;
}
public Object lookupByName(String name) {
return lookupByNameAndType(name, Object.class);
}
@Override
public <T> T lookupByNameAndType(String name, Class<T> type) {
return manager.createInstance().select(type, NamedLiteral.of(name)).stream()
.findAny().orElse(null);
}
// ...
}
CamelCdiRegistry
to the Camel contextpublic class CamelExtension implements Extension {
void addCamelContextBean(@Observes AfterBeanDiscovery abd, BeanManager manager) {
abd.addBean()
.types(CamelContext.class)
.scope(ApplicationScoped.class)
.produceWith(instance -> new DefaultCamelContext(new CamelCdiRegistry(manager)))
.disposeWith((context, instance) -> context.stop());
}
}
We can declare the sjms component with the @Named qualifier |
class JmsComponentFactoryBean {
@Produces
@Named("sjms")
@ApplicationScoped
SjmsComponent sjmsComponent(PropertiesComponent properties) {
SjmsComponent jms = new SjmsComponent();
jms.setConnectionFactory(new ActiveMQConnectionFactory("vm://broker?..."));
jms.setConnectionCount(
Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));
return component;
}
}
Declare the properties component with the @Named qualifier |
class PropertiesComponentFactoryBean {
@Produces
@Named("properties")
@ApplicationScoped
PropertiesComponent propertiesComponent() {
PropertiesComponent properties = new PropertiesComponent();
properties.setLocation("classpath:camel.properties");
return properties;
}
}
And get rid of the code related to the components registration: |
@ApplicationScoped
class CamelContextBean extends DefaultCamelContext {
@Inject
CamelContextBean(FileToJmsRouteBean route, SjmsComponent jms, PropertiesComponent properties) {
addComponent("properties", propertiesComponent);
addComponent("sjms", sjmsComponent);
addRoutes(route);
}
@PostConstruct
void startContext() {
super.start();
}
@PreDestroy
void stopContext() {
super.stop();
}
}
Camel provides a set of DI framework agnostic annotations for resource injection |
@PropertyInject(value = "jms.maxConnections", defaultValue = "10")
int maxConnections;
// But also...
@EndpointInject(uri = "jms:queue:foo")
Endpoint endpoint;
@BeanInject("foo")
FooBean foo;
How to support custom annotations injection?
Create a custom InjectionTarget that uses the default Camel bean post processor DefaultCamelBeanPostProcessor |
javax.enterprise.inject.spi.InjectionTarget
public interface InjectionTarget<T> extends Producer<T> {
void inject(T instance, CreationalContext<T> ctx);
void postConstruct(T instance);
void preDestroy(T instance);
}
Hook it into the CDI injection mechanism by observing the ProcessInjectionTarget lifecycle event |
Only for beans containing Camel annotations by observing the ProcessAnnotatedType lifecycle and using @WithAnnotations |
InjectionTarget
class CamelInjectionTarget<T> implements InjectionTarget<T> {
final InjectionTarget<T> delegate;
final DefaultCamelBeanPostProcessor processor;
CamelInjectionTarget(InjectionTarget<T> target, final BeanManager manager) {
delegate = target;
processor = new DefaultCamelBeanPostProcessor() {
public CamelContext getOrLookupCamelContext() {
return manager.createInstance().select(CamelContext.class).get();
}
};
}
public void inject(T instance, CreationalContext<T> ctx) {
processor.postProcessBeforeInitialization(instance, null); (1)
delegate.inject(instance, ctx);
}
//...
}
1 | Call the Camel default bean post-processor before CDI injection |
InjectionTarget
Observe the ProcessInjectionTarget event and set the InjectionTarget |
javax.enterprise.inject.spi.ProcessInjectionTarget
public interface ProcessInjectionTarget<X> {
AnnotatedType<X> getAnnotatedType();
InjectionTarget<X> getInjectionTarget();
void setInjectionTarget(InjectionTarget<X> injectionTarget);
void addDefinitionError(Throwable t);
}
To decorate it with the CamelInjectionTarget |
public class CamelExtension implements Extension {
<T> void camelBeansPostProcessor(@Observes ProcessInjectionTarget<T> pit, BeanManager manager) {
pit.setInjectionTarget(new CamelInjectionTarget<>(pit.getInjectionTarget(), manager));
}
}
public class CamelExtension implements Extension {
final Set<AnnotatedType<?>> camelBeans = new HashSet<>();
void camelAnnotatedTypes(@Observes @WithAnnotations(PropertyInject.class)
ProcessAnnotatedType<?> pat) { (1)
camelBeans.add(pat.getAnnotatedType());
}
<T> void camelBeansPostProcessor(@Observes ProcessInjectionTarget<T> pit,
BeanManager manager) {
if (camelBeans.contains(pit.getAnnotatedType())) (2)
pit.setInjectionTarget(new CamelInjectionTarget<>(pit.getInjectionTarget(), manager));
}
}
1 | Detect all the types containing Camel annotations with @WithAnnotations |
2 | Decorate the InjectionTarget corresponding to these types |
Instead of injecting the PropertiesComponent bean to resolve a configuration property |
class JmsComponentFactoryBean {
@Produces
@Named("sjms")
@ApplicationScoped
SjmsComponent sjmsComponent(PropertiesComponent properties) {
SjmsComponent jms = new SjmsComponent();
jms.setConnectionFactory(new ActiveMQConnectionFactory("vm://broker?..."));
jms.setConnectionCount(Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));
return component;
}
}
We can directly rely on the @PropertyInject Camel annotation in CDI beans |
class JmsComponentFactoryBean {
@PropertyInject("jms.maxConnections")
int maxConnections;
@Produces
@Named("sjms")
@ApplicationScoped
SjmsComponent sjmsComponent() {
SjmsComponent component = new SjmsComponent();
jms.setConnectionFactory(new ActiveMQConnectionFactory("vm://broker?..."));
component.setConnectionCount(maxConnections);
return component;
}
}
AOP instrumentation of the Camel DSL |
from("file:target/input?delay=1s")
.log("Sending message [${body}] to JMS...")
.to("sjms:queue:output");
With CDI observers |
from("file:target/input?delay=1s").to("sjms:queue:output").id("join point");
void advice(@Observes @Node("join point") Exchange exchange) {
logger.info("Sending message [{}] to JMS...", exchange.getIn().getBody(String.class));
}
We can create a CDI qualifier to hold the Camel node id metadata: |
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Node {
String value();
}
And create an extension that will: |
@Node
qualifier by observing the ProcessObserverMethod
event and collect the Camel processor nodes to be instrumentedInterceptStrategy
interface that will fire a CDI event each time an Exchange
is processed by the instrumented nodesObserve the ProcessObserverMethod lifecycle event |
javax.enterprise.inject.spi.ProcessObserverMethod
public interface ProcessObserverMethod<T, X> {
AnnotatedMethod<X> getAnnotatedMethod();
ObserverMethod<T> getObserverMethod();
void addDefinitionError(Throwable t);
}
And collect the observer method metadata |
public class CamelExtension implements Extension {
final Set<Node> nodePointcuts = new HashSet<>();
void camelNodePointcuts(@Observes ProcessObserverMethod<Exchange, ?> pom) {
pom.getObserverMethod().getObservedQualifiers().stream()
.filter(q -> q instanceof Node).map(Node.class::cast).forEach(nodePointcuts::add);
}
}
Intercept matching nodes and fire a CDI event |
void configureCamelContext(@Observes AfterDeploymentValidation adv, BeanManager manager) {
CamelContext context = manager.createInstance().select(CamelContext.class).get();
context.addInterceptStrategy((camel, definition, target, next) -> definition.hasCustomIdAssigned()
? nodePointcuts.stream().filter(node -> definition.getId().equals(node.value())).findFirst() (1)
.map(node -> (Processor) new DelegateAsyncProcessor(target) {
public boolean process(Exchange exchange, AsyncCallback callback) {
manager.fireEvent(exchange, node); (2)
return super.process(exchange, callback);
}
}).orElse(target) (2)
: target);
manager.createInstance().select(RoutesBuilder.class).forEach(context::addRoutes);
context.start();
}
1 | Checks if there is a @Node qualifier matching the processor id (with customIdAssigned ) |
2 | Returns a delegate processor that fires a event whenever an exchange is processed, or returns the default processor |
We can define join points in the Camel DSL |
from("file:target/input?delay=1s").to("sjms:queue:output").id("join point");
And advice them with CDI observers |
void advice(@Observes @Node("join point") Exchange exchange) {
List<MessageHistory> history = exchange.getProperty(Exchange.MESSAGE_HISTORY,
List.class);
logger.info("Sending message [{}] to [{}]...",
exchange.getIn().getBody(String.class),
history.get(history.size() - 1).getNode().getLabel());
}
public class CamelExtension implements Extension {
Set<AnnotatedType<?>> camelBeans = new HashSet<>(); Set<Node> nodes = new HashSet<>();
void camelAnnotatedTypes(@Observes @WithAnnotations(PropertyInject.class) ProcessAnnotatedType<?> pat) {
camelBeans.add(pat.getAnnotatedType());
}
<T> void camelBeansPostProcessor(@Observes ProcessInjectionTarget<T> pit, BeanManager m) {
if (camelBeans.contains(pit.getAnnotatedType())) pit.setInjectionTarget(new CamelInjectionTarget<>(pit.getInjectionTarget(), m));
}
void camelNodePointcuts(@Observes ProcessObserverMethod<Exchange, ?> pom) {
pom.getObserverMethod().getObservedQualifiers().stream().filter(q -> q instanceof Node).map(Node.class::cast).forEach(nodes::add);
}
void addCamelContext(@Observes AfterBeanDiscovery abd, BeanManager m) {
abd.addBean().types(CamelContext.class).scope(ApplicationScoped.class)
.produceWith(instance -> new DefaultCamelContext(new CamelCdiRegistry(m))).disposeWith((context, instance) -> context.stop());
}
void configureCamelContext(@Observes AfterDeploymentValidation adv, BeanManager manager) {
CamelContext context = manager.createInstance().select(CamelContext.class).get();
context.addInterceptStrategy((camel, definition, target, next) -> definition.hasCustomIdAssigned() ? nodes.stream()
.filter(node -> definition.getId().equals(node.value())).findFirst().map(node -> new DelegateAsyncProcessor(target) {
public boolean process(Exchange exchange, AsyncCallback callback) {
manager.fireEvent(exchange, node);
return super.process(exchange, callback);
}
}).orElse(target) : target);
manager.createInstance().select(RoutesBuilder.class).forEach(context::addRoutes);
context.start();
}
}
CDI Specification - cdi-spec.org |
Slides sources - github.com/astefanutti/further-cdi |
Metrics CDI sources - github.com/astefanutti/metrics-cdi |
Camel CDI sources - github.com/astefanutti/camel-cdi |
Slides generated with Asciidoctor, PlantUML and DZSlides backend |
Original slide template - Dan Allen & Sarah White |