深入理解SpringMVC中@RequestBody - Go语言中文社区

深入理解SpringMVC中@RequestBody


**

SpringMVC中使用@RequestBody,@ResponseBody注解实现Java对象和XML/JSON数据自动转换(上)

**

Spring3.1开始使用新的HandlerMapping 和 HandlerAdapter 来支持@Contoller 和@RequestMapping注解处理:处理器映射RequestMappingHandlerMapping和处理器适配器RequestMappingHandlerAdapter组合来代替Spring2.5 开始的处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter。

HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain 对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象;
HandlerAdapter:HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。


配合@ResponseBody注解,以及HTTP Request Header中的Accept属性,Controller返回的Java对象可以自动被转换成对应的XML或者JSON数据。
先看一个例子,只需要简单的几步,就可以返回XML数据。(本文使用Spring版本 4.1.6,并使用maven做项目构建)

1)在配置文件中添加


 
  1. <!-- 自动扫描的包名 -->
  2. <context:component-scan base-package="learning.webapp.controller" />
  3. <!-- 默认的注解映射的支持 -->
  4. <mvc:annotation-driven/>

2)添加以下几个java类


 
  1. package learning.webapp.model;
  2. public class Employee {
  3. private String name;
  4. private int salary;
  5. public Employee() {
  6. }
  7. public Employee(String name, int salary) {
  8. this.name = name;
  9. this.salary = salary;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public int getSalary() {
  15. return salary;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public void setSalary(int salary) {
  21. this.salary = salary;
  22. }
  23. }



 
  1. package learning.webapp.model;
  2. import javax.xml.bind.annotation.XmlRootElement;
  3. @XmlRootElement
  4. public class EmployeeX extends Employee {
  5. public EmployeeX() {
  6. super();
  7. }
  8. public EmployeeX(String name, int salary) {
  9. super(name, salary);
  10. }
  11. }



 
  1. package learning.webapp.controller;
  2. import learning.webapp.model.Employee;
  3. import learning.webapp.model.EmployeeX;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestMethod;
  8. import org.springframework.web.bind.annotation.ResponseBody;
  9. @Controller
  10. @RequestMapping( "/employees")
  11. public class XmlOrJsonController {
  12. @RequestMapping(value= "/xml/{name}", method=RequestMethod.GET)
  13. @ResponseBody
  14. public Employee getEmployeeXml(@PathVariable String name) {
  15. return new EmployeeX(name, 16000);
  16. }
  17. }

3) 在Eclipse中使用Jetty插件启动Web Server,然后在浏览器中访问:



非常简单!Spring是怎么实现这个转换的呢?我们先了解下Spring的消息转换机制。
在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。
我们可以用下面的图,简单描述一下这个过程。



这里最关键的就是<mvc:annotation-driven/>,加了这句配置,Spring会调用org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser来解析。
在这个类的parse(Element, ParserContext)方法中,分别实例化了RequestMappingHandlerMapping,RequestMappingHandlerAdapter等诸多类。
RequestMappingHandlerAdapter是请求处理的适配器,我们重点关注它的messageConverters属性。

1)RequestMappingHandlerAdapter在调用handle()的时候,会委托给ServletInvocableHandlerMethod的invokeAndHandle()方法进行处理,这个方法又调用HandlerMethodReturnValueHandlerComposite类进行处理。
HandlerMethodReturnValueHandlerComposite维护了一个HandlerMethodReturnValueHandler列表。

由于我们使用了@ResponseBody注解,getReturnValueHandler就会返回RequestResponseBodyMethodProcessor的实例。



2)之后RequestResponseBodyMethodProcessor.handleReturnValue()方法会被调用。此方法会调用AbstractMessageConverterMethodProcessor.writeWithMessageConverters()。它会根据request header中的Accept属性来选择合适的message converter.



3) messageConverters中有如下的6个converter. 它们是从哪里来的呢?前面提到,AnnotationDrivenBeanDefinitionParser.parse(Element, ParserContext)方法中,分别实例化了RequestMappingHandlerMapping,RequestMappingHandlerAdapter以及messageConverters属性。
需要关注org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter这个类,就是它实现了返回对象到XML的转换。


4)看一下getMessageConverters()中的处理。有5个message converter是一定会加进来的。


 
  1. if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute( "register-defaults"))) {
  2. messageConverters.setSource(source);
  3. messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
  4. RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
  5. stringConverterDef.getPropertyValues().add( "writeAcceptCharset", false);
  6. messageConverters.add(stringConverterDef);
  7. messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
  8. messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
  9. messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

然后再看,这里jaxb2Present为true, 因此Jaxb2RootElementHttpMessageConverter被添加到messageConverters中。



5)看一下jaxb2Present的定义,原来javax.xml.bind.Binder这个类是JDK中包含的类,所以jaxb2Present=true。



6)我们看一下Jaxb2RootElementHttpMessageConverter的canWrite()方法。返回true的条件有两个

