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 buttonto
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
No comments:
Post a Comment