Going further with CDI 1.2 Antoine Sabot-Durand · Antonin Stefanutti
GoingfurtherwithCDI1.2
AntoineSabot-Durand·AntoninStefanutti
CDI@Murex
CDIastheproductivityecosystemtobuildconnectivityinterfaces
CDI@Murex
AntoineSabot-Durand
SeniorSoftwareEngineerCDIco-speclead,JavaEE8EGRedHat,Inc.@antoine_sdwww.next-presso.comgithub.com/antoinesd
ShouldIstayorshouldIgo?
AtalkaboutadvancedCDI
Mightbehardforbeginners
Don’tneedtobeaCDIguru
@Inject @ProducesEvent<T> @Observes@Qualifier InjectionPoint
ShouldIstayorshouldIgo?
Ifyouknowthemostoftheseyoucanstay
Moreconcretely
What’sincluded:
1. Realusecasesfromreallifewithrealusers2. Newapproachtointroduceportableextensionconcepts3. CodeinIDEwithtests
What’snotincluded:
1. IntroductiontoCDI2. Oldcontentonextension3. WorkwithContext(need2morehours)
Toolsusedinthecode1/2
ApacheDeltaspike
1. ApacheDeltaSpikeisagreatCDItoolbox
2. Providehelperstodevelopextension3. Andacollectionofmoduleslike:
1. Security2. Data3. Scheduler
4. Moreinfoondeltaspike.apache.org
Toolsusedinthecode2/2
Arquillian
1. Arquillianisanintegrationtestplatform2. ItintegrateswithJUnit3. Createyourdeploymentinadedicatedmethod
4. Andlaunchyourtestsagainstthecontainerofyourchoice
5. We’llusethe weld-se-embedded andweld-ee-embedded container
6. TherightsolutiontotestJavaEEcode7. Moreinfoonarquillian.org
Agenda
MeetCDISPIIntroducingCDIExtensionsMetricsCDICDIQuizzCamelCDI
Slidesavailableatastefanutti.github.io/further-cdi
MeetCDISPI
SPIcanbesplitin4parts
SPIcanbesplitin4parts
Typemeta-model
SPIcanbesplitin4parts
CDImeta-model
SPIcanbesplitin4parts
CDIentrypoints
SPIcanbesplitin4parts
SPIdedicatedtoextensions
Whyhavingatypemeta-model?
Because @Annotations areconfiguration
buttheyarealsoread-only
Sotoconfigureweneedamutablemeta-model…
… forannotatedtypes
SPIfortypemeta-model
Annotated
TypegetBaseType()Set<Type>getTypeClosure()<TextendsAnnotation>getAnnotation(Class<T>)Set<Annotation>getAnnotations()booleanisAnnotationPresent(Class<?extendsAnnotation>)
AnnotatedMemberX
MembergetJavaMember()booleanisStatic()AnnotatedType<X>getDeclaringType()
AnnotatedParameterX
intgetPosition()AnnotatedCallable<X>getDeclaringCallable()
AnnotatedTypeX
Class<X>getJavaClass()Set<AnnotatedConstructor<X>>getConstructors()Set<AnnotatedMethod<?superX>>getMethods()Set<AnnotatedField<?superX>>getFields()
AnnotatedCallableX
List<AnnotatedParameter<X>>getParameters()
AnnotatedFieldX
FieldgetJavaMember()
AnnotatedConstructorX
Constructor<X>getJavaMember()
AnnotatedMethodX
MethodgetJavaMember()
SPIdedicatedtoCDImeta-modelBeanAttributes
T
Set<Type>getTypes()Set<Annotation>getQualifiers()Class<?extendsAnnotation>getScope()StringgetName()Set<Class<?extendsAnnotation>>getStereotypes()booleanisAlternative()
BeanT
Class<?>getBeanClass()Set<InjectionPoint>getInjectionPoints()booleanisNullable()
InterceptorT
Set<Annotation>getInterceptorBindings()booleanintercepts(InterceptionTypetype)Objectintercept(InterceptionType,T,InvocationContext)
DecoratorT
TypegetDelegateType()Set<Annotation>getDelegateQualifiers()Set<Type>getDecoratedTypes()
ProducerT
Tproduce(CreationalContext<T>)voiddispose(T)Set<InjectionPoint>getInjectionPoints()
InjectionTargetT
voidinject(T,CreationalContext<T>)voidpostConstruct(T)voidpreDestroy(T)
InjectionPoint
TypegetType()Set<Annotation>getQualifiers()Bean<?>getBean()MembergetMember()AnnotatedgetAnnotated()booleanisDelegate()booleanisTransient()
ObserverMethodT
Class<?>getBeanClass()TypegetObservedType()Set<Annotation>getObservedQualifiers()ReceptiongetReception()TransactionPhasegetTransactionPhase()voidnotify(T)
EventMetadata
Set<Annotation>getQualifiers()InjectionPointgetInjectionPoint()TypegetType()
ThisSPIcanbeusedinyourcode(1/2)
InjectionPoint canbeusedtogetinfoaboutwhat’sbeinginjected
@Qualifier@Retention(RetentionPolicy.RUNTIME)public@interfaceHttpParam{@NonbindingpublicStringvalue();}
@Produces@HttpParam("")StringgetParamValue(InjectionPointip,HttpServletRequestreq){returnreq.getParameter(ip.getAnnotated().getAnnotation(HttpParam.class).value());}
@Inject@HttpParam("productId")StringproductId;
ThisSPIcanbeusedinyourcode(2/2)
InjectionPoint containsinfoaboutrequestedtypeat @Inject
classMyMapProducer(){
@Produces<K,V>Map<K,V>produceMap(InjectionPointip){if(valueIsNumber(((ParameterizedType)ip.getType())))returnnewTreeMap<K,V>();returnnewHashMap<K,V>();}
booleanvalueIsNumber(ParameterizedTypetype){Class<?>valueClass=(Class<?>)type.getActualTypeArguments()[1];returnNumber.class.isAssignableFrom(valueClass)}}
SPIprovidingCDIentrypoints
UnmanagedT
Unmanaged(BeanManager,Class<T>)Unmanaged(Class<T>)UnmanagedInstance<T>newInstance()
UnmanagedInstanceT
Tget()UnmanagedInstance<T>produce()UnmanagedInstance<T>inject()UnmanagedInstance<T>postConstruct()UnmanagedInstance<T>preDestroy()UnmanagedInstance<T>dispose()
CDIProvider
CDI<Object>getCDI()
BeanManager
ObjectgetReference(Bean<?>,Type,CreationalContext<?>)ObjectgetInjectableReference(InjectionPoint,CreationalContext<?>)Set<Bean<?>>getBeans(Type,Annotation[])Bean<?extendsX>resolve(Set<Bean<?extendsX>>)voidvalidate(InjectionPoint)voidfireEvent(Object,Annotation[])
booleanisQualifier(Class<?extendsAnnotation>)booleanisStereotype(Class<?extendsAnnotation>)booleanareQualifiersEquivalent(Annotation,Annotation)booleanareInterceptorBindingsEquivalent(Annotation,Annotation)ContextgetContext(Class<?extendsAnnotation>)ELResolvergetELResolver()ExpressionFactorywrapExpressionFactory(ExpressionFactory)AnnotatedType<T>createAnnotatedType(Class<T>)InjectionTarget<T>createInjectionTarget(AnnotatedType<T>)InjectionTargetFactory<T>getInjectionTargetFactory(AnnotatedType<T>)BeanAttributes<T>createBeanAttributes(AnnotatedType<T>)Bean<T>createBean(BeanAttributes<T>,Class<X>,ProducerFactory<X>)InjectionPointcreateInjectionPoint(AnnotatedField<?>)
somemethodsskiped
CDIT
Set<CDIProvider>discoveredProvidersCDIProviderconfiguredProvider
CDI<Object>current()voidsetCDIProvider(CDIProviderprovider)BeanManagergetBeanManager()
SPIdedicatedtoextensions
BeforeBeanDiscovery
addQualifier(Class<?extendsAnnotation>)addScope(Class<?extendsAnnotation>,boolean,boolean)addStereotype(Class<?extendsAnnotation>,Annotation[])addInterceptorBinding(Class<?extendsAnnotation>,Annotation[])addAnnotatedType(AnnotatedType<?>)
AfterTypeDiscovery
List<Class<?>>getAlternatives()List<Class<?>>getInterceptors()List<Class<?>>getDecorators()addAnnotatedType(AnnotatedType<?>,String)
AfterDeploymentValidation BeforeShutdown
AfterBeanDiscovery
addBean(Bean<?>)addObserverMethod(ObserverMethod<?>)addContext(Context)AnnotatedType<T>getAnnotatedType(Class<T>,String)Iterable<AnnotatedType<T>>getAnnotatedTypes(Class<T>)
ProcessAnnotatedTypeX
AnnotatedType<X>getAnnotatedType()voidsetAnnotatedType(AnnotatedType<X>)veto()
ProcessBeanX
AnnotatedgetAnnotated()Bean<X>getBean()
ProcessBeanAttributesT
AnnotatedgetAnnotated()BeanAttributes<T>getBeanAttributes()setBeanAttributes(BeanAttributes<T>)veto()
ProcessInjectionPointT,X
InjectionPointgetInjectionPoint()setInjectionPoint(InjectionPoint)
ProcessInjectionTargetX
AnnotatedType<X>getAnnotatedType()InjectionTarget<X>getInjectionTarget()setInjectionTarget(InjectionTarget<X>)
ProcessObserverMethodT,X
AnnotatedMethod<X>getAnnotatedMethod()ObserverMethod<T>getObserverMethod()
ProcessProducerT,X
AnnotatedMember<T>getAnnotatedMember()Producer<X>getProducer()setProducer(Producer<X>)
AlltheseSPIinterfacesareeventscontainingmeta-modelSPI
TheseeventsfiredatboottimecanonlybeobservedinCDIextensions
Forinstance:
A ProcessAnnotatedType<T> eventisfiredforeachtypebeingdiscoveredatboottime
Observing ProcessAnnotatedType<Foo>allowsyoutoprevent Foo tobedeployedasabeanbycallingProcessAnnotatedType#veto()
IntroducingCDIPortableExtensions
Portableextensions
OneofthemostpowerfulfeatureoftheCDIspecification
Notreallypopularized,partlydueto:
1. Theirhighlevelofabstraction2. ThegoodknowledgeonBasicCDIandSPI3. Lackofinformation(CDIisoftenreducedtoabasicDIsolution)
Extensions,whatfor?
Tointegrate3rdpartylibraries,frameworksorlegacycomponents
Tochangeexistingconfigurationorbehavior
ToextendCDIandJavaEE
Thankstothem,JavaEEcanevolvebetweenmajorreleases
Extensions,how?
ObservingSPIeventsatboottimerelatedtothebeanmanagerlifecycle
Checkingwhatmeta-dataarebeingcreated
Modifyingthesemeta-dataorcreatingnewones
Moreconcretely
Serviceprovideroftheservicejavax.enterprise.inject.spi.Extension declaredinMETA-INF/services
Justputthefullyqualifiednameofyourextensionclassinthisfile
importjavax.enterprise.event.Observes;importjavax.enterprise.inject.spi.Extension;
publicclassCdiExtensionimplementsExtension{
voidbeforeBeanDiscovery(@ObservesBeforeBeanDiscoverybbd){}//...
voidafterDeploymentValidation(@ObservesAfterDeploymentValidationadv){}}
InternalStep HappenOnce LooponElements
Beanmanagerlifecycle
DeploymentStart
BeforeBean
Discovery
ScanArchive
ProcessAnnotated
Type
AfterType
Discovery
BeanEligibility
Check
ProcessInjectionPoint
ProcessInjectionTarget
ProcessBean
Attributes
ProcessBean
ProcessProducer
ProcessObserverMethod
AfterBean
Discovery
AfterDeploymentValidation
ApplicationRunning
BeforeShutdown
UndeployApplication
Example:IgnoringJPAentities
ThefollowingextensionpreventsCDItomanageentities
Thisisacommonlyadmittedgoodpractice
publicclassVetoEntityimplementsExtension{
voidvetoEntity(@Observes@WithAnnotations(Entity.class)ProcessAnnotatedType<?>pat){pat.veto();}}
ExtensionsarelaunchedduringbootstrapandarebasedonCDIevents
Oncetheapplicationisbootstrapped,theBeanManagerisinread-onlymode(noruntimebeanregistration)
Youonlyhaveto@Observes built-inCDIeventstocreateyourextensions
Remember
Howtointegratea3rdpartyLibrary(DropwizardMetrics)intotheCDIProgrammingModel
3rdpartyLibrary
AboutDropwizardMetrics
Providesdifferentmetrictypes: Counter , Gauge , Meter , Timer ,…
Providesdifferentreporter:JMX,console,SLF4J,CSV,servlet,…
Providesa MetricRegistry whichcollectsallyourappmetrics
ProvidesannotationsforAOPframeworks: @Counted , @Timed ,…
… butdoesnotincludeintegrationwiththeseframeworks
Moreatdropwizard.github.io/metrics
DiscoverhowwecreatedCDIintegrationmoduleforMetrics
Metricsoutofthebox(withoutCDI)classMetricsHelper{publicstaticMetricRegistryregistry=newMetricRegistry();}
classTimedMethodClass{
voidtimedMethod(){Timertimer=MetricsHelper.registry.timer("timer");Timer.Contexttime=timer.time();try{/*...*/}finally{time.stop();}}}
Notethatif Timer called "timer" doesn’texist, MetricRegistry willcreateadefaultoneandregisterit
1
1
BasicCDIintegrationclassMetricRegistryBean{@Produces@ApplicationScopedMetricRegistryregistry=newMetricRegistry();}
classTimedMethodBean{@InjectMetricRegistryregistry;
voidtimedMethod(){Timertimer=registry.timer("timer");Timer.Contexttime=timer.time();try{/*...*/}finally{time.stop();}}}
WecouldhavealotmorewithadvancedCDIfeatures
Ourgoals1. ApplyametricwiththeprovidedannotationinAOPstyle
@Timed("timer")voidtimedMethod(){//...}
2. Registerautomaticallyproducedcustommetrics@Produces@Metric(name="myTimer")Timertimer=newTimer(newSlidingTimeWindowReservoir(1L,TimeUnit.MINUTES));//...@Timed("myTimer")voidtimedMethod(){/*...*/}
AnnotationsprovidedbyMetrics
1
1
1
1
StepstoapplyatimerinAOPstyle
Createaninterceptorforthetimertechnicalcode
MaketheMetricsannotation @Timed avalidinterceptorbindingannotation
Programmaticallyadd @Timed asaninterceptorbinding
Usethemagic
Preparinginterceptorcreation
Tocreateaninterceptorweshouldstartbydetectingthe"technicalcode"thatwillwrapthe"businesscode"
classTimedMethodBean{
@InjectMetricRegistryregistry;
voidtimedMethod(){Timertimer=registry.timer("timer");Timer.Contexttime=timer.time();try{//Businesscode}finally{time.stop();}}}
Creatingtheinterceptor
Interceptorisanindependentspecification(JSR318).Highlightedcodebelowispartofit.
@InterceptorclassTimedInterceptor{
@InjectMetricRegistryregistry;@AroundInvokeObjecttimeMethod(InvocationContextcontext)throwsException{Timertimer=registry.timer(context.getMethod().getAnnotation(Timed.class).name());Timer.Contexttime=timer.time();try{
returncontext.proceed();}finally{time.stop();}}}
InCDIaninterceptorisabean,youcaninjectotherbeansinit
Herethe"business"oftheapplicationiscalled.Allthecodearoundisthetechnicalone.
1
2
1
2
Activatingtheinterceptor@Interceptor@Priority(Interceptor.Priority.LIBRARY_BEFORE)classTimedInterceptor{
@InjectMetricRegistryregistry;
@AroundInvokeObjecttimeMethod(InvocationContextcontext)throwsException{Timertimer=registry.timer(context.getMethod().getAnnotation(Timed.class).name());Timer.Contexttime=timer.time();try{returncontext.proceed();}finally{time.stop();}}}
Givinga @Priority toaninterceptoractivatesit.ThisannotationispartoftheCommonAnnotationsspecification(JSR250).InCDI,interceptoractivationcanalsobedoneinthe beans.xml file.
1
1
Addabindingtotheinterceptor@Timed@Interceptor@Priority(Interceptor.Priority.LIBRARY_BEFORE)classTimedInterceptor{
@InjectMetricRegistryregistry;
@AroundInvokeObjecttimeMethod(InvocationContextcontext)throwsException{Timertimer=registry.timer(context.getMethod().getAnnotation(Timed.class).name());Timer.Contexttime=timer.time();try{returncontext.proceed();}finally{time.stop();}}}
We’lluseMetrics @Timed annotationasinterceptorbinding
1
1
Backoninterceptorbinding
Aninterceptorbindingisanannotationusedin2kindofplaces:
1. Ontheinterceptordefinitionstoassociatethemtothisannotation2. Onthemethods/classestobeinterceptedbythisinterceptor
Aninterceptorbindingshouldbeannotatedwiththe@InterceptorBinding metaannotationorshouldbedeclaredasaninterceptorbindingprogrammatically
Iftheinterceptorbindingannotationhasmembers:
1. Theirvaluesaretakenintoaccounttodistinguishtwoinstances2. Unlessmembersareannotatedwith @NonBinding
@Timed sourcecodetellsusit’snotaninterceptorbinding
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.ANNOTATION_TYPE})
public@interfaceTimed{
Stringname()default"";
booleanabsolute()defaultfalse;}
Lackof @InterceptorBinding annotationandwehavenocodetoadditprogrammatically
Noneofthemembershavethe @NonBinding annotationsothey’llbeusedtodistinguishtwoinstances(i.e.@Timed(name="timer1") and @Timed(name="timer2") willbe2differentinterceptorbindings)
1
2
2
1
2
Theneeded @Timed sourcecodetomakeitaninterceptorbinding
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.ANNOTATION_TYPE})@InterceptorBindingpublic@interfaceTimed{
@NonBindingStringname()default"";
@NonBindingbooleanabsolute()defaultfalse;}
Howtoobtaintherequired@Timed ?
Wecannottouchthecomponentsource/binary!
Rememberthe AnnotatedType SPI?
ThankstoDeltaSpikewecaneasilycreatetherequired AnnotatedType
AnnotatedTypecreateTimedAnnotatedType()throwsException{AnnotationnonBinding=newAnnotationLiteral<Nonbinding>(){};returnnewAnnotatedTypeBuilder().readFromType(Timed.class).addToMethod(Timed.class.getMethod("name"),nonBinding).addToMethod(Timed.class.getMethod("absolute"),nonBinding).create();}
Thiscreatesaninstanceof @NonBinding annotation
Itwouldhavebeenpossiblebutfarmoreverbosetocreatethis AnnotatedType withoutthehelpofDeltaSpike.The AnnotatedTypeBuilder isinitializedfromtheMetrics @Timed annotation.
@NonBinding isaddedtobothmembersofthe @Timed annotation
123
3
1
2
3
Add @Timed tothelistofinterceptorbindingwithanextension
Byobserving BeforeBeanDiscovery lifecycleevent
publicinterfaceBeforeBeanDiscovery{
addQualifier(Class<?extendsAnnotation>qualifier);addQualifier(AnnotatedType<?extendsAnnotation>qualifier);addScope(Class<?extendsAnnotation>scopeType,booleannormal,booleanpassivation);addStereotype(Class<?extendsAnnotation>stereotype,Annotation...stereotypeDef);addInterceptorBinding(AnnotatedType<?extendsAnnotation>bindingType);addInterceptorBinding(Class<?extendsAnnotation>bindingType,Annotation...bindingTypeDef);addAnnotatedType(AnnotatedType<?>type);addAnnotatedType(AnnotatedType<?>type,Stringid);}
Thismethodistheoneweneedtoaddourmodified @Timed AnnotatedType
1
1
InternalStep HappenOnce LooponElements
BeforeBeanDiscovery isfirstinlifecycle
DeploymentStart
BeforeBean
Discovery
ScanArchive
ProcessAnnotated
Type
AfterType
Discovery
BeanEligibility
Check
ProcessInjectionPoint
ProcessInjectionTarget
ProcessBean
Attributes
ProcessBean
ProcessProducer
ProcessObserverMethod
AfterBean
Discovery
AfterDeploymentValidation
ApplicationRunning
BeforeShutdown
UndeployApplication
ThisextensionwilldothejobclassMetricsExtensionimplementsExtension{
voidaddTimedBinding(@ObservesBeforeBeanDiscoverybbd)throwsException{bbd.addInterceptorBinding(createTimedAnnotatedType());}
privateAnnotatedTypecreateTimedAnnotatedType()throwsException{AnnotationnonBinding=newAnnotationLiteral<Nonbinding>(){};returnnewAnnotatedTypeBuilder().readFromType(Timed.class).addToMethod(Timed.class.getMethod("name"),nonBinding).addToMethod(Timed.class.getMethod("absolute"),nonBinding).create();}}
Firstgoalachieved
Wecannowwrite:
@Timed("timer")voidtimedMethod(){//Businesscode}
AndhaveaMetricsTimer appliedtothemethod
Secondgoal:Automaticallyregistercustommetrics
Whywouldwewantcustommetrics?
@AroundInvokeObjecttimedMethod(InvocationContextcontext)throwsException{Stringname=context.getMethod().getAnnotation(Timed.class).name();Timertimer=registry.timer(name);Timer.Contexttime=timer.time();try{returncontext.proceed();}finally{time.stop();}}
Theregistryprovidesadefault Timer (ifnonewasregisteredbytheuser).Thedefaulttimerhistogramisexponentiallybiasedtothepast5minutesofmeasurements.Wemaywanttohaveanotherbehavior.
1
1
Automaticallyregistercustommetrics
Wewanttowritethis:
@Produces@Metric(name="myTimer")Timertimer=newTimer(newSlidingTimeWindowReservoir(1L,TimeUnit.MINUTES));
Andhave:
1. Thepossibilitytoretrievethis Timer fromtheregistrywhenit’sinjected(insteadofhavinganewinstancecreated)
2. This Timer producedwhenneeded(firstuse)3. This Timer registeredintheregistrywithitsname(here "myTimer" )
Thereare2 Metric :the com.codahale.metrics.Metric interfaceandthe com.codahale.metrics.annotation.Metric annotation
Howtoachievethis?
Weneedtowriteanextensionthatwill:
1. Declare @Metric asaqualifiertoeaseinjectionandnameresolutionina BeforeBeanDiscovery observer
2. Changehowa Metric instancewillbeproducedtolookitupintheregistryandproduce(andregister)itonlyifit’snotfound.We’lldothisby:1. observingthe ProcessProducer lifecycleevent2. decoratingMetric Producer toaddthisnewbehavior
3. Produceall Metric instancesattheendofboottimetohavetheminregistryforruntime1. we’lldothisbyobserving AfterDeploymentValidation event
InternalStep HappenOnce LooponElements
Sowewill @Observes these3eventstoaddourfeatures
DeploymentStart
BeforeBean
Discovery
ScanArchive
ProcessAnnotated
Type
AfterType
Discovery
BeanEligibility
Check
ProcessInjectionPoint
ProcessInjectionTarget
ProcessBean
Attributes
ProcessBean
ProcessProducer
ProcessObserverMethod
AfterBean
Discovery
AfterDeploymentValidation
ApplicationRunning
BeforeShutdown
UndeployApplication
Adding @Metric tothelistofqualifiers
Thistimeweneedannotationmemberstobe"binding"( @Metric("a") and @Metric("b") shouldbedistinguished)
Sowedon’thavetoadd @Nonbinding annotationtothem
publicclassMetricExtensionimplementsExtension{
voidaddMetricQualifier(@ObservesBeforeBeanDiscoverybbd){bbd.addQualifier(Metric.class);}//...}
Customizing Metric producingprocess
Wefirstneedtocreateourimplementationofthe Producer<X> SPI
classMetricProducer<XextendsMetric>implementsProducer<X>{
privatefinalProducer<X>delegate;
privatefinalBeanManagerbm;
privatefinalStringname;
MetricProducer(Producer<X>delegate,BeanManagerbm,Stringname){this.decorated=decorated;this.bm=bm;this.name=name;}//...
Customizing Metric producingprocess(continued)//...@OverridepublicXproduce(CreationalContext<X>ctx){MetricRegistryregistry=BeanProvider.getContextualReference(bm,MetricRegistry.class,false);if(!registry.getMetrics().containsKey(name))registry.register(name,delegate.produce(ctx));return(X)registry.getMetrics().get(name);}
@Overridepublicvoiddispose(Xinstance){}
@OverridepublicSet<InjectionPoint>getInjectionPoints(){returndecorated.getInjectionPoints();}}
BeanProvider isaDeltaSpikehelperclasstoeasilyretrieveabeanorbeaninstance
Wedon’twanttohavetheproduced Metric instancedestroyedbytheCDIcontainer
1
2
1
2
We’lluseour Producer<Metric> ina ProcessProducer observer
Throughthiseventwecansubstitutethestandardproducerbyours
publicinterfaceProcessProducer<T,X>{
AnnotatedMember<T>getAnnotatedMember();
Producer<X>getProducer();
voidsetProducer(Producer<X>producer);
voidaddDefinitionError(Throwablet);}
Getsthe AnnotatedMember associatedtothe @Produces fieldormethod
Getsthedefaultproducer(usefultodecorateit)
Overridestheproducer
1
2
3
1
2
3
Customizing Metric producingprocess(end)
Here’stheextensioncodetodothisproducerdecoration
publicclassMetricExtensionimplementsExtension{//...<Xextendscom.codahale.metrics.Metric>voiddecorateMetricProducer(@ObservesProcessProducer<?,X>pp,BeanManagerbm){Stringname=pp.getAnnotatedMember().getAnnotation(Metric.class).name();pp.setProducer(newMetricProducer<>(pp.getProducer(),bm,name));}//...}
Producingallthe Metric instancesattheendofboottime
Wedothatbyobservingthe AfterDeploymentValidation event
publicclassMetricExtensionimplementsExtension{//...voidregisterProduceMetrics(@ObservesAfterDeploymentValidationadv,BeanManagerbm){for(Bean<?>bean:bm.getBeans(com.codahale.metrics.Metric.class,newAnyLiteral()))for(Annotationqualifier:bean.getQualifiers())if(qualifierinstanceofMetric)BeanProvider.getContextualReference(bm,com.codahale.metrics.Metric.class,false,qualifier);}//...}
Getsallthe Metric beans
Retrievesaninstancethatwilluseourcustomproducerandthuswillfilltheregistry
1
2
1
2
Secondgoalachieved
Wecannowwrite:
@Produces@Metric(name="myTimer")Timertimer=newTimer(newSlidingTimeWindowReservoir(1L,TimeUnit.MINUTES));
@InjectMetricRegistryregistry;
@Inject@Metric("myTimer")Metrictimer;
Andbesurethat registry.getMetrics().get("myTimer") andtimer arethesameobject(ourcustom Timer )
CompleteextensioncodepublicclassMetricExtensionimplementsExtension{
voidaddMetricQualifier(@ObservesBeforeBeanDiscoverybbd){bbd.addQualifier(Metric.class);}
voidaddTimedInterceptorBinding(@ObservesBeforeBeanDiscoverybbd)throwsNoSuchMethodException{AnnotationnonBinding=newAnnotationLiteral<Nonbinding>(){};bbd.addInterceptorBinding(newAnnotatedTypeBuilder().readFromType(Timed.class).addToMethod(Timed.class.getMethod("name"),nonBinding).addToMethod(Timed.class.getMethod("absolute"),nonBinding).create());}
<Textendscom.codahale.metrics.Metric>voiddecorateMetricProducer(@ObservesProcessProducer<?,T>pp,BeanManagerbm){Stringname=pp.getAnnotatedMember().getAnnotation(Metric.class).name();pp.setProducer(newMetricProducer<>(pp.getProducer(),bm,name);}
voidregisterProduceMetrics(@ObservesAfterDeploymentValidationadv,BeanManagerbm){for(Bean<?>bean:bm.getBeans(com.codahale.metrics.Metric.class,newAnyLiteral()))for(Annotationqualifier:bean.getQualifiers())if(qualifierinstanceofMetric)BeanProvider.getContextualReference(bm,com.codahale.metrics.Metric.class,false,qualifier);}}
TestyourCDIknowledge
Quizztime
FindthevalidinjectionspointsclassMySuperBean{
@InjectBean<MySuperBean>myMeta;//A[]
@InjectBean<MyService>serviceMeta;//B[]
publicMySuperBean(@InjectMyServiceservice){/*...*/}//C[]
@InjectprivatevoidmyInitMethod(MyServiceservice){/*...*/}//D[]
@Inject@PostConstructpublicvoidmyInitMethod2(MyServiceservice){/*...*/}//E[]}
SolutionclassMySuperBean{
@InjectBean<MySuperBean>myMeta;//A[X]
@InjectBean<MyService>serviceMeta;//B[]
publicMySuperBean(@InjectMyServiceservice){/*...*/}//C[]
@InjectprivatevoidmyInitMethod(MyServiceservice){/*...*/}//D[X]
@Inject@PostConstructpublicvoidmyInitMethod2(MyServiceservice){/*...*/}//E[]}
FindBeanscandidateswithout beans.xml injar(CDI1.2)
@DecoratorpublicabstractclassMyDecoratorimplementsMyService{/*...*/}//A[]
@StatelesspublicclassMyServiceImplimplementsMyService{/*...*/}//B[]
publicclassMyBean{/*...*/}//C[]
@ModelpublicclassMyBean{/*...*/}//D[]
@SingletonpublicclassMyBean{/*...*/}//E[]
@ConversationScopedpublicclassMyBean{/*...*/}//F[]
Solution@DecoratorpublicabstractclassMyDecoratorimplementsMyService{/*...*/}//A[X]
@StatelesspublicclassMyServiceImplimplementsMyService{/*...*/}//B[X]
publicclassMyBean{/*...*/}//C[]
@ModelpublicclassMyBean{/*...*/}//D[X]
@SingletonpublicclassMyBean{/*...*/}//E[]
@ConversationScopedpublicclassMyBean{/*...*/}//F[X]
Findthevalidproducers@ApplicationScopedpublicclassMyBean{
@ProducespublicServiceproduce1(InjectionPointip,Bean<Service>myMeta){/*...*/}//A[]
@Produces@SessionScopedpublicServiceproduce2(InjectionPointip){/*...*/}//B[]
@ProducespublicMap<K,V>produceMap(InjectionPointip){/*...*/}//C[]
@ProducespublicMap<String,?extendsService>produceMap2(){/*...*/}//D[]}
Solution@ApplicationScopedpublicclassMyBean{
@ProducespublicServiceproduce1(InjectionPointip,Bean<Service>myMeta){/*...*/}//A[X]
@Produces@SessionScopedpublicServiceproduce2(InjectionPointip){/*...*/}//B[]
@ProducespublicMap<K,V>produceMap(InjectionPointip){/*...*/}//C[X]
@ProducespublicMap<String,?extendsService>produceMap2(){/*...*/}//D[]}
Whichobserverswillbetriggered?classFirstBean{
@InjectEvent<Post>postEvent;
publicvoidsaveNewPost(PostmyPost){postEvent.select(newAnnotationLiteral()<French>{}).fire(myPost);}}
classSecondBean{
voidlistenFrPost(@Observes@FrenchPostpost){/*...*/}//A[]voidlistenPost(@ObservesPostpost){/*...*/}//B[]voidlistenEnPost(@Observes@EnglishPostpost){/*...*/}//C[]voidlistenObject(@ObservesObjectobj){/*...*/}//D[]}
SolutionclassFirstBean{
@InjectEvent<Post>postEvent;
publicvoidsaveNewPost(PostmyPost){postEvent.select(newAnnotationLiteral()<French>{}).fire(myPost);}}
classSecondBean{
voidlistenFrPost(@Observes@FrenchPostpost){/*...*/}//A[X]voidlistenPost(@ObservesPostpost){/*...*/}//B[X]voidlistenEnPost(@Observes@EnglishPostpost){/*...*/}//C[]voidlistenObject(@ObservesObjectobj){/*...*/}//D[X]}
HowtouseCDIasdependencyinjectioncontainerforanintegrationframework(ApacheCamel)
CamelCDI
AboutApacheCamel
Open-sourceintegrationframeworkbasedonknownEnterpriseIntegrationPatterns
ProvidesavarietyofDSLstowriteroutingandmediationrules
ProvidessupportforbeanbindingandseamlessintegrationwithDIframeworks
DiscoverhowwecreatedCDIintegrationmoduleforCamel
Cameloutofthebox(withoutCDI)publicstaticvoidmain(String[]args){CamelContextcontext=newDefaultCamelContext();context.addRoutes(newRouteBuilder(){publicvoidconfigure(){from("file:target/input?delay=1000").convertBodyTo(String.class).log("Sendingmessage[${body}]toJMS...").to("sjms:queue:output");}});PropertiesComponentproperties=newPropertiesComponent();properties.setLocation("classpath:camel.properties");context.addComponent("properties",properties);//Registersthe"properties"component
SjmsComponentcomponent=newSjmsComponent();component.setConnectionFactory(newActiveMQConnectionFactory("vm://broker?broker.persistent=false"));jms.setConnectionCount(Integer.valueOf(context.resolvePropertyPlaceholders("{{jms.maxConnections}}")));context.addComponent("sjms",jms);//Registersthe"sjms"component
context.start();}
ThisroutewatchesadirectoryeverysecondandsendsnewfilescontenttoaJMSqueue
1
1
BasicCDIintegration(1/3)1. CamelcomponentsandroutebuilderasCDIbeans2. BindtheCamelcontextlifecycletothatoftheCDIcontainer
classFileToJmsRouteBeanextendsRouteBuilder{
@Overridepublicvoidconfigure(){from("file:target/input?delay=1000").convertBodyTo(String.class).log("Sendingmessage[${body}]toJMS...").to("sjms:queue:output");}}
BasicCDIintegration(2/3)classPropertiesComponentFactoryBean{
@Produces@ApplicationScopedPropertiesComponentpropertiesComponent(){PropertiesComponentproperties=newPropertiesComponent();properties.setLocation("classpath:camel.properties");returnproperties;}}
classJmsComponentFactoryBean{
@Produces@ApplicationScopedSjmsComponentsjmsComponent(PropertiesComponentproperties)throwsException{SjmsComponentjms=newSjmsComponent();jms.setConnectionFactory(newActiveMQConnectionFactory("vm://broker?broker.persistent=false"));jms.setConnectionCount(Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));returncomponent;}}
BasicCDIintegration(3/3)@ApplicationScopedclassCamelContextBeanextendsDefaultCamelContext{
@InjectCamelContextBean(FileToJmsRouteBeanroute,SjmsComponentjms,PropertiesComponentproperties){addComponent("properties",properties);addComponent("sjms",jms);addRoutes(route);}@PostConstructvoidstartContext(){super.start();}@PreDestroyvoidpreDestroy(){super.stop();}}
WecouldhavealotmorewithadvancedCDIfeatures
Ourgoals1. Avoidassemblingandconfiguringthe CamelContext manually2. AccessCDIbeansfromtheCamelDSLautomatically
.to("sjms:queue:output");//Lookupbyname(sjms)andtype(Component)
context.resolvePropertyPlaceholders("{{jms.maxConnections}}");//Lookupbyname(properties)andtype(Component)
3. SupportCamelannotationsinCDIbeans@PropertyInject(value="jms.maxConnections",defaultValue="10")intmaxConnections;
StepstointegrateCamelandCDI
Managethecreationandtheconfigurationofthe CamelContextbean
Bindthe CamelContext lifecyclethatoftheCDIcontainer
ImplementtheCamelSPItolookupCDIbeanreferences
Useacustom InjectionTarget forCDIbeanscontainingCamelannotations
Usethemagic
Howtoachievethis?
Weneedtowriteanextensionthatwill:
1. Declarea CamelContext beanbyobservingthe AfterBeanDiscoverylifecycleevent
2. Instantiatethebeansoftype RouteBuilder andaddthemtotheCamelcontext
3. Start(resp.stop)theCamelcontextwhentheAfterDeploymentValidation eventisfired(resp.the BeforeShutdownevent)
4. CustomizetheCamelcontexttoquerythe BeanManager tolookupCDIbeansbynameandtype
5. DetectCDIbeanscontainingCamelannotationsbyobservingtheProcessAnnotatedType eventandmodifyhowtheygetinjectedbyobservingthe ProcessInjectionTarget lifecycleevent
InternalStep HappenOnce LooponElements
Sowewill @Observes these5eventstoaddourfeatures
DeploymentStart
BeforeBean
Discovery
ScanArchive
ProcessAnnotated
Type
AfterType
Discovery
BeanEligibility
Check
ProcessInjectionPoint
ProcessInjectionTarget
ProcessBean
Attributes
ProcessBean
ProcessProducer
ProcessObserverMethod
AfterBean
Discovery
AfterDeploymentValidation
ApplicationRunning
BeforeShutdown
UndeployApplication
Addingthe CamelContext bean
Automaticallyadda CamelContext beaninthedeploymentarchive
Howtoaddabeanprogrammatically?
Declaringabeanprogrammatically
Weneedtoimplementthe Bean SPI
publicinterfaceBean<T>extendsContextual<T>,BeanAttributes<T>{
Class<?>getBeanClass();Set<InjectionPoint>getInjectionPoints();Tcreate(CreationalContext<T>creationalContext);//Contextual<T>voiddestroy(Tinstance,CreationalContext<T>creationalContext);Set<Type>getTypes();//BeanAttributes<T>Set<Annotation>getQualifiers();Class<?extendsAnnotation>getScope();StringgetName();Set<Class<?extendsAnnotation>>getStereotypes();booleanisAlternative();}
Implementingthe Bean SPIclassCamelContextBeanimplementsBean<CamelContext>{
publicClass<?extendsAnnotation>getScope(){returnApplicationScoped.class;}
publicSet<Annotation>getQualifiers(){returnCollections.singleton((Annotation)newAnnotationLiteral<Default>(){});}publicSet<Type>getTypes(){returnCollections.singleton((Type)CamelContext.class);}
publicCamelContextcreate(CreationalContext<CamelContext>creational){returnnewDefaultCamelContext();}publicvoiddestroy(CamelContextinstance,CreationalContext<CamelContext>creational){}
publicClass<?>getBeanClass(){returnDefaultCamelContext.class;}
publicSet<InjectionPoint>getInjectionPoints(){returnCollections.emptySet();}
publicSet<Class<?extendsAnnotation>>getStereotypes(){returnCollections.emptySet();}publicStringgetName(){return"camel-cdi";}publicbooleanisAlternative(){returnfalse;}publicbooleanisNullable(){returnfalse;}}
Addingaprogrammaticbeantothedeployment
Thenaddthe CamelContextBean beanprogrammaticallybyobservingthe AfterBeanDiscovery lifecyleevent
publicclassCamelExtensionimplementsExtension{
voidaddCamelContextBean(@ObservesAfterBeanDiscoveryabd){abd.addBean(newCamelContextBean());}}
InstantiateandassembletheCamelcontext
Instantiatethe CamelContext beanandthe RouteBuilder beansintheAfterDeploymentValidation lifecycleevent
publicclassCamelExtensionimplementsExtension{//...voidconfigureContext(@ObservesAfterDeploymentValidationadv,BeanManagerbm){CamelContextcontext=getReference(bm,CamelContext.class);for(Bean<?>bean:bm.getBeans(RoutesBuilder.class))context.addRoutes(getReference(bm,RouteBuilder.class,bean));}<T>TgetReference(BeanManagerbm,Class<T>type){returngetReference(bm,type,bm.resolve(bm.getBeans(type)));}<T>TgetReference(BeanManagerbm,Class<T>type,Bean<?>bean){return(T)bm.getReference(bean,type,bm.createCreationalContext(bean));}}
ManagedtheCamelcontextlifecycle
Start(resp.stop)theCamelcontextwhentheAfterDeploymentValidation eventisfired(resp.the BeforeShutdown )
publicclassCamelExtensionimplementsExtension{//...voidconfigureContext(@ObservesAfterDeploymentValidationadv,BeanManagerbm){CamelContextcontext=getReference(bm,CamelContext.class);for(Bean<?>bean:bm.getBeans(RoutesBuilder.class)context.addRoutes(getReference(bm,RouteBuilder.class,bean);context.start();}voidstopCamelContext(@ObservesBeforeShutdownbs,BeanManagerbm){CamelContextcontext=getReference(bm,CamelContext.class);context.stop();}}
Firstgoalachieved
Wecangetridofthefollowingcode:
@ApplicationScopedclassCamelContextBeanextendsDefaultCamelContext{@InjectCamelContextBean(FileToJmsRouteBeanroute,SjmsComponentjms,PropertiesComponentproperties){addComponent("properties",propertiesComponent);addComponent("sjms",sjmsComponent);addRoutes(route);}@PostConstructvoidstartContext(){super.start();}@PreDestroyvoidstopContext(){super.stop();}}
Secondgoal:AccessCDIbeansfromtheCamelDSL
HowtoretrieveCDIbeansfromtheCamelDSL?
.to("sjms:queue:output");//Lookupbyname(sjms)andtype(Component)context.resolvePropertyPlaceholders("{{jms.maxConnections}}");//Lookupbyname(properties)andtype(Component)
//Andalso....bean(MyBean.class);//LookupbytypeandDefaultqualifier.beanRef("beanName");//Lookupbyname
ImplementtheCamelregistrySPIandusethe BeanManager tolookupforCDIbeancontextualreferencesbynameandtype
ImplementtheCamelregistrySPIclassCamelCdiRegistryimplementsRegistry{privatefinalBeanManagerbm;
CamelCdiRegistry(BeanManagerbm){this.bm=bm;}
public<T>TlookupByNameAndType(Stringname,Class<T>type){returngetReference(bm,type,bm.resolve(bm.getBeans(name)));}public<T>Set<T>findByType(Class<T>type){returngetReference(bm,type,bm.resolve(bm.getBeans(type)));}publicObjectlookupByName(Stringname){returnlookupByNameAndType(name,Object.class);}<T>TgetReference(BeanManagerbm,Typetype,Bean<?>bean){return(T)bm.getReference(bean,type,bm.createCreationalContext(bean));}}
Addthe CamelCdiRegistry totheCamelcontext
classCamelContextBeanimplementsBean<CamelContext>{privatefinalBeanManagerbm;
CamelContextBean(BeanManagerbm){this.bm=bm;}//...publicCamelContextcreate(CreationalContext<CamelContext>creational){returnnewDefaultCamelContext(newCamelCdiRegistry(bm));}}
publicclassCamelExtensionimplementsExtension{//...voidaddCamelContextBean(@ObservesAfterBeanDiscoveryabd,BeanManagerbm){abd.addBean(newCamelContextBean(bm));}}
Secondgoalachieved1/3
Wecandeclarethe sjms componentwiththe @Named qualifier
classJmsComponentFactoryBean{
@Produces@Named("sjms")@ApplicationScopedSjmsComponentsjmsComponent(PropertiesComponentproperties){SjmsComponentjms=newSjmsComponent();jms.setConnectionFactory(newActiveMQConnectionFactory("vm://broker?..."));jms.setConnectionCount(Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));returncomponent;}}
…
Secondgoalachieved2/3
Declarethe properties componentwiththe @Named qualifier
classPropertiesComponentFactoryBean{
@Produces@Named("properties")@ApplicationScopedPropertiesComponentpropertiesComponent(){PropertiesComponentproperties=newPropertiesComponent();properties.setLocation("classpath:camel.properties");returnproperties;}}
…
Secondgoalachieved3/3
Andgetridofthecoderelatedtothe properties and sjmscomponentsregistration
@ApplicationScopedclassCamelContextBeanextendsDefaultCamelContext{@InjectCamelContextBean(FileToJmsRouteBeanroute,SjmsComponentjms,PropertiesComponentproperties){addComponent("properties",propertiesComponent);addComponent("sjms",sjmsComponent);addRoutes(route);}@PostConstructvoidstartContext(){super.start();}@PreDestroyvoidstopContext(){super.stop();}}
Thirdgoal:SupportCamelannotationsinCDIbeans
CamelprovidesasetofDIframeworkagnosticannotationsforresourceinjection
@PropertyInject(value="jms.maxConnections",defaultValue="10")intmaxConnections;
//Butalso...@EndpointInject(uri="jms:queue:foo")Endpointendpoint;
@BeanInject("foo")FooBeanfoo;
Howtosupportcustomannotationsinjection?
Howtosupportcustomannotationsinjection?
Createacustom InjectionTarget thatusesthedefaultCamelbeanpostprocessor DefaultCamelBeanPostProcessor
publicinterfaceInjectionTarget<T>extendsProducer<T>{voidinject(Tinstance,CreationalContext<T>ctx);voidpostConstruct(Tinstance);voidpreDestroy(Tinstance);}
HookitintotheCDIinjectionmechanismbyobservingtheProcessInjectionTarget lifecycleevent
OnlyforbeanscontainingCamelannotationsbyobservingtheProcessAnnotatedType lifecycleandusing @WithAnnotations
Createacustom InjectionTargetclassCamelInjectionTarget<T>implementsInjectionTarget<T>{
finalInjectionTarget<T>delegate;
finalDefaultCamelBeanPostProcessorprocessor;
CamelInjectionTarget(InjectionTarget<T>target,finalBeanManagerbm){delegate=target;processor=newDefaultCamelBeanPostProcessor(){publicCamelContextgetOrLookupCamelContext(){returngetReference(bm,CamelContext.class);}};}publicvoidinject(Tinstance,CreationalContext<T>ctx){processor.postProcessBeforeInitialization(instance,null);delegate.inject(instance,ctx);}//...}
CalltheCameldefaultbeanpost-processorbeforeCDIinjection
1
1
Registerthecustom InjectionTarget
Observethe ProcessInjectionTarget lifecyleeventandsettheInjectionTarget
publicinterfaceProcessInjectionTarget<X>{AnnotatedType<X>getAnnotatedType();InjectionTarget<X>getInjectionTarget();voidsetInjectionTarget(InjectionTarget<X>injectionTarget);voidaddDefinitionError(Throwablet);}
Todecorateitwiththe CamelInjectionTarget
classCamelExtensionimplementsExtension{
<T>voidcamelBeansPostProcessor(@ObservesProcessInjectionTarget<T>pit,BeanManagerbm){pit.setInjectionTarget(newCamelInjectionTarget<>(pit.getInjectionTarget(),bm));}}
ButonlyforbeanscontainingCamelannotationsclassCamelExtensionimplementsExtension{finalSet<AnnotatedType<?>>camelBeans=newHashSet<>();
voidcamelAnnotatedTypes(@Observes@WithAnnotations(PropertyInject.class)ProcessAnnotatedType<?>pat){camelBeans.add(pat.getAnnotatedType());}
<T>voidcamelBeansPostProcessor(@ObservesProcessInjectionTarget<T>pit,BeanManagerbm){if(camelBeans.contains(pit.getAnnotatedType()))pit.setInjectionTarget(newCamelInjectionTarget<>(pit.getInjectionTarget(),bm));}}
DetectallthetypescontainingCamelannotationswith @WithAnnotations
Decoratethe InjectionTarget correspondingtothesetypes
1
2
1
2
Thirdgoalachieved1/2
Insteadofinjectingthe PropertiesComponent beantoresolveaconfigurationproperty
classJmsComponentFactoryBean{
@Produces@Named("sjms")@ApplicationScopedSjmsComponentsjmsComponent(PropertiesComponentproperties){SjmsComponentjms=newSjmsComponent();jms.setConnectionFactory(newActiveMQConnectionFactory("vm://broker?..."));jms.setConnectionCount(Integer.valueOf(properties.parseUri("{{jms.maxConnections}}")));returncomponent;}}
Thirdgoalachieved2/2
Wecandirectlyrelyonthe @PropertyInject CamelannotationinCDIbeans
classJmsComponentFactoryBean{
@PropertyInject(value="jms.maxConnections",defaultValue="10")intmaxConnections;
@Produces@Named("sjms")@ApplicationScopedSjmsComponentsjmsComponent(){SjmsComponentcomponent=newSjmsComponent();jms.setConnectionFactory(newActiveMQConnectionFactory("vm://broker?..."));component.setConnectionCount(maxConnections);returncomponent;}}
Bonusgoal:CamelDSLAOP
AOPinstrumentationoftheCamelDSL
from("file:target/input?delay=1000").convertBodyTo(String.class).log("Sendingmessage[${body}]toJMS...").to("sjms:queue:output");
withCDIobservers
from("file:target/input?delay=1000").convertBodyTo(String.class).to("sjms:queue:output").id("joinpoint");}voidadvice(@Observes@NodeId("joinpoint")Exchangeexchange){logger.info("Sendingmessage[{}]toJMS...",exchange.getIn().getBody());}
Howtoachievethis?
WecancreateaCDIqualifiertoholdtheCamelnodeidmetadata:
@Qualifier@Retention(RetentionPolicy.RUNTIME)public@interfaceNodeId{Stringvalue();}
andcreateanextensionthatwill:
1. DetecttheCDIbeanscontainingobservermethodswiththe @NodeIdqualifierbyobservingthe ProcessObserverMethod eventandcollecttheCamelprocessornodestobeinstrumented
2. CustomizetheCamelcontextbyprovidinganimplementationoftheCamel InterceptStrategy interfacethatwillfireaCDIeventeachtimean Exchange isprocessedbytheinstrumentednodes
DetecttheCamelDSLAOPobservermethods
Observethe ProcessObserverMethod lifecyleevent
publicinterfaceProcessObserverMethod<T,X>{AnnotatedMethod<X>getAnnotatedMethod();ObserverMethod<T>getObserverMethod();voidaddDefinitionError(Throwablet);}
Andcollecttheobservermethodmetadata
classCamelExtensionimplementsExtension{finalSet<NodeId>joinPoints=newHashSet<>();
voidpointcuts(@ObservesProcessObserverMethod<Exchange,?>pom){for(Annotationqualifier:pom.getObserverMethod().getObservedQualifiers())if(qualifierinstanceofNodeId)joinPoints.add(NodeId.class.cast(qualifier));}}
InstrumenttheCamelcontext
InterceptmatchingnodesandfireaCDIevent
voidconfigureCamelContext(@ObservesAfterDeploymentValidationadv,finalBeanManagermanager){context.addInterceptStrategy(newInterceptStrategy(){publicProcessorwrapProcessorInInterceptors(CamelContextcontext,ProcessorDefinition<?>definition,Processortarget,ProcessornextTarget)throwsException{if(definition.hasCustomIdAssigned()){for(finalNodenode:joinPoints){if(node.value().equals(definition.getId())){returnnewDelegateAsyncProcessor(target){publicbooleanprocess(Exchangeexchange,AsyncCallbackcallback){manager.fireEvent(exchange,node);returnsuper.process(exchange,callback);}};}}}returntarget;}});}
Bonusgoalachieved
WecandefinejoinpointsintheCamelDSL
from("file:target/input?delay=1000").convertBodyTo(String.class).to("sjms:queue:output").id("joinpoint");}
AndadvisethemwithCDIobservers
voidadvice(@Observes@NodeId("joinpoint")Exchangeexchange){List<MessageHistory>history=exchange.getProperty(Exchange.MESSAGE_HISTORY,List.class);logger.info("Sendingmessage[{}]to[{}]...",exchange.getIn().getBody(),history.get(history.size()-1).getNode().getLabel());}
Conclusion
References
CDISpecification-cdi-spec.org
MetricsCDIsources-github.com/astefanutti/metrics-cdi
CamelCDIsources-github.com/astefanutti/camel-cdi
Slidessources-github.com/astefanutti/further-cdi
SlidesgeneratedwithAsciidoctor,PlantUMLandDZSlidesbackend
Originalslidetemplate-DanAllen&SarahWhite
AntoineSabot-DurandAntoninStefanutti@antoine_sd@astefanut
Annexes
CompletelifecycleeventsApplication lifecycle
BeforeBeanDiscovery
AfterTypeDiscovery
AfterBeanDiscovery
AfterDeploymentValidation
Type Discovery
ProcessAnnotatedType<X>
yes
whiletypesindeploymentarchive?
no
Bean Discovery
For each discovered types during type discovery
ProcessInjectionPoint<T,X>
ProcessInjectionTarget<X>
ProcessBeanAttributes<T>
ProcessManagedBean<X>
For each producer methods / fields of enabled beans
ProcessInjectionPoint<T,X>
ProcessProducer<T,X>
ProcessBeanAttributes<T>
ProcessProducerMethod<T,X>ProcessProducerField<T,X>
For each observer methods of enabled beans
ProcessInjectionPoint<T,X>
ProcessObserverMethod<T,X>