Karthik's Weblog

Taking Spring MVC @Controller method injection a step further

Posted in Java, Spring by karthik on February 3, 2010

Spring MVC @Controllers typically allow you to annotate your controller method parameters making it http agnostic and increasing the testability of the controllers at the same time. But sometimes you are required to repeat the parameter signature as shown below –

@Controller
class AccountController{
@RequestMapping("/create")
public String create(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){
}
@RequestMapping("/update")
public String update(@RequestHeader(value="SM_USERDN")String smUserdn,
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

}
}

Controller methods ‘create’ & ‘update’ access the same set of servlet environment specific parameters.
I was wondering if it would help if we could move all those parameters to an annotated POJO as shown
below and reuse the POJO in the controller methods –

Note that the Spring MVC @Controller annotations that let you access request, cookie, header values
can be specified at the method parameter level only. I obviously wanted to reuse these annotations and found that to be a limitation only to realize later that I could make use of constructor injection style

public class MyEnv{
private final String smUserdn;
private final String smSession;
private final String userId;
@Autowired
public MyEnv(@RequestHeader(value="SM_USERDN") String smuserdn;
@CookieValue(value="smsession") String smSession,
@SessionParam(value="user_id") String userId
){

this.smuserdn = smuserdn;
this.smSession = smSession;
this.userid = userId;
}

}
@Controller
class AccountController{
@RequestMapping("/create")
public String create(@Composite MyEnv env){

}
@RequestMapping("/update")
public String update(@Composite MyEnv env){
}
}

You need the @Composite custom annotation to trigger this binding.
Do you guys see a need for something like this? I did implement this feature. But wanted to check if people find this useful at all.
thanks!

Tagged with: , ,

Learn to customize Spring MVC @Controller method arguments

Posted in Spring by karthik on November 8, 2009

Spring MVC Annotation based controllers offers a lot of flexibility w.r.t Annotated controller handler method signature.
Refer to the Spring MVC documentation to get a detailed list of supported argument types & annotations.

What is interesting is that in addition to the supported controller method argument types , you can add support for custom argument types as well. You just need to provide a WebArgumentResolver interface implementation that knows how to resolve a particular controller method argument.

The interface definition is reproduced here for convenience -

    public interface WebArgumentResolver{ 
Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest);
}

Next let’s build a case for rolling out your own WebArgumentResolver implementation.

If you are trying to extract the value for the servlet request param ‘id’, instead of accessing the “raw” servlet apis as shown below -

@Controller
class MyController{
@RequestMapping("/test")
public void test(HttpServletRequest request){
String id = request.getParameter("id");
}
}

you could use a built-in annotation like this -

@Controller
class MyController{
@RequestMapping("/test")
public void test(@RequestParam(value="id") String id){

}
}

