Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface


A familiar problem when implementing a REST server, is that your framework limits what you can do. Sometimes, this problem can be mitigated (not solved) by reformulating (i.e. changing) your business needs. For example, you can reformulate save multiple changes to an invoice and its invoice lines with one click on a save button to save each of multiple changes to an invoice and its invoice lines as separate UI actions. Unfortunately, this change increases your audit trail and thus technology trumps business needs.


Another limit is that conversions have no context. This makes the conversions easier, but also makes functionality like partial updates impossible. That is, it is not possible to that a request body can contain any subset of the changeable properties of a resource, and that all properties that are left out are left at their current values. The default Spring implementation, for example, treats undefined properties as set to their default value. Our goal was to change that to keep the current value in the database.


In this in-depth technical article, I will show you how the new Spring 3.1 interface HandlerMethodArgumentResolver can be used to remove such framework limits. The result is that your business needs are again in control, not the technology.


Background



Spring MVC 3.0 supports REST using the annotation @RequestMapping. This annotation is also used to map normal web application requests to a method of an @Controller bean. With @RequestMapping you can specify the URL being mapped and the HTTP method that a controller method understands. You can also use the annotations @PathVariable and @RequestParam to extract URL templates and request parameters from requests, respectively. The pièce de résistance for REST are the annotations @RequestBody and @ResponseBody, which map Java classes from the request body, respectively to the response body.


If you look at the code, you will see that the translation from the request to the invocation of the controller method is done using a big if/else statement in the class HandlerMethodInvoker (used in AnnotationMethodHandlerAdapter). Not the prettiest solution, and nearly impossible to extend. You can augment some functionality by using AOP, but nothing more.



Spring 3.1



Spring 3.1 will improve this by using a new class, RequestMappingHandlerAdapter, which implements a strategy pattern using the interface HandlerMethodArgumentResolver. The old/current implementation, AnnotationMethodHandlerAdapter, will remain for backward compatibility.


The core of the new implementation (at least for developers) is the interface HandlerMethodArgumentResolver. This interface has two methods: supportsParameter(MethodParameter) to check if a method parameter is supported, and resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory) to actually resolve the method parameter from the request. Default implementations exist for all supported parameters of @RequestMapping annotated methods.


The class RequestMappingHandlerAdapter checks all method parameters against its lists of custom and default HandlerMethodArgumentResolver instances, and the first one that says it supports the method parameter will be used to resolve it.


This new interface gives us some very nice opportunities. I will explain some of them by demonstrating how easy it is to implement your own HandlerMethodArgumentResolver. The examples will show how to extend the REST framework provided by Spring, including adjusting the behavior of the existing Spring implementations.


The Spring version we are using is the latest and greatest, so first we need to add the Spring snapshot repository to our pom.xml:

        <repository>
<id>org.springframework.maven.snapshot</id>
<name>Spring Maven Snapshot Repository</name>
<url>http://maven.springframework.org/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>

And finally, we will need to adjust the Spring version of our dependencies to: 3.1.0.BUILD-SNAPSHOT



First example: ValidatingRequestBodyMethodArgumentResolver



One problem found when implementing a REST server, is that of input validation. Especially for objects, it is common to want to use the JSR-303 @Valid annotation to ensure your input is valid. Spring currently has an open bug, SPR-6709, which states that the @Valid annotation on an @RequestBody parameter does not work. Recent activity indicates it will not be with us much longer, but it is a good example to extend existing functionality.


Below is a class we have implemented to solve this problem. It delegates the translation of the request body to the original class, RequestResponseBodyMethodProcessor, and then validates the result if needed:

package nl.t42.spring31;

import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;

import nl.t42.generic.ValidationException;


/**
* A that delegates to
* ,
* but also ensured that the request body is validated.
*
* Currently, this class only supports (and assumes) a request content type of application/json.
*/
public class ValidatingRequestBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
@Autowired
private Validator validator;

@Override
public boolean supportsParameter(MethodParameter parameter) {

return getRequestResponseBodyMethodProcessor().supportsParameter(parameter);
}

private RequestResponseBodyMethodProcessor getRequestResponseBodyMethodProcessor() {

if (requestResponseBodyMethodProcessor == null) {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
requestResponseBodyMethodProcessor = new RequestResponseBodyMethodProcessor(messageConverters);
}
return requestResponseBodyMethodProcessor;
}

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

Object value = getRequestResponseBodyMethodProcessor()
.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
if (parameter.hasParameterAnnotation(Valid.class)) {
validateObject(value);
}
return value;
}

