If you have worked with Spring 2.5 annotation based web MVC framework you know that it allows you to freely design you controller method signatures:
@RequestMapping(value = "users", method = GET)
public String showUsers(Model model) {
model.addAttribute("users",
userManagement.getUsers());
return "users";
}
As you can see we use a parameter of type Model
that will we automatically get an instance handed by Spring. For a complete list of supported parameter and return types see Spring reference documentation on that topic.
But why do I mention that? The title says “neglected classes”, not “things I also could have read up in the reference docs”. The crucial thing here is to know that the list of supported types is not fixed and there is an SPI to write your class allowing to get a parameter injected automatically.
The first example I want to come up with is pagination. The OpenSource framework Hades provides dedicated support to allow paginated access to databases via JPA. The core abstractions achieving this is the Pageable interface in combination with the PageRequest
implementation class. But there is still the question on how to create instances of PageRequest
from an incoming request. A naive might be the following:
@RequestMapping(value = "/users", method = GET)
public String showUsers(Model model, WebRequest request) {
Integer size = // extract size from request
Integer page = // extract page from request
Sort sort = // extract sort options from request
Pageable pageable =
new PageRequest(page, size, sort);
model.addAttribute("users",
userManagement.getUsers(pageable));
return "users";
}
What’s wrong with this? First, it shouldn’t be the controllers task to extract pagination information from the request. Second, even if we’d move this code into a utility class, we’d be forced to get WebRequest
injected into every controller method that wants to use pagination, which breaks the abstraction level gained.
So what about rather something like this:
@RequestMapping(value = "/users", method = GET)
public String showUsers(Model model,
Pageable pageable) {
model.addAttribute("users",
userManagement.getUsers(pageable));
return "users";
}
public class PageableArgumentResolver implements WebArgumentResolver {
@Override
public Object resolveArgument(
MethodParameter methodParameter,
NativeWebRequest webRequest) throws Exception {
// implement extraction logic here
}
}
Inside the implementation your first task is to determine, whether you can return the given MethodParameter
or return a WebArgumentResolver.UNRESOLVED
. Our resolver would have to check if the target type is a Pageable
and extract the request parameters then.
A very often faced problem in web controllers is that one actually has to know which user has triggered the request. This could be of course done programatically inside the controller method similar to the naive approach shown above. What about this approach:
@RequestMapping(value = "/myaccount", method = GET)
public String showUsers(Model model,
@CurrentUser UserDetails userDetails) {
model.addAttribute("user",
userManagement.getUser(userDetails.getId());
return "user";
}
Similar to the plain type based WebArgumentResolver
implementation for Pageable
the implementation here would check for the custom CurrentUser
annotation and then access e.g. Spring Security API to access the currently logged in user. The additional annotation is “necessary” in this case, as one could also have a UserDetails
used as ModelAttribute
, but that depends on the detailed requirements of your application.
WebArgumentResolver
allows providing your own custom controller method parameters to centralize either lookup or extraction logic, that would otherwise be scattered through your controller code.