While both the code snippets achieve the same thing, the latter is probably more unit-test friendly and doesn’t require you to mock HttpServletRequest object as is the case with the former. In other words, the annotations preserve the POJO-ness of your @Controller. (In reality, the preceding code snippets don’t actually mean the same thing – Spring takes care of invoking the databinder, registered property editors as well when it sees a @RequestParam annotation.

Now let’s say you want to access a http session variable ‘user’, this is the way you would do it in a Controller –

@Controller
class MyController{
// Spring injects the current HttpSession variable at runtime into the 'test' method.
@RequestMapping("/test")
public void test(HttpSession session){
String user = (String)session.getAttribute("user");
doSomething(user);
}
}

While this works, it doesn’t seem consistent with the annotation based approach.

An annotation similar to @RequestParam albeit for accessing Http Session parameters could look like this –

  package com.springmvc.extensions; 

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionParam {
/** * The name of the Session attribute to bind to. */ String value() default "";
/** * Whether the parameter is required. * Default is true, leading to an exception thrown in case * of the parameter missing in the request. Switch this to * false if you prefer a * null in case of the parameter missing. * Alternatively, provide a {@link #defaultValue() defaultValue}, * which implicitely sets this flag to false. */ boolean required() default true;
/** * The default value to use as a fallback. Supplying a default value * implicitely sets {@link #required()} to false. */ String defaultValue() default "";
}

Next, let's define the argument resolver -

  package com.springmvc.extensions;

  import java.lang.annotation.Annotation;

  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpSession;

  import org.springframework.core.MethodParameter;
  import org.springframework.web.HttpSessionRequiredException;
  import org.springframework.web.bind.support.WebArgumentResolver;
  import org.springframework.web.context.request.NativeWebRequest;


  public class SessionParamArgumentResolver implements WebArgumentResolver {

    public Object resolveArgument(MethodParameter param,
        NativeWebRequest request) throws Exception {

      Annotation[] paramAnns = param.getParameterAnnotations();   #1
      Class paramType = param.getParameterType();

      for (Annotation paramAnn : paramAnns) {                     
        if (SessionParam.class.isInstance(paramAnn)) {            #2
          SessionParam sessionParam = (SessionParam) paramAnn;
          String paramName = sessionParam.value();
          boolean required = sessionParam.required();
          String defaultValue = sessionParam.defaultValue();
          HttpServletRequest httprequest = (HttpServletRequest) request
              .getNativeRequest();
          HttpSession session = httprequest.getSession(false);
          Object result = null;
          if (session != null) {
            result = session.getAttribute(paramName);
          }
          if (result == null)
            result = defaultValue;
          if (result == null && required && session == null)
            raiseSessionRequiredException(paramName, paramType);  #3
          if (result == null && required)
            raiseMissingParameterException(paramName, paramType); #4

          return result;
        }
      }

      return WebArgumentResolver.UNRESOLVED;                      #5

    }

    // ..

    protected void raiseMissingParameterException(String paramName,
        Class paramType) throws Exception {
      throw new IllegalStateException("Missing parameter '" + paramName
          + "' of type [" + paramType.getName() + "]");
    }

    protected void raiseSessionRequiredException(String paramName,
        Class paramType) throws Exception {
      throw new HttpSessionRequiredException(
          "No HttpSession found for resolving parameter '" + paramName
              + "' of type [" + paramType.getName() + "]");
    }

  }

Explanation -
#1 looks up the annotations specified on the method argument
#2 checks if the method argument annotation is of type @SessionParam
#3 throws an exception if the param is marked as 'required' and no valid session exists
#4 throws an exception if the param is not found in the http session
#5 You could configure a list of WebArgumentResolver-s. In this example, we have only one.
If the argument resolver fails to resolve the argument successfully, its imperative that you return the special object WebArgumentResolver.UNRESOLVED. That way Spring knows that it needs to consult with other argument resolver-s to resolve the a given controller method parameter. A return value of "null" from a WebArgumentResolver is considered valid and the method parameter is marked as 'resolved'!

Next we will discuss how to configure the SessionParamArgumentResolver class

AnnotationMethodHandlerAdapter class that ships with Spring is a HandlerAdapter implementation that is specifically designated to handle request intended for controllers based on the Annotation model. One of the primary responsibilities of this handler adapter is to 'resolve' all the handler method arguments prior to invoking the handler method.

While the AnnotationMethodHandlerAdapter knows how to resolve all the standard argument types, it consults the configured WebArgumentResolver-s for resolving the arguments that are not supported by default.

So next let's configure the custom WebArgumentResolver that we just developed -

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">     
   <property name="customArgumentResolver" ref="sessionArgResolver"/>
</bean>  
    
<bean id="sessionArgResolver" class=quot;com.springmvc.SessionParamArgumentResolver"/>
          

With the preceeding Spring bean configuration in place, you could rewrite your controller like this -

  @Controller
class MyController{
@RequestMapping("/test")
public void test(@SessionParam(value="user") String user){
doSomething(user);
}
}

Hopefully this article gives you a good idea of how to customize Spring MVC @Controller to suit your needs.

@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

Tagged with: , ,
Follow

Get every new post delivered to your Inbox.