Java EE - Add CDI Interceptor programmatically

08 April 2014
By Gonçalo Marques
In this article we will see how to programmatically add a CDI interceptor to a CDI managed bean.

Introduction

Java EE interceptors may be used along with CDI managed beans. This means that one may use an interceptor in order to intercept calls to CDI managed beans methods and do some pre or post processing inside the interceptor (ex: audit method calls, check for authorization, etc.).

CDI extensions provide a powerful infrastructure that allows an application to capture container initialization lifecycle events and act accordingly to those events (ex: alter managed beans metadata, change managed beans property values, intercept injection of managed beans into other managed beans, among other operations).

In this article we will see how to programmatically add an interceptor to a CDI managed bean by the means of a CDI extension.

This tutorial considers the following environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.21
  3. Glassfish 4.0

The interceptor

In this article we will use an illustrative Audit interceptor which definition follows next (we will not cover the Java EE interceptor infrastructure in this article since it is not its main focus):

Audit.java

package com.byteslounge.audit;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ METHOD, TYPE })
public @interface Audit {

}

First we defined the Audit interceptor annotation. Now we define the interceptor itself:

AuditInterceptor.java

package com.byteslounge.audit;

import java.io.Serializable;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@Audit
@Interceptor
public class AuditInterceptor implements Serializable {

  private static final long serialVersionUID = 1L;

  @AroundInvoke
  public Object auditMethodCall(InvocationContext invocationContext)
      throws Exception {
    System.out.println("Intercepting call to method: "
        + invocationContext.getMethod().getName());
    return invocationContext.proceed();
  }

}

Now one may use the interceptor by annotating a managed bean with @Audit:


@Audit
public class TestBean {

}

Every method call made to TestBean will now be intercepted by our AuditInterceptor.

What if we need to wire the interceptor programmatically, i.e. the TestBean class is not annotated with @Audit? We will see how to do the interceptor wiring programmatically in the next section.

Wiring the CDI interceptor programmatically

As we have said in the introduction, a CDI extension may be used, among other things, to intercept container lifecycle initialization events and change managed beans metadata.

Every CDI managed bean that is processed by the container during container initialization will trigger a ProcessAnnotatedType event. We will implement a CDI extension that will listen for this event and check if the type being processed is TestBean. If this is the case, we will add the @Audit annotation to the managed bean metadata.

CDI extension that programmatically wires the interceptor

package com.byteslounge.extension;

import java.lang.annotation.Annotation;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;

import com.byteslounge.audit.Audit;
import com.byteslounge.beans.TestBean;

public class AuditExtension implements Extension {

  public <T> void processAnnotatedType(
      @Observes ProcessAnnotatedType<T> processAnnotatedType) {

    AnnotatedType<T> annotatedType = processAnnotatedType
        .getAnnotatedType();

    if (annotatedType.getJavaClass().equals(TestBean.class)) {

      Annotation auditAnnotation = new Annotation() {
        @Override
        public Class<? extends Annotation> annotationType() {
          return Audit.class;
        }
      };

      AnnotatedTypeWrapper<T> wrapper = new AnnotatedTypeWrapper<T>(
          annotatedType, annotatedType.getAnnotations());
      wrapper.addAnnotation(auditAnnotation);

      processAnnotatedType.setAnnotatedType(wrapper);
    }

  }

}

The first thing we need in our extension is to implement the javax.enterprise.inject.spi.Extension interface.

As we have said before, we are listening to the ProcessAnnotatedType event so we will intercept the initialization - or processing - of every CDI bean that is managed by the container.

We check the type of the managed bean being processed. If the bean type is TestBean we add the @Audit annotation to the bean annotation set.

Note that we are using a wrapper. This is because a CDI managed bean annotation set is implemented by an unmodifiable set. In order to solve this we create a wrapper that will delegate all calls to the wrapped object except the ones that are related with the annotation set. In this later case the wrapper will use its own annotation set that contains the original set plus the annotations added in the extension.

Finally we override the annotated type being processed with the wrapper: processAnnotatedType.setAnnotatedType(wrapper).

The wrapper definition follows next:

AnnotatedTypeWrapper.java

package com.byteslounge.extension;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;

import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;

public class AnnotatedTypeWrapper<T> implements AnnotatedType<T> {

  private final AnnotatedType<T> wrapped;
  private final Set<Annotation> annotations;

  public AnnotatedTypeWrapper(AnnotatedType<T> wrapped,
      Set<Annotation> annotations) {
    this.wrapped = wrapped;
    this.annotations = new HashSet<>(annotations);
  }

  public void addAnnotation(Annotation annotation) {
    annotations.add(annotation);
  }

  @Override
  public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
    return wrapped.getAnnotation(annotationType);
  }

  @Override
  public Set<Annotation> getAnnotations() {
    return annotations;
  }

  @Override
  public Type getBaseType() {
    return wrapped.getBaseType();
  }

  @Override
  public Set<Type> getTypeClosure() {
    return wrapped.getTypeClosure();
  }

  @Override
  public boolean isAnnotationPresent(
      Class<? extends Annotation> annotationType) {
    for (Annotation annotation : annotations) {
      if (annotationType.isInstance(annotation)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public Set<AnnotatedConstructor<T>> getConstructors() {
    return wrapped.getConstructors();
  }

  @Override
  public Set<AnnotatedField<? super T>> getFields() {
    return wrapped.getFields();
  }

  @Override
  public Class<T> getJavaClass() {
    return wrapped.getJavaClass();
  }

  @Override
  public Set<AnnotatedMethod<? super T>> getMethods() {
    return wrapped.getMethods();
  }

}

As we have said before, we defined a wrapper that will be set as the annotated type being processed by the container. The wrapper will delegate all calls to the wrapped object except the ones that are related with the annotation set, which will be handled by the wrapper itself (the wrapper annotation set contains the original annotation set plus the @Audit annotation that was added by the CDI extension).

CDI extension configuration

The last step is the CDI extension configuration.

In order to properly register a CDI extension one must create a file named javax.enterprise.inject.spi.Extension and place it inside /META-INF/services folder.

The file content must have the fully qualified name of the extension:

/META-INF/services/javax.enterprise.inject.spi.Extension

com.byteslounge.extension.AuditExtension

Article source code

The source code covering the article content is available for download at the end of this page. We used a JSF view in order to access a getter of a TestBean CDI managed bean and checked that the interceptor is called as expected, so the programmatic interceptor wiring was correctly done.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: http://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: