Spring provides a generic mechanism of converting HTTP Message body to/from Java objects.
Based on 'Content-Type' and 'Accept' of request header values, a handler method is first mapped.
If the handler method has @RequestBody in it's parameter list and/or @ResponseBody as it's return type, then a suitable implementation of HttpMessageConverter is selected depending on media type of request or response.
What is HttpMessageConverter?
HttpMessageConverter is a strategy interface that can convert HTTP request body to an object type or the an object type to HTTP response body.
By default Spring supports various HttpMessageConverter and more can be discovered if they are in class path. In this tutorial we are going to show examples of the followings:
- ByteArrayHttpMessageConverter converts byte arrays.
- StringHttpMessageConverter converts strings.
- FormHttpMessageConverter converts form data to/from a MultiValueMap<String, String>
Byte Array and String conversions
@Controller
@RequestMapping("message")
public class MyController {
@RequestMapping(consumes = MediaType.TEXT_PLAIN_VALUE,
produces = MediaType.TEXT_HTML_VALUE,
method = RequestMethod.GET)
@ResponseBody
public String handleRequest (@RequestBody byte[] bytes, @RequestBody String str) {
System.out.println("body in bytes: "+bytes);
System.out.println("body in string: "+str);
return "<html><body><h1>Hi there</h1></body></html>";
}
}
Let's test above controller with JUnit test. (more about Spring MVC JUnit tests)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class RegistrationControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup () {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
@Test
public void testController () throws Exception {
ResultMatcher ok = MockMvcResultMatchers.status()
.isOk();
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/message")
.contentType(MediaType.TEXT_PLAIN)
.content("test message");
this.mockMvc.perform(builder)
.andExpect(ok)
.andDo(MockMvcResultHandlers.print());
}
}
Output:
body in bytes: [B@20b2475a
body in string: test message
MockHttpServletRequest:
HTTP Method = GET
Request URI = /message
Parameters = {}
Headers = {Content-Type=[text/plain]}
Handler:
Type = com.logicbig.example.MyController
Method = public java.lang.String com.logicbig.example.MyController.handleRequest(byte[],java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[text/html], Content-Length=[43]}
Content type = text/html
Body = <html><body><h1>Hi there</h1></body></html>
Forwarded URL = null
Redirected URL = null
Cookies = []
Form POST data conversion
The content-type of form data is application/x-www-form-urlencoded:
@Controller
@RequestMapping("message")
public class MyController {
@RequestMapping(method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseStatus(HttpStatus.OK)
public void handleFormRequest(@RequestBody MultiValueMap<String, String> formParams) {
System.out.println("form params received " + formParams);
}
}
What is @ResponseStatus?
Marks a method or exception class with the status code() and reason() that should be returned. The status code is applied to the HTTP response when the handler method is invoked and overrides status information set by other means, like ResponseEntity or "redirect:".
The Unit test:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class RegistrationControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup () {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
@Test
public void testFormPostController () throws Exception {
ResultMatcher ok = MockMvcResultMatchers.status()
.isOk();
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.post("/message")
.contentType("application/x-www-form-urlencoded")
//use param instead of content
.param("name", "joe");
this.mockMvc.perform(builder)
.andExpect(ok);
}
}
Output:
form params received {name=[joe]}
Form PUT data conversion
@Controller
@RequestMapping("message")
public class MyController {
@RequestMapping(method = RequestMethod.PUT,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public void handleFormPutRequest(@RequestBody MultiValueMap<String, String> formParams){
System.out.println("form params received " + formParams);
}
}
The JUnit test:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class RegistrationControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup () {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
@Test
public void testFormPutController () throws Exception {
ResultMatcher created = MockMvcResultMatchers.status()
.isCreated();
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.put("/message")
.contentType("application/x-www-form-urlencoded")
.content("name=mike");
this.mockMvc.perform(builder)
.andExpect(created);
}
}
Output:
form params received {name=[mike]}
What's Next?
In next tutorials we are going to demonstrate how to use @RequestBody and @ResponseBody to convert request/response body data to backing objects. The commonly used data types are XML and JSON. We can always write our own HttpMessageConverter for other data types. Also note that any data type can always be mapped to String or byte[].
FormHttpMessageConverter (which is used for content type application/x-www-form-urlencoded) cannot bind to backing Objects using @RequestBody and @ResponseBody. In this case we should always use either Spring implicit DataBinder approach or @ModelAttribute. If we still want to use @RequestBody or @ResponseBody then we should use MultiValueMap (as we showed above) instead of a backing object.
Example ProjectDependencies and Technologies Used: - Spring Web MVC 4.2.4.RELEASE: Spring Web MVC.
- Spring TestContext Framework 4.2.4.RELEASE: Spring TestContext Framework.
- Java Servlet API 3.0.1
- JUnit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
- JDK 1.8
- Maven 3.0.4
|