Karthik's Weblog

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.

28 Responses

Subscribe to comments with RSS.

  1. Karthikeyan said, on November 16, 2009 at 12:20 pm

    Hi Karthik thanks for sharing this..

  2. phil said, on November 19, 2009 at 12:20 pm

    Do you think something like this could be used to override the default behavior of the @ModelAttribute ?

    I would like @ModelAttribute to resolve the values I have in my session.

    I’m not in favor of using it with @SessionAttributes because I need to alter the way how the cleanup of the attributes occurs. Out of the box from spring, I’ve noticed the sessionattributes are hanging around for a longer period than desired, and calling sessionstaus.complete() is not always applicable (ie: on a GET).

    So I guess the real question would be to know if this argument resolver can override the ones that come with spring for their default annotations.

    Thanks

    -phil

    • karthik said, on November 19, 2009 at 12:58 pm

      I can relate to what you are saying. But sadly, overriding the default behavior of built-in annotations is NOT supported by Spring @MVC 3.0 in its current form.
      The custom argument resolvers get a chance only after the default ones have already executed and resolved the argument (@ModelAttribute for eg).
      Once an argument is marked as ‘resolved’, you cannot do anything about it.

      While AnnotationMethodHandlerAdapter is customizable, there is a lot of scope for improving the class design. Pretty much every method that you might want to ‘customize’ is either ‘private’ or difficult to override.

      Btw, there are quite a few Spring JIRA issues on this subject of AnnotationMethodHandlerAdapter extensibility. I’m think the @MVC team will address this issue in the next release (3.1).

      – Karthik

      • Matt said, on June 10, 2011 at 9:10 am

        With Spring 3.1 they fixed this and you CAN override the default argument resolvers, as well as still implement custom argument resolvers. Go Spring!

  3. phil said, on November 19, 2009 at 4:09 pm

    Yes i’ve spent some time going through the spring source and can’t find a way to handle my need from this perspective.

    However, I think I may be able to address my real problem with an interceptor. The problem with using the @ModelAttibute (as a handler’s parameter annotation) with @SessionAttributes is the model won’t be available on the HTTP GET. I can instantiate a new instance with an interceptor and then populate it in the controllers handler. For subsequent requests this model will already be populated. The next issue will to determine when to remove the session attribute – which I would like to keep it in session for as long as the same controller is being executed without having to specifically call SessionStatus.complete(). This is something else I can handle within an interceptor.

    I guess the only question I’m left with is how does the SessionAttributeStore play a role in populating the @ModelAttribute. I believe the store is what handles the session management, and I’m not sure how I can get a handle on that instance within the interceptor. I don’t know if there will be issues removing the session attribute directly from the session without going through the sessionattributestore.

    You have any thoughts on this? It would be greatly appreciated.

    -phil

  4. karthik said, on November 19, 2009 at 4:46 pm

    You can set the DefaultSessionAttributeStore as a property on AnnotationMethodHandlerAdapter and refer to the same DefaultSessionAttributeStore bean in your interceptor if that helps.

  5. phil said, on November 19, 2009 at 5:38 pm

    Yes that’s true I will have to give it a shot.

  6. Luther Baker said, on March 23, 2010 at 5:05 pm

    Great post. Moving to Spring 3 and wondering how I could keep the signatures clean AND have access to the HttpSession. SessionAttributes won’t work as it’s scope is too narrow, but this, … this is great!

    Wonderful real-world example that demonstrates several things including how simple the idea really is. Your code example was spot on. I’m already using this code example in my unit testing!

    Thanks.

  7. JC said, on May 4, 2010 at 7:25 am

    hi

    just want to know if there is any similar example for spring mvc portlet.. i would like to store my model into the session and re-use them in another method call within the same controller?

    void test(@SessionParam(value=”user”) String user){

  8. Leadhyena said, on July 13, 2010 at 4:05 pm

    So I’m doing some work with creating my own annotations similar to the above, and one of the things that you lose with this approach is the automatic application of Converters and Validation. This is because when you use a custom WebArgumentResolver, Converters and Validation never get executed; that part of the resolveHandlerArguments code short circuits when you return something that’s not WebArgumentResolver.UNRESOLVED in your resolver.

  9. bozhobg said, on September 1, 2010 at 1:26 pm

    Wonderful. One note – you can use param.getParameterName(); if “value” is empty. Much like “@RequestParam”

  10. John Glynn said, on November 24, 2010 at 11:04 pm

    Wonderful writeup. I was just considering the other day an opportunity to simplify a common scenario we have where we often first look to the request for a property, if it isn’t present we look to the session for it. Your example here outlining the possibilities for WebArgumentResolver is exactly the route I was looking for; kudos.

  11. […] then I found this great blog post detailing exactly what I was interested in.  In a nutshell, you need to […]

  12. Krishna said, on April 12, 2011 at 6:44 pm

    Hi Karthik,
    Thanks for sharing a great deal of coll stuff!!!

  13. […] Learn to customize Spring MVC @Controller method arguments Tagged: questionsspringspring-security /* * * CONFIGURATION VARIABLES: EDIT BEFORE […]

  14. […] think you can try to use a Custom MethodArgumentResolver (see this blog for an […]

  15. rogez said, on November 29, 2012 at 5:46 am

    Hello,
    thanks for this post, it’s very useful. But I think it has a little issue when the argument is null and required because of the order of the “if” statements at the end of the resolveArgument method. I think the code never reach #4 because default value cannot be null.
    And another issue is that really SessionArgument only supports String values, correct?

  16. Rosario Hebard said, on March 2, 2014 at 9:03 am

    Looking at the shifting interest of investors over Online trading in india numerous online
    stock broking firms have established. The
    guy who actually mentioned the relocate advance, and perhaps even
    traded it, or even the guy who occurs AFTER
    the move has happened and spouts a variety of complex-sounding fancy words and theories.
    For the examples, a tick on a corn contract is $12.

  17. Johng27 said, on June 11, 2014 at 7:18 pm

    I discovered your blog web site website on the search engines and check several of your early posts. Always sustain up the very excellent operate. I lately additional increase Rss to my MSN News Reader. Looking for toward reading much far more on your part later on! fdbcfcafcfdd

  18. […] @See: Learn to customize Spring MVC @Controller method arguments […]

  19. SARAVANAN said, on May 3, 2018 at 8:58 am

    Hi Villiam,
    I am having the following issue
    a. In my GET method accountId is visible, while hitting edit action on the form. Account details are visible. In this case user changing accountId on the URL then respective account details are visible. In this case i am using encryption. Now accountId is encrypted if user changing value. Nothing is appeared.
    When as while hitting the edit button, page getting 400-bad request parameter error.
    i found Filter not allowing encrypted value in the url. So, i decided to write CustomHandlerMethodArgumentResolver by implementing HandlerMethodArgumentResolver class. i have done above approach, but my CustomHandlerMethodArgumentResolver.java is not calling.
    Could you please let me know how to write.
    I have done below steps
    1. i have written my own customHandlerArgumentResolver.
    2. supportsParameter override.
    3. resolveArgument override.
    4. entry about CustomHandlerResolver details given in app_servlet.xml file (spring)
    5.

    In this case my own customHandlerResolver is not calling. Where i made mistake.

  20. […] @See:学习自定义Spring MVC @Controller方法参数 […]

  21. […] @See : Spring MVC @Controller 메소드 인자를 커스터마이징하는 법 배우기 […]

  22. […] @See: Learn to customize Spring MVC @Controller method arguments […]

  23. […] @See: Learn to customize Spring MVC @Controller method arguments […]


Leave a reply to phil Cancel reply