3

Let's say I have a Spring Controller like this:

    @Controller
    public class FooController {

        @RequestMapping(value = "/foo", method = RequestMethod.GET)
        public String update (Model model,
                          @RequestParam (value="id") String id,
                          @RequestParam (value="description") String description) {

                Foo foo = new Foo(id, description);

                fooService.create(update);

        return "foo";
    }

I'd like to re-write it like follows, but define my own request param mapping rather than let Spring's @ModelAttribute define it:

    @Controller
    public class FooController {

        @RequestMapping(value = "/foo", method = RequestMethod.GET)
        public String update(Model model,
                          @ModelAttribute("foo") Foo foo) {

        fooService.update(foo);

        return "foo";
    }

Does anyone know how I would do this? I have looked at converters, PropertyEditors and using @RequestBody but I don't think any of these are quite right. I need to somehow override Spring's databinding it seems.

7
  • Generally speaking, HTTP POST requests don't have query parameters. What exactly are you trying to do? Commented Jan 21, 2014 at 13:27
  • what do you mean with "define my own request param mapping rather than let Spring's @ModelAttribute define it"? Using @ModelAttribute Spring maps parameters to a model object automatically. What do you need to do? Commented Jan 21, 2014 at 14:14
  • @chrylis thanks for the comment. I updated to GET requests to avoid any confusion. To clarify, I have no problem with getting this spring controller to work. What I want to do is create my own mapping of http form request parameters to a model object - not just go with Spring's default of looking for model object accessors (getters and setters) that have the same name as the request parameter. The first code example does this in the method but I'm trying to encapsulate this elsewhere. This is a tiny example to illustrate, I'm interested in doing this for a large number of request params. Commented Jan 21, 2014 at 14:17
  • @davioooh This is a very small illustrative example. Let's say there were 20 request params to deal with, possibly mapping to different model objects. Instead of having a RequestMapping method that has 20 parameters, I'd like to encapsulate this mapping separately. I don't want to use ModelAttribute because I may want to give an http form parameter a different name to that of the getter/setter I'd like it to map to. Commented Jan 21, 2014 at 14:21
  • 1
    @Stuart You can annotate different names on the fields of the form object. Commented Jan 21, 2014 at 14:25

3 Answers 3

3

The solution I settled on was to use implement Spring's HandlerMethodArgumentResolver class to detect the Object and construct it from the HttpServletRequest. Note - this is very similar to @daviooh's answer. The main difference here is that this solution is Spring specific and it can detect the object class "automatically".

Add to spring context.xml

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="your.package.name.FooResolver" />   
    </mvc:argument-resolvers>
</mvc:annotation-driven>

Create the FooResolver:

public class FooResolver implements HandlerMethodArgumentResolver {

public static final String ID = "id";
public static final String DESCRIPTION = "description";

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterType().equals(Foo.class);
}

@Override
public Object resolveArgument(MethodParameter parameter,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest,
                              WebDataBinderFactory binderFactory) throws Exception {

    Foo foo = null;

    if(parameter.getParameterType().equals(Foo.class)) {

        foo = new Foo();

        if (webRequest.getParameter(ID) != null) tag.setId((String) webRequest.getParameter(ID));
        if (webRequest.getParameter(DESCRIPTION) != null) tag.setDescription((String) webRequest.getParameter(DESCRIPTION));
    }

    return foo;
}

}

Then controller method becomes:

    @RequestMapping(value = "/foo", method = RequestMethod.GET)
    public String update (Model model, Foo foo) {
        fooService.update(foo);
        return "foo";
    }

This solution allows me to de-couple the http request parameter names from the getter and setter method names on my model objects and pull this logic out of my controller so as to keep it uncluttered and so easier to follow. So controller logic is in the controller and http request to object mapping logic is in the resolver class.

Sign up to request clarification or add additional context in comments.

2 Comments

Another option I've used since asking this question is to use @ModelAttribute and create a new object FooForm whose getters and setters exactly match the http parameters. Then define some mapping for how to create a Foo object from the FooForm object.
One major advantage of this over using @ModelAttribute is that you can inject collaborators into your resolver class thereby giving you much more power and flexibility over mapping the request params to the object.
1

An option could be to implement an utility class (one for each entity you need to map) that parses request parameters to a custom object. Something like this:

public class FooParser{

    public static final String ID = "id";
    public static final String DESCRIPTION = "description";

    public static Foo parse(HttpServletRequest req){
        Foo foo = null;
        if (request.getParameter(ID) != null
            && request.getParameter(DESCRIPTION) != null) {
            foo = new Foo(request.getParameter(ID), request.getParameter(DESCRIPTION));
        }
        return foo;
    }
}

and use it into your controller method.

@Controller
public class FooController {

    @RequestMapping(value = "/foo", method = RequestMethod.GET)
    public String update(Model model,
                      HttpServletRequest req) {

    Foo foo = FooParser.parse(req);
    fooService.update(foo);

    return "foo";
}

1 Comment

@daviooh this is a great answer - it is a possible solution to my question. In addition it is independent of Spring (separation of concerns) so maybe better than "provide a custom implementation of the WebBindingInitializer interface" mentioned in my comment to Dave's answer. I will investigate both of these and post back my result. The only addition I would make would be autodetect of java object class which I might get from Spring's WebBindingInitializer.
0

Spring should do this for you automatically. The key is to match your form field with the properties of your "Foo" Object.

Here's some documentation: http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-methods

The next step is data binding. The WebDataBinder class matches request parameter names — including query string parameters and form fields — to model attribute fields by name. Matching fields are populated after type conversion (from String to the target field type) has been applied where necessary.

What does the other side of this request look like? What's the html of your submit form?

Edit

I've just created a demo that might help. Below is the code including the form, controller and foo class. Let me know any questions or problems you are having.

Form:

<form method="POST" action="/fooAction">
<input type="text" name="bar" id="bar"/></form>

Controller:

@Controller
public class GreetingController {

      @RequestMapping(value = "/fooAction", method = RequestMethod.POST)
      public String fooAction(Model m, Foo foo) {
        System.out.println("foo is: " + foo);
        m.addAttribute(foo);
        return "fooAction";
      }

    }

Foo Class:

public class Foo {
  private String bar;

  public String getBar() { return bar; }

  public void setBar(String bar) { this.bar = bar; }

  @Override
  public String toString() {
    return "Foo[" + bar + "]";
  }      
}

1 Comment

Thanks for your answer. This isn't what I was asking but you did point me in the right direction. docs.spring.io/spring/docs/4.0.0.RELEASE/…. I want to override Spring's default data binding so I need to "provide a custom implementation of the WebBindingInitializer interface". I'll look into this and post my answer code here for future reference.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.