33

I have a class that models my request, something like

class Venue {
    private String city;
    private String place;

    // Respective getters and setters.
}

And I want to support a RESTful URL to get information about a venue. So I have controller method like this.

@RequestMapping(value = "/venue/{city}/{place}", method = "GET")
public String getVenueDetails(@PathVariable("city") String city, @PathVariable("place") String place, Model model) {
    // code
}

Is there a way, I can say in spring to bind my path variables to the model object (in this case Venue) instead of getting every individual parameter?

M. Prokhorov
  • 3,894
  • 25
  • 39
Senthil Babu
  • 1,243
  • 2
  • 11
  • 20

3 Answers3

46

Spring MVC offers the ability to bind request params and path variables into a JavaBean, which in your case is Venue. For example:

@RequestMapping(value = "/venue/{city}/{place}", method = "GET")
public String getVenueDetails(Venue venue, Model model) {
    // venue object will be automatically populated with city and place
}

Note that your JavaBean has to have city and place properties for it to work.

For more information, you can take a look at the withParamGroup() example from spring-projects/spring-mvc-showcase

buræquete
  • 14,226
  • 4
  • 44
  • 89
Liang Zhou
  • 2,055
  • 19
  • 20
  • 1
    I never thought even path variable can be automatically put into a bean. Any doc on this interesting behaviour? what is the formal name of it? – torez233 Sep 13 '20 at 05:44
  • 1
    What's odd about this solution is that Spring will accept urls like `/venue/{city}/{place}?city=foo&place=bar` and bind the object properties from the query parameters rather than the path parameters. It would be nice if there was a way to tell Spring Boot that those are only supposed to be from the path, not query params. – Shannon Oct 15 '21 at 20:53
3

As per the Spring documentation available at http://static.springsource.org/spring/docs/3.2.3.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates, automatic support is provided to simple types only:

A @PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.

I haven't tried this specific combination of @RequestParam and Model, but it looks like you can achieve your desired implementation by creating a custom WebBindingInitializer, as detailed at http://static.springsource.org/spring/docs/3.2.3.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-typeconversion.

The custom class will have access to the WebRequest and would return a domain object populated with data extracted from this request.

vincentks
  • 694
  • 6
  • 14
3

You could provide an implementation of the HandlerMethodArgumentResolver interface. This interface is mainly used to resolve arguments to your controller methods in ways that Spring can't (which is essentially what you are trying to do). You accomplish this by implementing these two methods:

public boolean supportsParameter(MethodParameter mp) {
    ...
}

public Object resolveArgument(mp, mavc, nwr, wdbf) throws Exception {
    ...
}

You can inject your implementation into the Spring context via:

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="com.path.ImplementationOfHandlerMethodArgumentResolver"/>
    </mvc:argument-resolvers>
</mvc:annotation-driven>

When Spring encounters a parameter it can't resolve, it will call your method supportsParameter() to see if your resolver can resolve the parameter. If your method returns true, then Spring will call your resolveArgument() method to actually resolve the parameter. In this method, you have access to the NativeWebRequest object, which you can use to grab the path beyond the contextPath. (in your case it would be: /venue/{city}/{place}) you can parse through the request path and attempt to get the city/place Strings to populate in your Venue Object.

Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
Paul Gray
  • 546
  • 1
  • 5
  • 10