Skip to content
avrecko edited this page Sep 14, 2010 · 40 revisions

What is this

This is a prototype, or proof of principle if you will, of programmatic configuration of CDI.

If somebody wants to make something similar – this is a good starting point. It includes pretty much all of the know-how.

In a nutshell the extensions instantiates Guice Modules to read the programmatic configuration and then it rewrites the annotated type information to match.

What works

Currently Guice API for linked binding works e.g.

bind(Mailer.class).to(AsynchronousMailer.class).in(Singleton.class);

and AOP support e.g.

bindInterceptor(any(), annotatedWith(Transactional.class), new TxnInterceptor());

The rest is left as en excersize for the reader to implement. ;)

One Caveat

Due to the way CDI works things like

bind(Mailer.class).annotatedWith(Blue.class).to(AsynchronousMailer.class).in(Singleton.class);
bind(Mailer.class).annotatedWith(Red.class).to(AsynchronousMailer.class).in(SessionScoped.class);

currently doesn’t work as AsynchronousMailer gets both @Singleton and @SessionScoped.

How to use it

Best if you check out unit test(s).

  1. Run ant jar (this will create weld-guiceconfig.jar)
  2. Put weld-guiceconfig.jar in the class path (all internal dependencies (guava, weld-extensions) are baked in using jarjar. Guice, weld, aopalliance and slf4j are not baked-in and have to be put on the classpath seperately.)
  3. Create META-INF/guiceconfig/Modules.properties in your project and reference any Guice modules using fully qualified names

Will there be any more updates

Most probably not. This was principally a fun project to play with Weld a bit. Need to spend time on preparing for exams and do some work.

Implementation details

Reading the programmatic configuration

Using beforeBeanDiscovery(Observes BeforeBeanDiscovery event, BeanManager beanManager) we do the work of reading the programmatic configuration.

Using getClass().getClassLoader().getResources(PACKAGES_FILE); we read text file that includes the fully qualified names of Guice modules.

I reused Guice API later on because I did not want to create a 1-to-1 mapping between my own custom programmatic API. But at first I started with custom API. Guice is only used for providing the configuration API. It is possible to integrate things more intimately, like using Guice Injector but didn’t explore this path.

Check out weld.guiceconfig.attic package for an example of custom fluent style API. Might give you some ideas for implementation of your own.

The modules are instantiated and feed to CdiBindingOracle which prepares all the configuration for easy access by the Phases. The term “phase” is explained later on.

Modifying the Annotated Types

At processAnotated(Observes ProcessAnnotatedType<T> event, BeanManager manager)

We feed AnnotatedTypeBuilder based on the event.getAnnotatedType to each Phase. There are 3 phases

RedefineDefaultInjectionPointsPhase

This phase replaces @Default qualifier with @HardDefault for all injection points for which there is a no qualified default binding. E.g.

bind(Foo.class).to(FooImpl.class); causes

@Inject Foo foo; is redefined with @Inject @HardDefault Foo foo;

Classes are not modified, just the way Weld sees them using AnnotatedType.

We need to do this so there is no chance of ambiguity.

ApplyLinkedBindingsAdvicePhase

This phase puts correct annotations on the implementation. E.g.

bind(Foo.class).annotatedWith(Red.class).to(FooImpl.class).in(Singleton.class);

causes Weld to see

@Singleton
@Red
public void FooImpl{}

ApplyInterceptorAdvicePhase

This puts @GuiceIntercepted on all methods that pass both Class and Method matcher and creates a entry in the Multimap<Method, MethodInterceptors>.

bindInterceptor(any(),annotatedWith(Transactional.class), new MethodInterceptor...);

will cause

@Transactional
public void someMethod(){ ... } to be seen by Weld/CDI as

@GuiceIntercepted @Transactional
public void someMethod(){ ... }

the GuiceConfigInterceptor will be used to intercept all methods annotated with GuiceConfigIntercepted. The interceptor will invoke the correct set of interceptors for the Method being intercepted.

After all the phases are complete the call event.setAnnotatedType(builder.create()); is called.

Just thought it is a nice way to structure all the processing code. Might need a refacture.

Reporting Errors

Left as excersize for the reader.