@responseBody注解的使用

在Controller中的方法若用此方法注解:


[java] view plain copy
  1.     
  2.      @ResponseBody  
  3.          @RequestMapping("/pay/tenpay")  
  4.     public String tenpayReturnUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  5.         unpackCookie(request, response);  
  6.         payReturnUrl.payReturnUrl(request, response);  
  7.         return "pay/success";  
  8.     }  


用此办法注解将会在访问的页面上输出 字符串:strHtml,而不会跳转页面至pay/success.jsp页面

原因是:

@ResponseBody

作用: 

      该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

使用时机:

      返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;


那么只需删除注解:@ResponseBody 便可以返回页面pay/success.jsp。而且达到了与客户端后台交互的效果。






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

1)在配置文件中添加

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

2)添加以下几个java类

[java] view plain copy
  1. package learning.webapp.model;  
  2.   
  3. public class Employee {  
  4.     private String name;  
  5.     private int salary;  
  6.   
  7.     public Employee() {  
  8.     }  
  9.       
  10.     public Employee(String name, int salary) {  
  11.         this.name = name;  
  12.         this.salary = salary;  
  13.     }  
  14.   
  15.     public String getName() {  
  16.         return name;  
  17.     }  
  18.   
  19.     public int getSalary() {  
  20.         return salary;  
  21.     }  
  22.       
  23.     public void setName(String name) {  
  24.         this.name = name;  
  25.     }  
  26.   
  27.     public void setSalary(int salary) {  
  28.         this.salary = salary;  
  29.     }  
  30. }  


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


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

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

@responseBody注解的使用

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

@responseBody注解的使用

这里最关键的就是<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的实例。

@responseBody注解的使用

@responseBody注解的使用


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

@responseBody注解的使用

@responseBody注解的使用


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

@responseBody注解的使用

@responseBody注解的使用


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

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

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

@responseBody注解的使用


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

@responseBody注解的使用


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

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

[java] view plain copy
  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

@responseBody注解的使用


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

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

[java] view plain copy
  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的定义。

[java] view plain copy
  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());  



[java] view plain copy
  1. private static final boolean gsonPresent =  
  2.         ClassUtils.isPresent("com.google.gson.Gson", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());  

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

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

[html] view plain copy
  1. <dependency>  
  2.     <groupId>com.fasterxml.jackson.core</groupId>  
  3.     <artifactId>jackson-databind</artifactId>  
  4.     <version>2.6.1</version>  
  5. </dependency>  

或者

[html] view plain copy
  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()方法

[java] view plain copy
  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来进行数据转换。


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

[java] view plain copy
  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值不方便修改,我们自己写客户端来调用。

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