Skip to main content
Last updated May 27, 2011 16:29, by spericas
Feedicon  

Interceptors and Filters


Background


Interceptors are defined in the context of JSR 318 - EJB 3.1. The specification is [available here|http://jcp.org/aboutJava/communityprocess/final/jsr318/index.html]. Interceptor methods are used to interpose on bean method invocations and lifecycle events. Interceptor methods can be defined in an _interceptor class_ or in the _target class_ whose methods are being interposed.

An interceptor method is identified by one of the following annotations: @PostConstruct, @PreDestroy, @AroundInvoke and @AroundTimeout. The annotations @PostConstruct and @PreDestroy are used to intercept the corresponding lifecycle events; the annotation @AroundInvoke is used to intercept bean method invocations and @AroundTimeout is used to intercept timeout method invocations.

Interceptor methods annotated by @AroundInvoke and @AroundTimeout must be defined as:

Object <method-name>(InvocationContext) throws Exception
Methods annotated by @PostConstruct and @PreDestroy must be defined as:
void <method-name>(InvocationContext) throws Exception
when defined in an interceptor class, and:
void <method-name>() throws Exception
when defined as part of the target class.

The @Interceptors annotation can be used to associate an interceptor with a method in a target class or the target class itself (thus, intercepting all the methods in the class).

The following example illustrates the concepts presented thus far:

@Stateless
@Interceptors(MyInterceptor1.class)
public class MyBean {

  @Interceptors(MyInterceptor2.class)
  public void method1() { ... }

  public void method2() { ... }

  @AroundInvoke
  public Object f16(InvocationContext ctx) {
    ...
    ctx.proceed();
    ...
  }

  @PostConstruct
  public void f15() { ... }
}

Interceptors in CDI


CDI enhances interceptors with an annotation-based approach to binding interceptors to beans. Rather than listing interceptors using @Interceptors on a bean class, CDI uses annotations to associate beans with interceptors.

For example, logging capabilities can be added to a method by defining a @Logged annotation that decorates both the method and its interceptor.

@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Logged { 
}

@Logged
@Interceptor
public class MyInterceptor {

  @AroundInvoke
  public Object f15(InvocationContext ctx) {
    // log ...
    ctx.proceed();
    return null;
  } 
}

public class MyBean {
  
  @Logged
  public void method1() { ... }
}

The method f15() will intercept calls to method1() and log them.

Interceptors in CDI are *not* enabled by default. They must be listed in the beans.xml to be active.

<beans ...>
    <interceptors>
        <class>mypackage.MyInterceptor</class>
    </interceptors>
</beans>

Use Cases in JAX-RS Applications


Some of the most popular use cases for using interceptors in JAX-RS applications are:

  • [uc1] Logging: e.g., resource method calls
  • [uc2] Authorization: checking credentials before a resource method is called
  • [uc3] Entity processing: compression, digital signatures, etc.
  • [uc4] Caching: returned cached entity without calling resource method
  • [uc5] Java security: using a Subject and an AccessControlContext to wrap a resource method invocation

Interceptors in Resteasy


Resteasy has identified new _interception points_ that are useful in, and specific to, JAX-RS applications. These interception points enable developer to tackle the use cases listed above. They are:

  1. Wrapping MessageBodyReader.readFrom() (see [uc3])
  2. Wrapping MessageBodyWriter.writeTo() (see [uc3])
  3. Non-wrapping before a resource method is called and before unmarshalling (see [uc1] [uc2] [uc4])
  4. Non-wrapping after a resource method is called and before marshalling (see [uc1])

Correspondingly, and to provide additional contextual information to interceptors, Resteasy defines Java interfaces for each of these interception points. Namely,

public interface MessageBodyReaderInterceptor {
   Object read(MessageBodyReaderContext context) throws IOException, WebApplicationException;
}

public interface MessageBodyWriterInterceptor {
   void write(MessageBodyWriterContext context) throws IOException, WebApplicationException;
}

public interface PreProcessInterceptor {
   ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException;
}

public interface PostProcessInterceptor {
   void postProcess(ServerResponse response);
}

Note that PreProcessInterceptor returns a ServerResponse. If a call to preProcess() returns a non-null value, the resource method is never invoked. This can be used, for example, by a security interceptor that fails to authorize a request.

Server-side interceptors are defined as JAX-RS providers. They are identified from other providers by using the @ServerInterceptor annotation. By default, interceptors apply to all resource methods in all resource classes. This can be fine tuned _dynamically_ if an interceptor implements the AcceptByMethod interface.

public interface AcceptedByMethod {
   public boolean accept(Class declaring, Method method);
}

Note that this is different from EJB and CDI in which the binding is static, either by direct reference to interceptor classes or via annotations. The following is an interceptor for all resource methods annotated by @GET; this interceptor adds a header before MessageBodyWriter.writeTo() is called.

@Provider
@ServerInterceptor
public class MyHeaderDecorator implements MessageBodyWriterInterceptor, AcceptedByMethod {

    public boolean accept(Class declaring, Method method) {
       return method.isAnnotationPresent(GET.class);
    }

   public void write(MessageBodyWriterContext context) throws IOException, WebApplicationException
   {
      context.getHeaders().add("My-Header", "custom");
      context.proceed();
   }
}

Client Side Interceptors in Resteasy


Client-side interceptors are also supported in Resteasy. They implement ClientExecutionInterceptor and are annotated by @ClientInterceptor (and @Provider).

public interface ClientExecutionInterceptor {
  ClientResponse execute(ClientExecutionContext ctx) throws Exception;
}

public interface ClientExecutionContext {
  ClientRequest getRequest();
  ClientResponse proceed() throws Exception;
}

These interceptors wrap the actual HTTP invocation (after MessageBodyWriter) and, thus, can be used to for tasks like compression and caching.

{note}Why aren't these interception points akin to those on the server side? I.e., wrapping MessageBodyReader.readFrom() and MessageBodyWriter.writeTo(), respectively.{note}

Filters in Jersey


Jersey tackles the use cases listed above using _filters_. Filters are supported both on the client and the server, albeit with somewhat different semantics. Client filters can be associated with instances of Client and WebResource and define a single method:

ClientResponse handle(ClientRequest cr)

Consequently, these client filters are _wrapping_, using the terminology from interceptors. I.e., a typical filter will look as follows:

ClientResponse handle(ClientRequest cr) {
   // Inspect/modify request cr
   ClientReponse rs = getNext().handle(cr);
   // Inspect/modify response rs
}

Server-side interceptors in Jersey are non-wrapping. They execute before and after the resource method is called, thus pre-processing the request and post-processing the response.

References


 
 
 
Close
loading
Please Confirm
Close