This recipe explains how to configure Spring MVC for REST handlers to be as integrated as possible with their business domain. We focus on designing self-explanatory method handlers, externalized type conversions, and abstracted response marshalling (serialization to specific formats such as json
, xml
, csv
, and so on).
We are going to review the configuration changes applied to the cloudstreetmarket-api
webapp in order to set up a Type conversion from either a request parameter or a URI template variable.
We will see how to configure automatic marshalling (for responses) into json
. We will focus on two very simple method handlers created for this chapter.
The following steps describe the codebase changes that relate to the request binding and the response marshalling configuration:
v4.x.x
. Then run a maven clean install
command on the cloudstreetmarket-parent
module. To do this, right-click on the module, select Run as… | Maven Clean, then select Run as… | Maven Install again. After this, select Maven Update Project to synchronize Eclipse with the Maven configuration. To do so, right-click on the module and then select Maven | Update Project….dispatcher-context.xml
file (in the cloudstreetmarket-api module). The RequestMappingHandlerAdapter
bean has been defined the three webBindingInitializer
, messageConverters
and customArgumentResolvers
properties:<bean class="org.sfw.web... method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer"> <bean class="org.sfw... support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService"/> </bean> </property> <property name="messageConverters"> <list> <ref bean="jsonConverter"/> </list> </property> <property name="customArgumentResolvers"> <list> <bean class="net.kaczmarzyk.spring.data.jpa.web. SpecificationArgumentResolver"/> <bean class="org.sfw.data.web.PageableHandlerMethodArgumentResolver"> <property name="pageParameterName" value="pn"/> <property name="sizeParameterName" value="ps"/> </bean> </list> </property> <property name="requireSession" value="false"/> </bean> <bean id="jsonConverter" class="org.sfw... converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes" value="application/json"/> <property name="objectMapper"> <bean class="com.fasterxml.jackson. databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm"/> </bean> </property> </bean> </property> </bean> <bean id="conversionService" class="org.sfw.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="edu.zc.csm.core. converters.StringToStockProduct"/> </list> </property> </bean>
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2</version> </dependency> <dependency> <groupId>net.kaczmarzyk</groupId> <artifactId>specification-arg-resolver</artifactId> <version>0.4.1</version> </dependency>
CloudstreetApiWCI
, the allowDateBinding
method has been created with an @InitBinder
annotation:private DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); @InitBinder public void allowDateBinding ( WebDataBinder binder ){ binder.registerCustomEditor( Date.class, new CustomDateEditor( df, true )); }
getHistoIndex()
method in IndexController
:@RequestMapping(value="/{market}/{index}/histo", method=GET) public HistoProductDTO getHistoIndex( @PathVariable("market") MarketCode market, @PathVariable("index") String indexCode, @RequestParam(value="fd",defaultValue="") Date fromDate, @RequestParam(value="td",defaultValue="") Date toDate, @RequestParam(value="i",defaultValue="MINUTE_30") QuotesInterval interval){ return marketService.getHistoIndex(indexCode, market, fromDate, toDate, interval); }
cloudstreetmarket-api
module and restart the server. To do so, start by right-clicking on the Tomcat server in the Servers tab:http://localhost:8080/api/indices/EUROPE/^GDAXI/histo.json
.getHistoIndex
method handler and produces the following json
output:StockProductController
. It hosts the following method handler:@RequestMapping(value="/{code}", method=GET) @ResponseStatus(HttpStatus.OK) public StockProductOverviewDTO getByCode( @PathVariable(value="code") StockProduct stock){ return StockProductOverviewDTO.build(stock); }
StringToStockProduct
converter must be presented because it was required to achieve the previous step:@Component public class StringToStockProduct implements Converter<String, StockProduct> { @Autowired private ProductRepository<StockProduct> productRepository; @Override public StockProduct convert(String code) { StockProduct stock = productRepository.findOne(code); if(stock == null){ throw new NoResultException("No result has been found for the value "+ code +" !"); } return stock; } }
http://localhost:8080/api/products/stocks/NXT.L.json
. This should target the presented getByCode
handler and produce the following json
response:To understand how the preceding elements work together, we must introduce the key role of RequestMappingHandlerAdapter
.
We briefly introduced RequestMappingHandlerAdapter
in Chapter 2, Designing a Microservice Architecture with Spring MVC. This bean implements the high-level HandlerAdapter
interface, which allows custom MVC core-workflow implementations. RequestMappingHandlerAdapter
is the native implementation that comes with the framework.
We mentioned that RequestMappingHandlerAdapter
and RequestMappingHandlerMapping
respectively are two replacement classes for the now deprecated AnnotationMethodHandlerAdapter
and DefaultAnnotationHandlerMapping
.
In fact, RequestMappingHandlerAdapter
provides better centralization for all the method handlers. Also, some new capabilities have been opened for HandlerInterceptors
and HandlerExceptionResolver
.
Practically, the handler argument that can be found in the preHandle
, postHandle
, and afterCompletion
methods’ signature (WebContentInterceptors
) can be casted into HandlerMethod
objects. The HandlerMethod
Type offers interesting examination methods such as getReturnType
, getMethodAnnotation
, getMethodParameters
.
Also, in regard to RequestMappingHandlerAdapter
and RequestMappingHandlerMapping
, the Spring documentation specifies that:
"The new support classes are enabled by default by the MVC namespace and the MVC Java config but must be configured explicitly if using neither." | ||
--JavaDoc |
In both our web apps, we make use of the MVC namespace specifically with the <mvc:annotation-driven/>
element.
This element is enjoyable from the configuration-by-default feature it activates on a couple of web features. However, in a lot of situations, different behaviors might still be expected.
In most cases, custom definitions are made either on the namespace itself or on with RequestMappingHandlerAdapter
.
The main role of RequestMappingHandlerAdapter
is to provide support and customization for handlers of the Type HandlerMethod
. These handlers are bound to @RequestMapping
annotations.
"A HandlerMethod object encapsulates information about a handler method consisting of a method and a bean. Provides convenient access to method parameters, the method return value, method annotations." | ||
--JavaDoc |
The RequestMappingHandlerAdapter
gets most of its support methods from the historical DefaultAnnotationHandlerMapping
. Let's take a closer look at the methods that particularly interest us.
The
messageConverters
template can be registered through the setMessageConverters
setter as List<HttpMessageConverter>
. Spring will perform the unmarshalling of an HTTP request's body for us into Java object(s) and the marshalling of a Java resource into an HTTP response's body.
It is important to remember that the framework provides converter implementations for the main media types. These are registered by default with RequestMappingHandlerAdapter
and RestTemplate
(on the client side).
The following table summarizes the native converters we can make use of:
Have a look at the following address to get information on remoting and web services using Spring: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/remoting.html.
In our application, we have overridden the definition of the two native MappingJackson2HttpMessageConverter
and MarshallingHttpMessageConverter
classes.
The setCustomArgumentResolvers
setter provides to RequestMappingHandlerAdapter
a support for custom arguments. If you remember back in Chapter 2, Using Spring MVC to Support Responsive Designs, the very first recipe talks about supported annotations for arguments. At the time, we saw @PathVariable
, @MatrixVariable
, @RequestBody
, @RequestParam
, and so on.
All these annotations are built-in ArgumentResolver
. They are mapped to registered implementations to externally prepopulate arguments from different sources.
We have the possibility to define our own annotations and prepopulate our method arguments following the required business logic. These resolvers must implement the HandlerMethodArgumentResolver
interface.
The development of our application didn't specifically require the development of customArgumentResolver
. However, we have registered two of them:
net.kaczmarzyk.spring.data.jpa.web.SpecificationArgumentResolver
: This resolver is a third-party library that we are going to explain in the 3rd recipe of this chapterorg.springframework.data.web.PageableHandlerMethodArgumentResolver
: This will allow the automatic resolution of pagination parameters in order to use the native Spring Data pagination supportA
WebBindingInitializer
interface is a callback interface to globally initialize WebDataBinder
and perform data binding in the context of web requests.
Before going forward, we must stop and revisit the 4th step of the recipe that defined the following method:
@InitBinder public void allowDateBinding(WebDataBinder binder){ binder.registerCustomEditor(Date.class, new CustomDateEditor( df, true )); }
We define this method in a controller to register an abstracted Date conversion binding using a PropertyEditor
.
Now let's focus on the WebDataBinder
argument. In this section, we are talking about the initialized part. The WebDataBinder
interface provides a couple of interesting methods. These methods are mostly validation-related (validate
, setRequiredFields
, isAllowed
, getErrors
, and so on) and conversion-related (getTypeConverter
, registerCustomEditor
, setBindingErrorProcessor
, getBindingResult
, and so on).
A WebDataBinder
argument can also be set as a ConversionService
object. Rather than doing this locally in our allowDateBinding
method (with the WebDataBinder.setConversion
setter), we are going to use a global and declarative initialization.
The
WebBindingInitializer
implementation we have chosen is the Spring ConfigurableWebBindingInitializer
bean. It is indeed a convenient class for declarative configurations in a Spring application context. It enables the reusability of preconfigured initializers over multiple controllers/handlers.
In our case, the WebBindingInitializer
will be useful to globally initialize registered Type converters such as StringToStockProduct
, but also to achieve the global exception handling we are aiming for.
The 11th step defines a StringToStockProduct
converter that allows the definition of a lean and clean
getByCode
method handler:
@RequestMapping(value="/{code}", method=GET) @ResponseStatus(HttpStatus.OK) public StockProductOverviewDTO getByCode( @PathVariable(value="code") StockProduct stock){ return StockProductOverviewDTO.build(stock); }
These converters can be used broadly among the Spring application for any conversion without being restricted to a request scope. Their use of Generics can be very beneficial. They are bound to a conversionService
bean and there is no specific way to avoid their individual declaration.
The
PropertyEditors
and the converters from ConversionService
might appear as an alternative to each other in their String-to-type use.
Spring heavily uses the concept of PropertyEditors
to set properties for beans. In Spring MVC, they are meant to parse HTTP requests. Their declaration in Spring MVC is bound to the request scope.
Even if they can be initialized globally, you must see PropertyEditors
as initially restricted scope elements. Seeing them this way legitimates their attachment to @InitBinder
methods and WebBinderData
. They are less generic than converters.
When using PropertyEditors
for enums, Spring offers a naming convention that can avoid the individual declaration of enums. We will make use of this handy convention later on.
We are going to look at other RequestMappingHandlerAdapter
properties in the next recipes. For now, there is more to discuss about PropertyEditors
and especially the built-in ones.
The following
PropertyEditors
implementations come natively with Spring. They can be applied manually in all controllers for the purpose of binding. You will probably recognize CustomDateEditor
, which has been registered in CloudstreetApiWCI
.
Find more details about Type conversion and PropertyEditors
in the Spring IO Reference document, check out: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/validation.html.