@RequestBody and @ResponseBody annotations are used to bind the HTTP request/response body with a domain object in method parameter or return type. Behind the scenes, these annotation uses HTTP Message converters to convert the body of HTTP request/response to domain objects.
@RequestBody
If a method parameter is annotated with @RequestBody, Spring will bind the incoming HTTP request body(for the URL mentioned in @RequestMapping for that method) to that parameter. While doing that, Spring will [behind the scenes] use HTTP Message converters to convert the HTTP request body into domain object [deserialize request body to domain object], based on Accept header present in request.
- The Accept header is used by HTTP clients [browsers] to tell the server what content types they’ll accept.
- The server sends back the response, which will include a Content-Type header telling the client what the content type of the returned content actually is. In case of POST or PUT request, browsers do send data in request, so they actually send content-type as well.
Take this method for example:
@RequestMapping (value= "/user/create" , method=RequestMethod.POST) public ResponseEntity<Void> createUser( @RequestBody User user, UriComponentsBuilder ucBuilder){ System.out.println( "Creating User " +user.getName()); if (userService.isUserExist(user)){ System.out.println( "A User with name " +user.getName()+ " already exist" ); return new ResponseEntity<Void>(HttpStatus.CONFLICT); } userService.saveUser(user); HttpHeaders headers = new HttpHeaders(); headers.setLocation(ucBuilder.path( "/user/{id}" ).buildAndExpand(user.getId()).toUri()); return new ResponseEntity<Void>(headers, HttpStatus.CREATED); } |
This is the controller method to handle typical HTTP Post request [for URL /user/create]. In a pure REST oriented approach, this controller method creates a user, and returns the HTTP 201[CREATED] along with a LocationHeader containing the locations of newly created user [/app-address/user/1 e.g].
Now back to our original discussion, HTTP Post request body contains the detail of user to be created. When a client sends a request [/user/create] to create a user, it will be intercepted in this method. Method parameter user is marked with @RequestBody annotation. Thanks to this annotation, Spring will try to bind the request body [which can be JSON/XML/Other] to user object[ Means crating a new user object with the details found in the request body like user name,age etc..], based on Content-Type header in Http request.
But Spring need help to convert the request body into user object. It needs a converter which can convert the data in HTTP request body [which can be JSON/XML/Other] into user object.
Spring provides out-of-box many default HttpMessageConverters, which will be used for conversion, depending on presence of certain library in project classpath.
For example, if the Content-Type in request Header was one of application/json or application/xml , that means the POST body contains json or XML[Popular formats], and if Jackson library is found in your classpath, Spring will delegate the conversion to MappingJackson2HttpMessageConverter [for json] or MappingJackson2XmlHttpMessageConverter [for xml].
To declare a dependency to Jackson library (jackson-databind) include following dependency in your pom.xml
< dependency > < groupId >com.fasterxml.jackson.core</ groupId > < artifactId >jackson-databind</ artifactId > < version >${jackson.version}</ version > </ dependency > |
ResponseEntity (used in above example) represents the entire HTTP response. Good thing about it is that you can control anything that goes into it. You can specify status code, headers, and body. Next post goes into details of it with a fully working example.
@ResponseBody
If a method is annotated with @ResponseBody, Spring will bind the return value to outgoing HTTP response body. While doing that, Spring will [behind the scenes] use HTTP Message converters to convert the return value to HTTP response body [serialize the object to response body], based on Content-Type present in request HTTP header.
Take this method for example:
@RequestMapping (value = "/user/all" , method = RequestMethod.GET) public @ResponseBody List<User> listAllUsers() { return userService.findAllUsers(); } |
This is the controller method to handle typical HTTP GET request [for URL /user/all] to retrieve all users. In this case, Spring will convert the user list into appropriate format [JSON/XML/Other] using available converters, based on content type.
NOTE : As from Spring 4, @RestController is the preferred way to achieve the same functionality earlier provided by @ResponseBody. Under the hood, @RestController is @Controller+@ResponseBody, and it avoids the need of prefixing every method with @ResponseBody. Next post goes into details with a full working example.
Default HttpMessageConverters
Spring provides out of box following Http message converters which implements HttpMessageConverter interface [Credit : Spring Reference].
- StringHttpMessageConverterAn HttpMessageConverter implementation that can read and write Strings from the HTTP request and response. By default, this converter supports all text media types ( text/*), and writes with a Content-Type of text/plain.
- FormHttpMessageConverterAn HttpMessageConverter implementation that can read and write form data from the HTTP request and response. By default, this converter reads and writes the media type application/x-www-form-urlencoded. Form data is read from and written into a MultiValueMap
. - ByteArrayHttpMessageConverterAn HttpMessageConverter implementation that can read and write byte arrays from the HTTP request and response. By default, this converter supports all media types ( */*), and writes with a Content-Type of application/octet-stream. This can be overridden by setting the supportedMediaTypes property, and overriding getContentType(byte[]).
- MarshallingHttpMessageConverterAn HttpMessageConverter implementation that can read and write XML using Spring’s Marshaller and Unmarshaller abstractions from the org.springframework.oxm package. This converter requires a Marshaller and Unmarshaller before it can be used. These can be injected via constructor or bean properties. By default this converter supports ( text/xml) and ( application/xml).
- MappingJackson2HttpMessageConverterAn HttpMessageConverter implementation that can read and write JSON using Jackson’s ObjectMapper. JSON mapping can be customized as needed through the use of Jackson’s provided annotations. When further control is needed, a custom ObjectMapper can be injected through the ObjectMapper property for cases where custom JSON serializers/deserializers need to be provided for specific types. By default this converter supports ( application/json).
- MappingJackson2XmlHttpMessageConverterAn HttpMessageConverter implementation that can read and write XML using Jackson XML extension’s XmlMapper. XML mapping can be customized as needed through the use of JAXB or Jackson’s provided annotations. When further control is needed, a custom XmlMapper can be injected through the ObjectMapper property for cases where custom XML serializers/deserializers need to be provided for specific types. By default this converter supports ( application/xml).
- SourceHttpMessageConverterAn HttpMessageConverter implementation that can read and write javax.xml.transform.Source from the HTTP request and response. Only DOMSource, SAXSource, and StreamSource are supported. By default, this converter supports ( text/xml) and ( application/xml).
- BufferedImageHttpMessageConverterAn HttpMessageConverter implementation that can read and write java.awt.image.BufferedImage from the HTTP request and response. This converter reads and writes the media type supported by the Java I/O API.
Custom HttpMessageConverters
Most of the time, the default converters provided by Spring are enough. But if you need some custom behavior, you can roll out your own implementaion.
For instance, in above example, MappingJackson2HttpMessageConverter was used to handle JSON content. By default, the Jackson ObjectMapper supplied with this converter fails if there are missing properties in your JSON or domain object. It simply fails to convert. You can override this behavior by telling object mapper not to fail on missing properties, as shown below:
package com.websystique.springmvc.configuration; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @Configuration @EnableWebMvc @ComponentScan (basePackages = "com.websystique.springmvc" ) public class HelloWorldConfiguration extends WebMvcConfigurerAdapter{ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(mappingJackson2HttpMessageConverter()); } @Bean public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper( new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false )); return converter; } } |
Here we have overridden extendMessageConverters method which provides a hook to add your own validator, wihtout skipping all existing validators.
No comments:
Post a Comment