a) 返回对象的类具有XmlRootElement注解;
b) 请求头中的Accept属性包含application/xml。


 
  1. @Override
  2. public boolean canWrite(Class<?> clazz, MediaType mediaType) {
  3. return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType));
  4. }

7) 在chrome中打开开发者工具,可以看到请求头中确实包含了Accept=application/xml



接下来我们看看如果想要返回JSON数据,应该怎么做?

根据上面的分析,首先我们需要添加一个支持JSON的message converter. 前面分析getMessageConverters()代码的时候,看到


 
  1. if (jackson2Present) {
  2. RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
  3. GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
  4. jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue( 0, jacksonFactoryDef);
  5. messageConverters.add(jacksonConverterDef);
  6. }
  7. else if (gsonPresent) {
  8. messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
  9. }

然后再来看看jackson2Present和gsonPresent的定义。


 
  1. private static final boolean jackson2Present =
  2. ClassUtils.isPresent( "com.fasterxml.jackson.databind.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
  3. ClassUtils.isPresent( "com.fasterxml.jackson.core.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());


 


 
  1. private static final boolean gsonPresent =
  2. ClassUtils.isPresent( "com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());

所以我们只要把Jackson2或者GSON加入工程的class path,Spring就会自动把GsonHttpMessageConverter加进来。

1)我们在POM中添加以下依赖


 
  1. <dependency>
  2. <groupId>com.fasterxml.jackson.core </groupId>
  3. <artifactId>jackson-databind </artifactId>
  4. <version>2.6.1 </version>
  5. </dependency>

或者


 
  1. <dependency>
  2. <groupId>com.google.code.gson </groupId>
  3. <artifactId>gson </artifactId>
  4. <version>2.3.1 </version>
  5. </dependency>

2)在XmlOrJsonController.java中添加getEmployeeJson()方法


 
  1. @RequestMapping(value= "/json/{name}", method=RequestMethod.GET)
  2. @ResponseBody
  3. public Employee getEmployeeJson(@PathVariable String name) {
  4. return new Employee(name, 16000);
  5. }

和getEmployeeXml()相比,这里唯一的不同是返回对象变成了Employee,因为Employee类上没有@XmlRootElement注解,所以Spring不会选择Jaxb2RootElementHttpMessageConverter。又因为Accept属性中包含了*/*,表示接受任意格式返回数据,所以GsonHttpMessageConverter的canWrite()方法返回true.这样Spring就会选择MappingJackson2HttpMessageConverter或者GsonHttpMessageConverter来进行数据转换。



至此,我们知道请求头中的Accept属性是一个很关键的东西,我们可以根据这个在Controller中写一个方法,根据Accept的值自动返回XML或者JSON数据。


 
  1. /**
  2. * 根据request header中的Accept自动选择返回XML or JSON
  3. */
  4. @RequestMapping(value= "/{name}", method=RequestMethod.GET)
  5. @ResponseBody
  6. public Employee getEmployee(@PathVariable String name) {
  7. return new EmployeeX(name, 16000);
  8. }

因为浏览器的Accept值不方便修改,我们自己写客户端来调用。


 
  1. package learning.webapp;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.net.URI;
  5. import java.net.URISyntaxException;
  6. import org.junit.Test;
  7. import org.springframework.http.HttpEntity;
  8. import org.springframework.http.HttpHeaders;
  9. import org.springframework.http.HttpMethod;
  10. import org.springframework.http.client.ClientHttpRequest;
  11. import org.springframework.http.client.ClientHttpResponse;
  12. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  13. import org.springframework.web.client.RestTemplate;
  14. public class XmlOrJasonControllerTest {
  15. @Test
  16. public void testJsonResponse() throws IOException, URISyntaxException {
  17. String url = "http://localhost:8080/employees/Jack";
  18. ClientHttpRequest request = new SimpleClientHttpRequestFactory().createRequest( new URI(url), HttpMethod.GET);
  19. request.getHeaders().set( "Accept", "application/json");
  20. ClientHttpResponse response = request.execute();
  21. InputStream is = response.getBody();
  22. byte bytes[] = new byte[( int) response.getHeaders().getContentLength()];
  23. is.read(bytes);
  24. String jsonData = new String(bytes);
  25. System.out.println(jsonData);
  26. }
  27. @Test
  28. public void testXmlResponse() throws IOException, URISyntaxException {
  29. String url = "http://localhost:8080/employees/Jack";
  30. // response headers 中包含Transfer-Encoding:chunked,没有content length,
  31. HttpHeaders requestHeaders = new HttpHeaders();
  32. requestHeaders.set( "Accept", "application/xml");
  33. RestTemplate restTemplate = new RestTemplate();
  34. HttpEntity<Object> httpEntity = new HttpEntity<Object>(requestHeaders);
  35. String xmlData = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class).getBody();
  36. System.out.println(xmlData);
  37. }
  38. }



[参考资料]

1)http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-convert.html

2)http://my.oschina.net/lichhao/blog/172562

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_35273452/article/details/82791552
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