Neglected classes in Spring – Part II


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.

Pagination

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.

Accessing the current user

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.

Summary

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.

Information and Links

Join the fray by commenting, tracking what others have to say, or linking to it from your blog.


Other Posts

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Thanks for the article!

But does it really work to have request mapping method signature with “Model model” without adding BindingResult result as well? As far as I remember, this will lead into a Spring exception… ?

/KYUSS

Hey /KYUSS,

that works fine. You actually have Model as method parameter on most of the handler methods mapped to a GET. Have you mixed that up with a parameter annotated with @ModelAttribute and the connected Errors instance being required to directly follow the parameter?

Regards,
Ollie

Using Model will pass your method the current implicit model with all the attributes.
@ModelAttribute will pass only the model attribute you specify.

In neither case you need to add a BindingResult nor Errors Object. Read the docs. It’s optional, only requirement is that if you do add a BindingResult/Errors in your parameters, it has to be right after you model object parameter, that’s it.