Saturday, June 10, 2017

JavaEE: Interceptors and InterceptorBinding

Interceptors are used to invoke interceptor methods on an associated target class in conjunction with method invocations or lifecycle events.  Interceptor class may be designated with @Interceptor annotation, but it is not required.  An interceptor class must have a public and no-argument constructor.  Interceptor methods within the target class or an interceptor class are annotated with one of the following annotations.

Interceptor Method Annotations
  • javax.interceptor.AroundConstruct
       Designates the method as an interceptor method that receives a callback after the target class is constructed.
  • javax.interceptor.AroundInvoke
       Designates the method as an interceptor method.
  • javax.interceptor.AroundTimeout
       Designates the method as a timeout interceptor for interposing on timeout method for enterprise bean timers.
  • javax.annotation.PostConstruct
       Designates the method as an interceptor method for post-construct lifecycle events.
  • javax.annotation.PreDestroy
       Designates the method as an interceptor method for pre-derstroy lifecycle events.

Interceptor classes have the same lifecycle as their associated target class.

Example
- Interceptor
This interceptor will be used to log each method calls and an execution time of it.  On a previous post, we created an injectable Logger object and the injection is used in this example.  A proceed method of the InvocationContext proceed to the next interceptor.  If the interceptor is the last interceptor, it triggers the target class method.

@Interceptor
public class LoggingInterceptor implements Serializable {
   @Inject
   private transient Logger logger;
   
   @AroundInvoke
   public Object logMethod(InvocationContext ict) throws Exception {
      long start = System.currentTimeMillis();
      
      logger.info(" Entering " + ict.getTarget().getClass().getName() + " - " + ict.getMethod().getName());
      
      try{
         return ict.proceed();
      }finally{
         logger.info(" Exiting  " + ict.getTarget().getClass().getName() + " - " + ict.getMethod().getName() + 
               " Execution Time: " + (System.currentTimeMillis() - start) + "ms");
      }
   }   
}

- Target class
I will use a simple Hello RESTful service, which is very same as one shown on a previous post.  To use an interceptor in a target class, an @Interceptors(LoggingInterceptor.class) annotation is used.  When you use more than one interceptor, you can use the annotation like this: @Interceptors({FirstInterceptor.class, SecondInterceptor.class})

@Interceptors(LoggingInterceptor.class)
@Path("/hello")
public class Hello {
   @Inject
   private Logger logger;
   
   @GET
   public String sayHello(){
      logger.info("Hello World Logging!");
      return"Hello RESTful world!";
   }
}

Let's run it on a TomEE maven plug in.  Pom.xml is still same as shown on this post and this post.
   mvn clean install tomee:run

- Result
Open a URL http://localhost:8080/demo/hello
Then, look at the console output.  The second line is from the sayHello method in the target class and the first & the third line is from the interceptor method.

INFO [http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Entering demo.rest.endpoint.Hello - sayHello
INFO [http-nio-8080-exec-2] demo.rest.endpoint.Hello.sayHello Hello World Logging!
INFO [http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Exiting  demo.rest.endpoint.Hello - sayHello Execution Time: 0ms

InterceptorBinding
Interceptor bindings are intermediate annotations can be used to associate an interceptor with target beans (with class level or method level).

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

We can use this interceptor binding with the LoggingInterceptor shown above.  Simply added the @Logging annotation
@Logging
@Interceptor
public class LoggingInterceptor implements Serializable {
   @Inject
   private transient Logger logger;
   
   @AroundInvoke
   public Object logMethod(InvocationContext ict) throws Exception {
      long start = System.currentTimeMillis();
      
      logger.info(" Entering " + ict.getTarget().getClass().getName() + " - " + ict.getMethod().getName());
      
      try{
         return ict.proceed();
      }finally{
         logger.info(" Exiting  " + ict.getTarget().getClass().getName() + " - " + ict.getMethod().getName() + 
               " Execution Time: " + (System.currentTimeMillis() - start) + "ms");
      }
   }   
}

Then, interceptor classes need to be defined in the beans.xml file (shown on a previous post) with an <interceptors> element.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                           http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       version="1.1" bean-discovery-mode="all">
    <interceptors>
      <class>demo.interceptor.LoggingInterceptor</class>
    </interceptors>
</beans>

After this, we can use the interceptor binding directly in target classes instead of an @Interceptors annotation.  The logging result is same.

@Logging
@Path("/hello")
public class Hello {
   @Inject
   private Logger logger;
   
   @GET
   public String sayHello(){
      logger.info("Hello World Logging!");
      return"Hello RESTful world!";
   }
}

No comments:

Post a Comment

Java 9: Flow - Reactive Programming

Programming world has always been changed fast enough and many programming / design paradigms have been introduced such as object oriented p...