22 April 2014

Working with Java EE Interceptors and Repeatable Annotations

Since Java EE 6 you can invoke Interceptors around method calls. With Interceptors you will add Aspect-oriented Programming (AoP) patterns to your Java EE project. Given you have a method bound to an Interceptor a method call will look like this:

Overview and Behavior of Interceptors

To actually implement this behavior there are some simple steps to do. First implement an Interceptor:

@javax.interceptor.Interceptor
public class OurInterceptor {
    @javax.interceptor.AroundInvoke
    public Object someMethodName(InvocationContext context) throws Exception {

        // do something before method
        System.out.println(">before");

        //executing source method
        Object result = context.proceed();

        // do something after method
        System.out.println("<after");

        return result;
    }
}

And register our Interceptor in the beans.xml:



    
        de.beanbelt.bbinterceptorsandreapeatableannotations.interceptors.OurInterceptor
    

Now you can simply invoke this Interceptor by annotating a class or method in e.g. an Stateless EJB.

@Stateless
// interceptor will be invoked on every method in OurService
@javax.interceptor.Interceptors(OurInterceptor.class)
public class OurService {

    // You can invoke an Interceptor on one method only
    // Note: this will overwrite the class-level Interceptor
    //@Interceptors(OurInterceptor.class)
    public String getName(long id) {
        System.out.println("method");
        return database.get(id).getName();
    }
}

If you execute the code the following will be printed to the console:

>before
method
<after

Now we extends the example by our own annotations which will invoke the interceptor. Create an annotation that looks like the following:

@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OurAnnotation {
    @Nonbinding
    boolean someValue() default false;
}

And change the Interceptor like this:

@OurAnnotation
@javax.interceptor.Interceptor
public class OurInterceptor{...}

Now we can use the interceptor on method-level in OurService like this:

@Stateless
public class OurService {

    @OurAnnotation(someValue=true)
    public String getName(long id) {
        System.out.println("method");
        return database.get(id).getName();
    }
}

As you can see, we can pass parameters to the Annotation which we can read in our interceptor like this:

@OurAnnotation
@javax.interceptor.Interceptor
public class OurInterceptor{
    @javax.interceptor.AroundInvoke
    public Object someMethodName(InvocationContext context) throws Exception {
        Method method = context.getMethod();
        if (method.isAnnotationPresent(OurAnnotation.class)) {
            boolean someValue = method.getAnnotation(OurAnnotation.class).someValue();
            System.out.println("someValue is: "+someValue);
        }
        // do something before method
        System.out.println(">before");

        //executing source method
        Object result = context.proceed();

        // do something after method
        System.out.println("<after");

        return result;
    }

Since we annotated our method with someValue=true the output on the console will look like this:

someValue is: true
>before
method
<after

So far so good but what happens if we want to annotate our method multiple times like this?

@OurAnnotation(someValue=false)
    @OurAnnotation(someValue=true)
    public String getName(long id) {...}

We have to create a grouping Annotation (Note the S in OurAnnotationS).

@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OurAnnotations {
    @Nonbinding
    OurAnnotation[] value();
}

Now we can annotate our method like this:

@OurAnnotations({
        @OurAnnotation(someValue=false),
        @OurAnnotation(someValue=true)
    })    public String getName(long id) {...}

In Java 8 we can use the Repeatable Annotations feature. To do so we just need to add an Annotation to @OurAnnotation

@Inherited
@Repeatable(OurAnnotations.class)
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OurAnnotation {...}

Now we can annotate our method as we stated before:

@OurAnnotation(someValue=false)
    @OurAnnotation(someValue=true)
    public String getName(long id) {...}

But what happened to the Interceptor, why is it not working anymore? Well, we have to implement a new interceptor for our grouping Annotation like this:

@OurAnnotations
@javax.interceptor.Interceptor
public class OurGroupingInterceptor{
    @javax.interceptor.AroundInvoke
    public Object someMethodName(InvocationContext context) throws Exception {
        Method method = context.getMethod();
        if (method.isAnnotationPresent(OurAnnotations.class)) {
               OurAnnotation[] ourAnnotations = method.getAnnotation(OurAnnotations.class).value();
            for (OurAnnotation annotation : ourAnnotations) {
                System.out.println("someValue is: "+ annotation.someValue());
            }
        }
        // do something before method
        System.out.println(">before");

        //executing source method
        Object result = context.proceed();

        // do something after method
        System.out.println("<after");

        return result;
    }
}

Now the output on the console will look like this:

someValue is: false
someValue is: true
>before
method
<after

Now we successfully implemented an Interceptor with our own Annotation which we can repeat like we want.


Happy Coding!

1 comment :

  1. Nice tutorial. do you have some more on how to get values from the method you annotated with the interceptor?

    ReplyDelete