In the last tutorial, we saw how content-negotiation works in Spring. In this example, we will utilize ServletPathExtensionContentNegotiationStrategy by using a path extension with the request. We are going to use XML media type. This strategy is enabled by default, we just need to use file extension to make use of it.
Example
Writing Controller
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping(produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public User getUserById (@RequestParam("id") long userId) {
//creating dummy user
User user = new User();
user.setId(userId);
user.setName("joe");
user.setEmailAddress("joe@example.com");
return user;
}
@RequestMapping
@ResponseBody
public String getUserStringById (@RequestParam("id") long userId) {
return "joe, id: "+userId;
}
}
@XmlRootElement
public class User implements Serializable {
private Long id;
private String name;
private String password;
private String emailAddress;
.............
}
Writing JUnit tests
In this test, we are not yet going to use xml extension.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class UserTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
.............
@Test
public void testUserRequest () throws Exception {
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.get("/user")
.param("id", "100");
this.mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andDo(MockMvcResultHandlers.print());
}
.............
}
Output
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user
Parameters = {id=[100]}
Headers = {}
Handler:
Type = com.logicbig.example.UserController
Method = public java.lang.String com.logicbig.example.UserController.getUserStringById(long)
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/plain;charset=ISO-8859-1], Content-Length=[12]}
Content type = text/plain;charset=ISO-8859-1
Body = joe, id: 100
Forwarded URL = null
Redirected URL = null
Cookies = []
The test request maps to the handler which returns String.
Now let's use xml file extension with the request:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class SuperUserTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
.............
@Test
public void testSuperUserWithExtension () throws Exception {
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.get("/super-user.xml")
.param("id", "200");
this.mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andDo(MockMvcResultHandlers.print());
}
}
Running the test
mvn -q test -Dtest=SuperUserTests#testSuperUserWithExtension
Note that, we still did not specify 'Accept' header with the test request.
Output
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user.xml
Parameters = {id=[100]}
Headers = {}
Handler:
Type = com.logicbig.example.UserController
Method = public com.logicbig.example.User com.logicbig.example.UserController.getUserById(long)
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=[application/xml]}
Content type = application/xml
Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><user><emailAddress>joe@example.com</emailAddress><id>100</id><name>joe</name></user>
Forwarded URL = null
Redirected URL = null
Cookies = []
This time we got xml response, even though we did not specify 'Accept' header with the request. The extension xml with the request caused it to map to the handler which produces xml response.
Specifying the extension with Controller Mapping
Now let's use the path extension with @RequestMapping without 'produces' element.
@Controller
@RequestMapping("super-user.xml")
public class SuperUserController {
@RequestMapping
@ResponseBody
public User getUserById (@RequestParam("id") long userId) {
User user = new User();
user.setId(userId);
user.setName("tim");
user.setEmailAddress("tim@example.com");
return user;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class SuperUserTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
.............
@Test
public void testSuperUserWithoutExtension () throws Exception {
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.get("/super-user")
.param("id", "200");
this.mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andDo(MockMvcResultHandlers.print());
}
.............
}
Running the test
mvn -q test -Dtest=SuperUserTests#testSuperUserWithoutExtension
Output
The test fails:
java.lang.AssertionError: Status
Expected :200
Actual :404
Let's use the file extension with the request:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class UserTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
.............
@Test
public void testUserRequestWithXmlExtension () throws Exception {
MockHttpServletRequestBuilder builder =
MockMvcRequestBuilders.get("/user.xml")
.param("id", "100");
this.mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status()
.isOk())
.andDo(MockMvcResultHandlers.print());
}
.............
}
Running the test
mvn -q test -Dtest=UserTests#testUserRequestWithXmlExtension
Output
MockHttpServletRequest:
HTTP Method = GET
Request URI = /super-user.xml
Parameters = {id=[200]}
Headers = {}
Handler:
Type = com.logicbig.example.SuperUserController
Method = public com.logicbig.example.User com.logicbig.example.SuperUserController.getUserById(long)
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=[application/xml]}
Content type = application/xml
Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><user><emailAddress>tim@example.com</emailAddress><id>200</id><name>tim</name></user>
Forwarded URL = null
Redirected URL = null
Cookies = []
Conclusion
If we don't specify 'Accept' header with the request, instead use the file extension (xml) then the request will map to the handler which 'produces' XML response. That happens because of Path extension content-negotiation Strategy. Also, on the handler method, using file extension with @RequestMapping#path instead of specifying 'produces' element will also work and Path extension strategy will be used. In both cases, we need to use file extension with the request URI (e.g. /user.xml).
Note that, path extension strategy works for the extensions json, xml, rss, atom by default if corresponding dependencies are in the classpath.
Example ProjectDependencies and Technologies Used: - spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
- spring-test 4.3.9.RELEASE: Spring TestContext Framework.
- javax.servlet-api 3.1.0 Java Servlet API
- junit 4.12: JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
- JDK 1.8
- Maven 3.3.9
|