Trifork Blog

Use immutable objects in your Spring MVC controller by implementing your own WebArgumentResolver

December 8th, 2011 by
|

How flexible is Spring MVC in combination with immutable objects? Why don’t we want Spring MVC decide for us how to build our objects used for binding? Curious how we tackled this problem? Read on!

In our current project we are using Spring MVC 3 to build our frond-end.
The binding mechanism of Spring MVC is very powerfull and flexible.
For example Spring MVC will automaticly bind fields from the request to the object you are using in your controller. But binding fields from the request to an object will only work when the class contains getters and setters.

An example
Our straightforward mutable address class:

public class MutableAddress {

    private String street;
    private String houseNumber;
    private String postalCode;
    private String city;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

   // Getters and setters for all other fields
}

The handler method on the controller takes a MutableAddress object (for example populated with data from the form) and saves it.

@Controller
public class AddressController {

   @RequestMapping(value = "/add", method = POST)
   public String storeAddress(MutableAddress mutableAddress) {
	addressService.storeAddress(mutableAddress);
	return "redirect:/overview";
   }
}

This all works fine but in our case we use immutable objects by design. So we don’t have any setters on our address class. The idea behind an immutable object is once it is created it will contain the correct values and cannot be changed anymore. So Spring MVC has some influence on the objects that we are using for binding data, but there is a way we can avoid that.

This is how the immutable version of our address class looks like:

public class ImmutableAddress {

    private final String street;
    private final String houseNumber;
    private final String postalCode;
    private final String city;

    public ImmutableAddress(String street, String houseNumber, String postalCode, String city) {    
        Assert.hasText(street, "'street' must contain text");
        Assert.hasText(houseNumber, "'houseNumber' must contain text");
        Assert.hasText(postalCode, "'postalCode' must contain text");
        Assert.hasText(city, "'city' must contain text");

        this.street = street;
        this.houseNumber = houseNumber;
        this.postalCode = postalCode;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

   // All getters for the other fields. We don’t have setters!
}

The ImmutableAddress doesn’t contain a default constructor and can only be instantiated by passing in all parameters. Note that all fields are final and again we don’t have any setters! So out of the box using the ImmutableAddress in the method of our controller will not work.

Because we want to use ImmutableAddress directly in our controller method we have to do the binding part ourself. The best way to do this is to write a custom WebArgumentResolver.

public class ImmutableAddressWebArgumentResolver implements WebArgumentResolver {

    @Override
    public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
        if (ImmutableAddress.class.equals(methodParameter.getParameterType())) {
            ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
            HttpServletRequest request = servletWebRequest.getRequest();
            
            String street = request.getParameter("street");
            String houseNumber = request.getParameter("houseNumber");
            String postalCode = request.getParameter("postalCode");
            String city = request.getParameter("city");
            return new ImmutableAddress(street, houseNumber, postalCode, city);
        }

        return WebArgumentResolver.UNRESOLVED;
    }
}

As you can see it’s quite easy. The only thing to do is implement a single method called “resolveArgument”. The implementation pulls out the parameters from the request and constructs a new ImmutableAddress that will be returned. This is only done when a ImmutableAddress is used as a parameter in one of our controller methods. In all other cases our custom WebArgumentResolver can’t resolve the arguments for the request so WebArgumentResolver.UNRESOLVED will be returned.

We have only one step left and that is to make sure our custom resolver kicks in before the default one of Spring MVC does.

Define our ImmutableAddressWebArgumentResolver as a Spring bean

<!-- Our custom argumentResolver -->
    <bean id="afnameIndentificatieArgumentResolver"
          class="nl.dutchworks.blog.web.binding.ImmutableAddressWebArgumentResolver"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
          p:customArgumentResolver-ref="immutableAddressWebArgumentResolver">
</bean>

Now we can use the ImmutableAddress directly in our controller

@RequestMapping(value = "/add-immutable", method = POST)
public String storeAddress(ImmutableAddress mutableAddress) {
	addressService.storeAddress(mutableAddress);
	return "redirect:/overview";
}

Make sure you don’t use:

<mvc:annotation-driven />

in your Spring context because we configured the AnnotationMethodHandlerAdapter in the context to make use of the customArgumentResolver.

At the moment of writing Spring 3.1 RC1 is available and you are able to use the mvc namespace to register your custom argument resolver:

<mvc:annotation-driven>
	<mvc:argument-resolvers>
		<bean class="nl.dutchworks.blog.web.binding.ImmutableAddressWebArgumentResolver"/>
	</mvc:argument-resolvers>
</mvc:annotation-driven>

My conclusion: there is no need to let Spring MVC decide the way you should build your objects for binding. And you don’t have to because Spring MVC is flexible enough just use WebArgumentResolvers!

To dive a little bit deeper in the code download the complete example here.

3 Responses

  1. December 17, 2011 at 12:11 by Hans Desmet

    What if one or more properties of ImmutableAddress were not Strings, but ints or Dates ?
    request.getParameter gives you a String, so you have to try to cast
    If the cast does not succeed (the user typed a wrong value), I want to add an error to the BindingResult.
    Is this possible ?

  2. February 4, 2012 at 21:29 by Joris Kuipers

    If you want to use the regular data binding facilities incl. support for PropertyEditors or the newer Converters and Formatters and the validation support, I would probably choose a less strict definition of ‘immutable’ and have non-final fields and a (private or default visibility) default constructor on my ‘immutable’ classes that I want to use for form-backing objects.
    You can then simply tell Spring to use field-level binding so you don’t require setters. Just add this method to your controller:

    @InitBinder
    void initBinder(WebDataBinder binder) {
    binder.initDirectFieldAccess();
    }

    Although you now change the object technically speaking, it’s still immutable from an API point-of-view, which is often enough.
    Another benefit there would be that you can re-render wrong values with error messages when binding and/or validation fails, because the partial results are still available from your form-backing object. With a strictly immutable object that’s not the case anymore, you’d have to come up with another way to prevent the user from having to re-enter all the data after (s)he made a mistake.

  3. February 24, 2012 at 20:53 by Hans Desmet

    Hello Joris,
    Thanks for your reply.
    I known about the method initDirectFieldAccess, but it accesses direct fielss in the command object itself, not in an associated object.

    Example: you use a Client object as command object. The class is:
    class Client { // mutable entity class
    private String name;
    private Address address;

    }
    class Address { // immutable value object class
    private String street;
    }

    initDirectFieldAccess can access directly the field name, but not the fields in the class Address (like street)

    This is a pitty, because in DDD, entities (like Client) regulary have associated Value objects (like Address) and immutable Value objects have a lot of benefits.