private void validateObject(Object value) {

Set<ConstraintViolation<Object>> violations = validator.validate(value);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
}
}

In this class, the delegation is clearly visible. The injected dependency to RequestMappingHandlerAdapter seems unfortunate, but it handles the underlying HttpMessageConverter implementations much better than we could do. Finally, the exception class ValidationException translates the JSR-303 constraint violations into a form we want.


Lastly, we need to configure Spring MVC to use our class. We use an XML configuration, which looks like this:

    <mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="nl.t42.spring31.ValidatingRequestBodyMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>

The end result is that @Valid and @RequestBody play nice together.



Second example: PathEntityMethodArgumentResolver



Another thing we encounter regularly, is that we want to issue a POST request to a URL to execute an action on a domain object. As we prefer not to repeat the code to retrieve the domain object, we’ve created an annotation @PathEntity so we can do this:


@RequestMapping(value = "/", method = RequestMethod.POST, params = "action=approve")
@ResponseBody
public Invoice approveInvoice(@PathEntity("invoiceId") Invoice invoice) {
// ...
}

Granted, we could also do this by overriding the Spring ConversionService, but as we’ll see in the next example this approach has it’s advantages. The code for the @PathEntity annotation and the PathEntityMethodArgumentResolver to handle it is this:

package nl.t42.spring31;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
* Annotation to indicate a method parameter should be bound to a URL template, and retrieved as a domain entity.
* Used for annotated methods.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathEntity {

/**
* The name of the URL template that contains the
* .
*
* @return the name of the URL template that is the entity id
*/
String value();
}

And this is the class to resolve parameters annotated with @PathEntity:

package nl.t42.spring31;

import java.util.Map;

import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;

import nl.t42.generic.EntityNotFoundException;
import nl.t42.generic.DomainEntity;


/**
* A that converts a HTTP request body to a , provided that
* the handler method argument is annotated with .
*/
public class PathEntityMethodArgumentResolver implements HandlerMethodArgumentResolver {

/**
* A conversion service to convert primary key values.
*/
@Autowired
private ConversionService conversionService;
/**
* The entity manager used to retrieve the entities.
*/
@Autowired
private EntityManager entityManager;


@Override
public boolean supportsParameter(MethodParameter parameter) {

return parameter.hasParameterAnnotation(PathEntity.class) && isADomainEntity(parameter);
}


/**
* Determine if a method parameter is a DomainEntity.
*
* @param parameter a method parameter
* @return if the type of the parameter is (a subclass of) DomainEntity
*/
protected boolean isADomainEntity(MethodParameter parameter) {

return DomainEntity.class.isAssignableFrom(parameter.getParameterType());
}


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

String primaryKeyName = getPrimaryKeyName(parameter);

Long entityId = findPrimaryKeyValue(webRequest, primaryKeyName);
return findEntity(parameter, entityId);
}


/**
* Get the nam of the URL template that holds the primary key.
*
* @param parameter the method parameter
* @return the name of a URL template
*/
protected String getPrimaryKeyName(MethodParameter parameter) {

return parameter.getParameterAnnotation(PathEntity.class).value();
}


private Long findPrimaryKeyValue(NativeWebRequest webRequest, String primaryKeyName) {

Map<String, String> pathVariables = getPathVariables(webRequest);

String primaryKeyValue = pathVariables.get(primaryKeyName);

if (primaryKeyValue == null) {
throw new IllegalStateException(String.format("The path variable %s cannot be resolved.", primaryKeyName));
}

return conversionService.convert(primaryKeyValue, Long.class);
}


private Map<String, String> getPathVariables(NativeWebRequest webRequest) {

HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return (Map<String, String>) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
}


private DomainEntity findEntity(MethodParameter parameter, Long entityId) {

Class<? extends DomainEntity> entityType = (Class<? extends DomainEntity>) parameter.getParameterType();

DomainEntity entity = entityManager.find(entityType, entityId);
if (entity == null) {
throw new EntityNotFoundException(
String.format("Cannot find a %s with id=%d", entityType.getSimpleName(), entityId));
}

return entity;
}
}

Registering this MethodHandlerArgumentResolver is as usual:

    <mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="nl.t42.spring31.PathEntityMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>

With these two classes in place, we can identify our domain objects with our REST resources (URL’s). When the controller method is invoked, we have an instance of the domain entity the URL represents. Note that outside a transaction, this entity is a detached entity (this is prescribed by the JPA specification). So we still need to call EntityManager.merge(Object) to persist any changes.



Third example: RequestEntityMethodArgumentResolver



