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

About these ads
Tagged with: , ,

19 Responses

Subscribe to comments with RSS.

  1. john h said, on December 1, 2009 at 9:05 am

    some good stuff! thanks.

  2. Stephane Fellah said, on May 28, 2010 at 3:06 pm

    Very interesting article. I tried to implement your approach. However the final step seems to be incomplete. I didn’t manage to have my controller annotations with Intersectors be handled at all. Could u provide more details about this final step, how it works ? I am using the latest release of Spring 3.0 with Roo.

    • karthik said, on May 28, 2010 at 5:18 pm

      You need to make sure that the custom HandlerMapping is used by Spring dispatcher servlet to resolve @Controller-s.

      If you are using spring 3.0 MVC namespace ( <mvc:annotation-driven/>, it automatically registers the default DefaultAnnotationHandlerMapping. In that case, HandlerInterceptorAnnotationAwareHandlerMapping will not be used by DispatcherServlet.
      So Interceptors will not be detected.

      So you could comment out <mvc:annotation-driven/> and give it a shot.
      I’m assuming that you are “component-scanning” your @Controller-s

      - Karthik

      • mark said, on September 19, 2011 at 4:57 pm

        I found I could leave the annotation-driven, so long as I explicitly set the “order” of my customer handler mapping to -1. It’s kinda weird, but the annotation-driven parser explicitly creates, and sets the default handler to 0, and provides no way to directly override. However, since it does honor order, this seems to work fine.

  3. Tarique Islam said, on September 5, 2010 at 12:20 am

    Thanks karthik. I was looking for something like this

  4. Dom said, on January 20, 2011 at 10:49 am

    Nice idea, apart from BeanUtils.instantiateClass(interceptorClass). What if my interceptor needs injected dependencies? Is there a way I can get a bean from the context?

    • Dom said, on January 20, 2011 at 11:23 am

      It seems that the DefaultAnnotationHandlerMapping already has access to the full context. So you can replace the instantiateClass call with getApplicationContext().getBean(interceptorClass) if you need beans that have been autowired already.

      • karthik said, on February 21, 2011 at 8:17 am

        Yep, we do both. Look for the class in the application context and if not found , instantiate it.

        - Karthik

  5. PaquitoSoft said, on January 25, 2011 at 3:10 am

    Great and very useful post.
    Thanks!

  6. chris said, on February 21, 2011 at 3:13 am

    I know this is an old post but I found it very usefull so : thank you.

    I just modify it to avoid the @Interceptors annotation and add all the beans that implement the HandlerInterceptor (you loose the order but if you do not need it…) :

    protected HandlerInterceptor[] detectInterceptors() {
    Map interceptors = this.getApplicationContext().getBeansOfType(HandlerInterceptor.class);
    Collection interceptors_ = interceptors.values();
    return interceptors_.toArray(new HandlerInterceptor[interceptors_.size()]);
    }

    • Dom said, on February 21, 2011 at 4:47 am

      @Chris.
      I thought the point was to keep the class and annotation together so when you read the class you can understand it is intercepted.

  7. mark said, on September 19, 2011 at 4:59 pm

    Anybody tried adding checks for method level interceptors. I don’t need it today, but I can see people asking (my short term answer will be to tell them just to make the call directly in the method, but it would be cleaner if we could use the exact same syntax at both method and class level)

  8. pete911 said, on January 5, 2012 at 5:39 am

    Hi,
    I’ve changed it a bit, so instead of using class in annotation, I use bean name (so it can be set in spring context with dependencies if required), then in handler interceptor “getApplicationContext().getBean(…)”

  9. Bryan said, on February 2, 2012 at 11:18 am

    What if you want an interceptor to run for all requests, or better yet, only “get” requests?

  10. chrisol said, on June 21, 2012 at 6:09 am

    Thanks karthik, this post is very useful. I have just changed the following line :
    interceptors.add((HandlerInterceptor) BeanUtils.instantiateClass(interceptorClass));
    by:
    interceptors.add((HandlerInterceptor) getApplicationContext().getBean(interceptorClass));

    In order to get interceptors alreadt fully instantiated by spring.

  11. Venkatesh said, on August 30, 2012 at 6:08 am

    Thanks for sharing an Interesting and an useful post..

  12. nehal said, on October 3, 2012 at 1:12 pm

    Thanks Karthik, this post is very useful. I would like to implement this on action method level as well. Has anyone tried that?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: