Karthik's Weblog

@HandlerInterceptor for Spring MVC

Posted in Java, Spring by karthik on October 12, 2009

I have been doing quite a bit of Spring MVC at work these days. I like the @Controller model and with the bunch of
enhancements that have gone into Spring 3.0, Spring MVC has the potential to become the defacto Action based framework.

While Spring MVC has excellent support for Controller Annotations to cut down on amount of the XML
configuration, you are still expected to configure HandlerInterceptor -s in the Spring configuration file.
Sometimes it helps to keep all the related code together in one artifact.

So let me quickly summarize what I did –

I defined a custom annotation @Interceptors that lets you specify the interceptors that you would like to apply on
the controller.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Interceptors {
      /** HandlerInterceptor**/  
     Class[] value();
}

The custom @Interceptors annotation is exactly same as the EJB 3.0 Interceptors annotation definition. I kind of liked the fact that I could navigate to the interceptor implementation class using the IDE.

Next you define a couple of interceptors like so –

class MyInterceptor1 implements HandlerInterceptor{
        //..
} 

class MyInterceptor2 extends HandlerInterceptorAdapter{
        //..
}

and annotate the @Controller-s –

@Controller
@Interceptors({MyInterceptor1.class,MyInterceptor2.class})    
class TestController{

      @RequestMapping("/test")
      void test(ModelMap mm){

      }
}

Next let’s look at the implementation –

Spring’s HandlerMapping is responsible for resolving the HandlerExectionChain that maps to a given request URL. The HandlerExecutionChain encapuslates the Controller and the associated interceptors for a given request URL.

public interface HandlerMapping {      
      HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;    
}

 

Spring’s DefaultAnnotationHandlerMapping class maps handlers/@Controllers based on HTTP paths expressed through the RequestMapping annotation at the type or method level.

So we will enhance DefaultAnnotationHandlerMapping and add the ability to detect @Interceptors on the controllers

   
   package com.springmvc.extensions;

   import java.util.ArrayList;
   import java.util.List;
   import javax.servlet.http.HttpServletRequest;
   import org.springframework.beans.BeanUtils;
   import org.springframework.core.annotation.AnnotationUtils;
   import org.springframework.web.servlet.HandlerExecutionChain;
   import org.springframework.web.servlet.HandlerInterceptor;
   import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;

   public class HandlerInterceptorAnnotationAwareHandlerMapping extends  DefaultAnnotationHandlerMapping {

    @Override
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler,HttpServletRequest request){ #1
        HandlerExecutionChain chain = super.getHandlerExecutionChain(handler,request);  #2
        HandlerInterceptor[] interceptors = detectInterceptors(chain.getHandler().getClass());
        chain.addInterceptors(interceptors);
        return chain;
    }

    protected HandlerInterceptor[] detectInterceptors(Class handlerClass) {

        Interceptors interceptorAnnot = AnnotationUtils.findAnnotation(handlerClass, Interceptors.class);    #3
        List interceptors = new ArrayList();
        if (interceptorAnnot != null) {
          Class[] interceptorClasses = interceptorAnnot.value();
          if (interceptorClasses != null) {
            for (Class interceptorClass : interceptorClasses) {     #4   
              if (!HandlerInterceptor.class.isAssignableFrom(interceptorClass)) {
                raiseIllegalInterceptorValue(handlerClass,interceptorClass);      #5 
              }
              interceptors.add((HandlerInterceptor) BeanUtils.instantiateClass(interceptorClass));   #6 
            }
          }
        }
        return interceptors.toArray(new HandlerInterceptor[0]);
    }


    protected void raiseIllegalInterceptorValue(Class handlerClass,Class interceptorClass) {          
        throw new IllegalArgumentException(interceptorClass + " specified on "
            + handlerClass + " does not implement " + HandlerInterceptor.class.getName());
            
    }

 }

 

#1 Override the getHandlerExecutionChain() method defined in DefaultAnnotationHandlerMapping
#2 Call ‘super()’ so that you don’t change the base class behavior . This way you can aggregate the interceptors configured
in the Spring context file against the property ‘interceptors’ as well (it at all configured) .
#3 Look for @Interceptors annotation for a given Controller class.
#4 #5 Run through all the specified HandlerInterceptor implementations , making sure that they indeed implement the Spring’s
HandlerInterceptor interface.
#6 Instantiate the HandlerInterceptor instance (This will look for a default no-arg contructor). You could look up the
Spring ApplicationContext and check if an Interceptor has been configured as well.

As a final step, you need to declare the HandlerInterceptorAnnotationAwareHandlerMapping class in the Spring Application Context

  <bean class="com.springmvc.extensions.HandlerInterceptorAnnotationAwareHandlerMapping "/>

 

Let me know what you think.

Update: Nice to see keith link to this blog on spring jira

Advertisements
Tagged with: , ,