For CRUD operations, a PUT request to a URL is common and usually overwrites the entire object. In our case however, this is not possible: some object properties are read-only, and others can only be written if the object has a certain status. So what we need is a _partial_ update.


And although it is usually possible to omit fields from the request body, this does not do what we want. What happens is for missing fields, use the default value from the constructor. But what we want is: for missing fields, to not change the value in the database.


We’ve created an annotation @RequestEntity to handle these parameters. Like @PathEntity, a URL template is tied to a domain entity, but now the request body is used to update the (detached) record. An example usage would be:


@RequestMapping(value = "/", method = RequestMethod.PUT)
@ResponseBody
public Invoice updateInvoice(@RequestEntity("invoiceId") Invoice invoice) {
// ...
}

This controller method now only has to call the service method that updates the database. The code looks like this:

package nl.t42.spring31;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
* Annotation to indicate a method parameter should be bound to the web request body. Used for {@link
* org.springframework.web.bind.annotation.RequestMapping @RequestMapping} annotated methods.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestEntity {

/**
* The name of the URL template that contains the .
*
* @return the name of the URL template that is the entity id
*/
String value();
}

The code to handle this annotation extends our PathEntityMethodArgumentResolver from the previous example:

package nl.t42.spring31;

import java.io.IOException;
import java.io.Reader;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;

import nl.t42.generic.ParseException;
import nl.t42.generic.ValidationException;
import nl.t42.generic.DomainEntity;


/**
*

A that converts a HTTP request body to a , provided
* that the handler method argument is annotated with .


*
*

Currently, this class only supports (and assumes) a request content type of application/json.


*/
public class RequestEntityMethodArgumentResolver extends PathEntityMethodArgumentResolver {



/**
* The Jackson object mapper (JSON support).
*/
@Autowired
private ObjectMapper objectMapper;
/**
* The JSR-303 validator.
*/
@Autowired
private Validator validator;



@Override
public boolean supportsParameter(MethodParameter parameter) {



return parameter.hasParameterAnnotation(RequestEntity.class) && isADomainEntity(parameter);
}



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



DomainEntity entity = super.resolveArgument(parameter, mavContainer, webRequest, binderFactory);



Reader requestBody = getRequestBody(webRequest);
updatePartially(entity, requestBody);



if (parameter.hasParameterAnnotation(Valid.class)) {
validateObject(entity);
}
return entity;
}



@Override
protected String getPrimaryKeyName(MethodParameter parameter) {



return parameter.getParameterAnnotation(RequestEntity.class).value();
}



private Reader getRequestBody(NativeWebRequest webRequest) throws IOException {



HttpServletRequest httpRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return httpRequest.getReader();
}



private void updatePartially(Object entity, Reader requestBody) {



try {
objectMapper.updatingReader(entity).readValue(requestBody);
} catch (IOException e) {
throw new PostduifParseException(e.getMessage(), e);
}
}



/**
* Validate an object.
*
* @param object the object to validate
* @throws PostduifValidationException when the object fails validation
*/
private void validateObject(Object object) {



Set<ConstraintViolation<Object>> violations = validator.validate(object);
if (!violations.isEmpty()) {
throw new PostduifValidationException(violations);
}
}
}




CRUD applications



The provided examples show the building blocks to reduce a CRUD application to almost no code beyond searching:


Create
The @RequestBody annotation combined with @Valid (see the first example) ensure that the controller only needs to save the record.

Read
The @PathEntity annotation in the second example already provides a loaded record, so the controller can simply return it. Only returning multiple records (i.e. searches) requires more code.

Update
The @RequestEntity annotation in the third example provides a detached, updated and optionally validated record. This includes partial updates The controller method only has to save it.

Delete
The @PathEntity annotation provides a record, so it can simply be removed.


As you can see, this means that implementing CRUD has become as trivial as calling your persistence layer. And as an added bonus, the more complex use cases are now also easier to implement: only the call to your business method is needed.



Conclusion



The examples in this article show that it is easy to extend the default Spring functionality to extract any information you want from the request. You can augment the existing functionality with, for example, JSR-303 validations. Or you can completely roll your own solution, including merging the request information with existing data. Combined with the generic Spring concept that any REST call is a method invocation, anything is possible. Your business needs are in control, not the technology.

1 comment:

  1. Anonymous9/10/19 09:36

    Spring currently has an open bug, SPR-6709, which states that the @Valid annotation on an @RequestBody parameter does not work. Recent activity indicates it will not be with us much longer, but it is a good example to extend existing functionality. 212-89 exam dumps

    ReplyDelete