Until CDI 2.0, it was not trivial to implement an extension based on proxies and keep interceptors support. Almost each time it meant to reimplement the interceptor chain handling or use a library like Apache DeltaSpike and partial bean binding feature. Since CDI 2.0, this has been fixed, even if it requires some tip to make it simple.

What is an interface based API in CDI land?

Before seeing how to do it, here is a quick example of the API and the kind of target this post is about:

@AutoHandled
public interface MyService {
    @HandledWith(....)
    SomeResult doProcess(...);
}

The @AutoHandled is just a marker to detect the interfaces to "auto implement" by a CDI extension. This means you would be able to use it as simple as:

@Inject
private MyService service;

public void someMethod() {
    service.doProcess();
}

In other words: the implementation is transparent for the consumers.

The @HandledWith is an optional configuration for the "auto implementation" which allows to specify more precisely that with only the return type, method name and parameters the behavior to bind to the methods.

So what did change in CDI 2.0? In short: the InterceptionFactory API was released. However this is not as straight forward as expected. Why? Because this API does not accept a java lang proxy as input - for no real technical reason but this is specified, so respected by Weld and OpenWebBeans.

The naive implementation and its pitfall

I'll not detail the whole implementation here but until CDI 2.0 the common implementation was to capture the interfaces to implement and then, for each one of them, add a custom bean in the CDI context which was creating an instance using a java lang proxy:

public MyExtension implements Extension {
    private final Collection<Class<?>> toImplement = new ArrayList<>();

    void findTypes(@Observes @WithAnnotations(AutoHandled.class) final ProcessAnnotatedType<?> pat) {
        toImplement.add(pat.getAnnotatedType().getJavaClass());
    }

    void addImpls(@Observes final AfterBeanDiscovery abd, final BeanManager beanManager) {
        toImplement.forEach(type -> {
            abd.addBean()
               .id("MyHandler#" + type.getName())
               .scope(ApplicationScoped.class)
               .types(type, Object.class)
               .beanClass(type)
               .qualifiers(Default.Literal.INSTANCE, Any.Literal.INSTANCE)
               .createWith(ctx -> {
                   return Proxy.newProxyInstance(
                       Thread.currentThread().getContextClassLoader()
                       new Class<?>[]{type},
                       new MyHandler());
               });
        });
    }
}

This simple implementation does exactly what we described, catch all types to implement and proxy a proxy for each of them. Indeed we don't detail MyHandler logic which is an InvocationHandler which does what the auto implementation is supposed to do.

The issue of that implementation is that if you add interceptors on the interface they are just ignored.

InterceptionFactory to the rescue

The InterceptionFactory sounds like the best option to solve it. You can grab an instance from the BeanManager:

InterceptionFactory<T> factory = beanManager.createInterceptionFactory(
  creationalContext, typeToIntercept);

Then, once you got an instance of the factory, you can configure it with the interceptors you want and create the proxied instance delegating to the actual instance - our proxy here:

// 1
factory.configure()
  .add(new AnnotationLiteral<MyInterceptor>() {});
// 2
factory.ignoreFinalMethods();
// 3
return factory.createInterceptedInstance(proxiedInstance);
  1. We configure the factory to add explicitly some interceptor (indeed you can add multiple, add some on some methods only etc...). This is not required as, by default it, will use the AnnotatedType associated to the Class you passed to create the instance so if this one already has the interceptors we are good.
  2. We ignore final methods - if any - in the interception (this is not compatible and it doesn't hurt to call the method in general). Note however, this is optional for interface based implementation.
  3. Finally, we create an intercepted instance based on an instance.

This looks awesome but will fail with a proxy in the last step because the proxy can't be subclassed - it does not have a default constructor - and CDI does not handle this kind of instance and does not respect to proxy other API than the instance passed as last parameter (but it could technically). However this is what we will apply to solve it.

CDI interface based API with interceptor: the solution

At the moment we can create an instance of our bean, we can add interceptor on an instance but we can't put them together. Is there no solution at all? Indeed there is one - otherwise this post wouldn't be that useful.

The idea is to modify a little how we implement our bean. To do that you need to think about what is a java lang proxy? Technically it is just a way to intercept method calls, right? This is also on what JavaEE was based until CDI.

All the trick is there: intercept. You can implement your bean with a plain CDI interceptor instead of an InvocationHandler. The question is then: on which instance? If the interceptor replaces an invocation handler it does not need any delegate instance so null sounds like a good option.

With the InterceptionFactory we can bind our internal interceptor automatically and therefore have a bean. The only issue is that this factory does not accept null as instance as well. The workaround is simple: let CDI generate an instance for us. If you "normal-scope" your null instance (making it @ApplicationScoped) for instance, CDI will have to lazy generate the instance of the bean by design and therefore when you will lookup this instance it will proxy you a proxy matching the API you want. In other words: you just got an instance which will be compatible with the InterceptionFactory.

The last trick is to add two beans instead of one: the first one will be our fake instance, using null as actual delegate - but which will never be called, and the second is the actual injectable instance which will have all the interceptors, including the "implementation" one.

To distiguish both, the easiest way is to add to the first one the @Internal qualifier. Then, this is just a matter of putting it all together: the first instance has the @Internal qualifier, the second one looks up the first instance and creates it using the InterceptionFactory:

public MyExtension implements Extension {
    private final Collection<Class<?>> toImplement = new HashSet<>();

    void findTypes(@Observes @WithAnnotations(AutoHandled.class) final ProcessAnnotatedType<?> pat) {
        toImplement.add(pat.getAnnotatedType().getJavaClass());
    }

    void addImpls(@Observes final AfterBeanDiscovery abd, final BeanManager beanManager) {
        toImplement.forEach(type -> {
            // the fake instance which is just there to ask CDI to give us
            // an instance with the right "shape"
            abd.addBean()
               .id("autohandled#" + type.getName())
               .scope(ApplicationScoped.class)
               .types(type, Object.class)
               .beanClass(type)
               .qualifiers(Internal.LITERAL)
               .createWith(ctx -> null); // faked, we will never actually use that

            // the actual instance with interceptors
            abd.addBean()
               .id("autohandled#" + type.getName())
               .scope(ApplicationScoped.class)
               .types(type, Object.class)
               .beanClass(type)
               .qualifiers(Default.Literal.INSTANCE, Any.Literal.INSTANCE)
               .createWith(ctx -> {
                   // lookup fake instance
                   final Object delegate = beanManager.getReference(
                           beanManager.resolve(beanManager.getBeans(type, Internal.LITERAL)),
                           type,
                           beanManager.createCreationalContext(null));

                   // create the factory instance
                   final InterceptionFactory factory = beanManager.createInterceptionFactory(ctx, Class.class.cast(delegate.getClass()));
                   // bind our implementation
                   factory.configure().add(new AnnotationLiteral<AutoImplementationInterceptor>() {});
                   // create the intercepted instance
                   return factory.ignoreFinalMethods().createInterceptedInstance(delegate);
               });
        });
    }

    @Target(TYPE)
    @Retention(RUNTIME)
    public @interface Internal {
        Annotation LITERAL = new AnnotationLiteral<Internal>() {};
    }
}

Limitations and warnings

At that point we solved the original issue and didn't have to resolve ourself the interceptors nor to implement the interceptor chain. However this implementation only works with CDI 2.0 implementation and the 2.0.10 version of OpenWebBeans. So if you need to support it for older versions, you will still need to either ignore the interceptors or implement it the old way.

Nonetheless, CDI InterceptionFactory is a very good move of the specification to something simpler and powerful which just needs a few adjustment to be even more friendly.

 

From the same author:

In the same category: