Spring MVC的@ResponseBody和responseEntity的使用

本文讲解Spring MVC的Response,深刻了解一下@RequestMapping配合@ResponseBody的用法,同时介绍另一个和Response有关的类ResponseEntity。html

首先看看本文演示用到的类ResponseController:java

Java代码 web

  1. package org.springframework.samples.mvc.response;  
  2.   
  3. import org.springframework.http.HttpHeaders;  
  4. import org.springframework.http.HttpStatus;  
  5. import org.springframework.http.MediaType;  
  6. import org.springframework.http.ResponseEntity;  
  7. import org.springframework.stereotype.Controller;  
  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. public class ResponseController {  
  14.   
  15.     @RequestMapping(value="/response/annotation", method=RequestMethod.GET)  
  16.     public @ResponseBody String responseBody() {  
  17.         return "The String ResponseBody";  
  18.     }  
  19.   
  20.     @RequestMapping(value="/response/charset/accept", method=RequestMethod.GET)  
  21.     public @ResponseBody String responseAcceptHeaderCharset() {  
  22.         return "こんにちは世界! (\"Hello world!\" in Japanese)";  
  23.     }  
  24.   
  25.     @RequestMapping(value="/response/charset/produce", method=RequestMethod.GET, produces="text/plain;charset=UTF-8")  
  26.     public @ResponseBody String responseProducesConditionCharset() {  
  27.         return "こんにちは世界! (\"Hello world!\" in Japanese)";  
  28.     }  
  29.   
  30.     @RequestMapping(value="/response/entity/status", method=RequestMethod.GET)  
  31.     public ResponseEntity<String> responseEntityStatusCode() {  
  32.         return new ResponseEntity<String>("The String ResponseBody with custom status code (403 Forbidden - stephansun)",  
  33.                 HttpStatus.FORBIDDEN);  
  34.     }  
  35.   
  36.     @RequestMapping(value="/response/entity/headers", method=RequestMethod.GET)  
  37.     public ResponseEntity<String> responseEntityCustomHeaders() {  
  38.         HttpHeaders headers = new HttpHeaders();  
  39.         headers.setContentType(MediaType.TEXT_PLAIN);  
  40.         return new ResponseEntity<String>("The String ResponseBody with custom header Content-Type=text/plain",  
  41.                 headers, HttpStatus.OK);  
  42.     }  
  43.   
  44. }  

 

访问http://localhost:8080/web/response/response/annotation,对应responseBody(),这个方法很典型,以前已经见过屡次了,将字符串直接输出到浏览器。spring

 

访问http://localhost:8080/web/response/charset/accept,对应responseAcceptHeaderCharset(),该方法和responseBody()并无什么不一样,只是,返回的字符串中带有日文。浏览器显示"???????? ("Hello world!" in Japanese)",有乱码出现。apache

 

访问http://localhost:8080/web/response/charset/produce,对应responseProducesConditionCharset(),该方法跟responseAcceptHeaderCharset()相比,在@RequestMapping中增长了“produces="text/plain;charset=UTF-8"”,浏览器显示"こんにちは世界! ("Hello world!" in Japanese)",乱码没有了。浏览器

为了将这二者的区别说清楚,看看日志:spring-mvc

responseAcceptHeaderCharset():mvc

  1. DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [こんにちは世界! ("Hello world!" in Japanese)] as "text/html" using [org.springframework.http.converter.StringHttpMessageConverter@6b414655]  

responseProducesConditionCharset():app

日志代码 框架

  1. DEBUG: org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Written [こんにちは世界! ("Hello world!" in Japanese)] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@6b414655]  

 

前者使用默认的"text/html",后者使用了特定的"text/plain;charset=UTF-8"。为何以"text/html"形式输出日文(其实中文也是同样的)就会乱码的根本缘由我还没透彻地搞清楚,但我注意到spring-web-3.1.0.REALEASE.jar中org.springframework.http.converter.StringHttpMessageConverter中有这样一段代码:

Java代码 

  1. public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");  

应该跟"ISO-8859-1"有关系吧。

 

后者经过指定@RequestMapping的produces属性为text/plain,字符集为UTF-8,能够避免乱码,这个是能够理解的。

 

最后看看responseEntityStatusCode()和responseEntityCustomHeaders()方法,这两个方法和前面的方法的区别是没有@ResponseBody,返回的类型为ResponseEntity<String>。

responseEntityStatusCode()方法返回一个字符串,并附带了HTTP状态码为HttpStatus.FORBIDDEN(即403);

responseEntityCustomHeaders()除了返回一个字符串,HTTP状态码为HttpStatus.OK(即200),还指定了返回内容的content-type为text/plain;

这里最容易困惑咱们的就是HTTP状态码了,由于从浏览器的输出看不到任何与HTTP状态码相关的东西。

 

访问http://localhost:8080/web/response/entity/statushttp://localhost:8080/web/response/entity/headers

均能正常的将字符串内容显示在网页上,那么HttpStatus.FORBIDDEN起什么做用呢?

ResponseEntity继承于HttpEntity类,HttpEntity类的源代码的注释说"HttpEntity主要是和RestTemplate组合起来使用,一样也能够在SpringMVC中做为@Controller方法的返回值使用"。ResponseEntity类的源代码注释说"ResponseEntity是HttpEntity的扩展,增长了一个HttpStutus的状态码,一般和RestEntity配合使用,固然也能够在SpringMVC中做为@Controller方法的返回值使用"。那么使用RestTemplate写个程序模拟一下吧(RestTemplate的具体用法见本文附录):

Java代码 

  1. package org.springframework.samples.mvc.response;  
  2.   
  3. import org.springframework.http.HttpStatus;  
  4. import org.springframework.http.MediaType;  
  5. import org.springframework.http.ResponseEntity;  
  6. import org.springframework.web.client.RestTemplate;  
  7.   
  8. public class ResponseControllerTest {  
  9.   
  10.     public static void main(String[] args) {  
  11.         RestTemplate template = new RestTemplate();  
  12.         ResponseEntity<String> entity = template.getForEntity(  
  13.                 "http://localhost:8080/web/response/entity/status", String.class);  
  14.         String body = entity.getBody();  
  15.         MediaType contentType = entity.getHeaders().getContentType();  
  16.         HttpStatus statusCode = entity.getStatusCode();  
  17.         System.out.println("statusCode:[" + statusCode + "]");  
  18.     }  
  19. }  

访问http://localhost:8080/web/response/entity/status,观察日志:

日志代码 

  1. DEBUG: org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/web/response/entity/status"  
  2. DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]  
  3. WARN : org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/web/response/entity/status" resulted in 403 (Forbidden); invoking error handler  
  4. Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 403 Forbidden  
  5.     at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:76)  
  6.     at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)  
  7.     at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)  
  8.     at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401)  
  9.     at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:221)  
  10.     at org.springframework.samples.mvc.response.ResponseControllerTest.main(ResponseControllerTest.java:12)   

将URL换成http://localhost:8080/web/response/entity/headers,观察日志:

Java代码 

  1. DEBUG: org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/web/response/entity/headers"  
  2. DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, */*]  
  3. DEBUG: org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/web/response/entity/headers" resulted in 200 (OK)  
  4. DEBUG: org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain" using [org.springframework.http.converter.StringHttpMessageConverter@2e297ffb]  
  5. statusCode:[200]  

发现使用RestTemplate时,若是它发现返回的信息中HTTP状态码为403时,就抛出异常了,正好符合了咱们的指望,至于为何浏览器上没有体现,暂时还不不太明白(待补完)。

 

===================================================================

 

SpringSource的Team Blog上有一篇文章是关于@RequestMapping的produces属性的讨论。

 

===================================================================

 

附录Spring Reference Documentation中的相关内容:

 

[了解一下@RequesetMapping支持的返回值类型]

16.3.3.2 @RequestMapping注解方法支持的返回值类型

如下返回值的类型(return types)均支持:

  • ModelAndView对象,with the model implicitly enriched with command objects and the results of @ModelAttributes annotated reference data accessor methods.(恕我实在翻译不出 TAT)。
  • Model对象,with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • Map对象,for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
  • View对象,with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • String,value that is interpreted as the logical view name, with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
  • void,if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).
  • @ResponseBody,If the method is annotated with @ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters. See Section 16.3.3.5, “Mapping the response body with the @ResponseBody annotation”.
  • HttpEntity或者ResponseEntity,HttpEntity<?>或者ResponseEntity<?>对象能够取到Servlet的response的HTTP头信息(headers)和内容(contents)。这个实体(entity body)能够经过使用HttpMessageConverter类被转成Response流。See Section 16.3.3.6, “Using HttpEntity<?>”.
  • Any other return type is considered to be a single model attribute to be exposed to the view, using the attribute name specified through @ModelAttribute at the method level (or the default attribute name based on the return type class name). The model is implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.

 

[了解一下RestTemplate,下一篇文章有用]文档中有关RestTemplate的内容:

 

20.9 在客户端访问RESTful服务

RestTemplate是客户端访问RESTful服务的核心类。它在概念上同Spring中的其它模板类类似,如JdbcTemplate和JmsTemplate还有一些其它Spring portfolio工程中的模板类。RestTemplate提供了一个回调方法,使用HttpMessageConverter将对象marshal到HTTP请求体里,而且将response unmarshal成一个对象。使用XML做为消息格式是一个很广泛的作法,Spring提供了MarshallingHttpMessageConverter类,该类使用了Object-to-XML框架,该框架是org.springframe.oxm包的一部分。你有多种XML到Object映射技术可选。

本节介绍了如何使用RestTemplate以及和它相关的HttpMessageConverters。

20.9.1 RestTemplate

在Java中调用RESTful服务的一个经典的作法就是使用一个帮助类,如Jakarta Commons的HttpClient,对于通用的REST操做,HttpClient的实现代码比较底层,以下:

Java代码 

  1. String uri = "http://example.com/hotels/1/bookings";  
  2.   
  3. PostMethod post = new PostMethod(uri);  
  4. String request = // create booking request content  
  5. post.setRequestEntity(new StringRequestEntity(request));  
  6.   
  7. httpClient.executeMethod(post);  
  8.   
  9. if (HttpStatus.SC_CREATED == post.getStatusCode()) {  
  10.   Header location = post.getRequestHeader("Location");  
  11.   if (location != null) {  
  12.     System.out.println("Created new booking at :" + location.getValue());  
  13.   }  
  14. }  

 RestTemplate对HTTP的主要六种提交方式提供了更高层的抽象,使得调用RESTful服务时代码量更少,而且使REST表现的更好。

表格20.1 RestTemplate方法预览

 

HTTP方法 RestTemplate方法

DELETE delete

GET getForObject getForEntity

HEAD headForHeaders(String url, String... urlVariables)

OPTIONS optionsForAllow(String url, String... urlVariables)

POST postForLocation(String url, Object request, String... urlVariables) postForObject(String url, Object request, Class<T> responsetype, String... uriVariables)

PUT put(String url, Object request, String... urlVariables)

 

RestTemplate的方法名称遵循着一个命名规则,第一部分说明调用的是什么HTTP方法,第二部分说明的是返回了什么。好比,getForObject()方法对应GET请求,将HTTP response的内容转成你须要的一种对象类型,并返回这个对象。postForLocation()方法对应POST请求,converting the given object into a HTTP request and return the response HTTP Location header where the newly created object can be found.若是在执行一个HTTP请求时出现异常,会抛出RestClientException异常;能够在RestTemplate自定义ResponseErrorHandler的实现来自定义这种异常。

这些方法经过HttpMessageConverter实例将传递的对象转成HTTP消息,并将获得的HTTP消息转成对象返回。给主要mime类型服务的转换器(converter)默认就注册了,可是你也能够编写本身的转换器并经过messageConverters()bean属性注册。该模板默认注册的转换器实例是ByteArrayHttpMessageConverter,StringHttpMessageConverter,FormHttpMessageConverter以及SourceHttpMessageConverter。你可使用messageConverters()bean属性重写这些默认实现,好比在使用MarshallingHttpMessageConverter或者MappingJacksonHttpMessageConverter时你就须要这么作。

每一个方法有有两种类型的参数形式,一种是可变长度的String变量,另外一种是Map<String, String>,好比:

Java代码 

  1. String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}",  
  2.                                           String.class,"42", "21");  

使用可变长度参数,

Java代码 

  1. Map<String, String> vars = Collections.singletonMap("hotel", "42");  
  2. String result =  
  3.   restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);  

使用一个Map<String, String> 。

你能够直接使用RestTemplate的默认构造器建立一个RestTemplate的实例。它会使用jdk中java.net包的标准Java类建立HTTP请求。你也能够本身指定一个ClientHttpRequestFactory的实现。Spring提供了CommonsClientHttpRequestFactory的实现,该类使用了Jakarta Commons的HttpClient建立HTTP请求。CommonsClientHttpRequestFactory配置了org.apache.commons.httpclient.HttpClient的一个实例,该实例反过来被credentials information或者链接池配置。

前面那个使用了Jakarta Commons的HttpClient类的例子能够直接使用RestTemplate重写,以下:

Java代码 

  1. uri = "http://example.com/hotels/{id}/bookings";  
  2.   
  3. RestTemplate template = new RestTemplate();  
  4.   
  5. Booking booking = // create booking object  
  6.   
  7. URI location = template.postForLocation(uri, booking, "1");  

通用的回调接口是RequestCallback,该接口在execute方法被调用时被调用。

Java代码 

  1. public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,  
  2.                      ResponseExtractor<T> responseExtractor,  
  3.                      String... urlVariables)  
  4.   
  5.   
  6. // also has an overload with urlVariables as a Map<String, String>.  

RequestCallback接口定义以下:

Java代码 

  1. public interface RequestCallback {  
  2.  void doWithRequest(ClientHttpRequest request) throws IOException;  
  3. }  

该接口容许你管控(manipulate)request的header而且象request体中写数据。当使用execute方法时,你没必要担忧任何资源管理问题的,模板老是会关闭request,而且处理任何error,你能够参考API文档来得到更多有关使用execute方法及该模板其它方法参数含义的信息。

20.9.1.1 同URI一块儿工做

对HTTP的主要方法,RestTemplate的第一个参数是可变的,你可使用一个String类型的URI,也可使用java.net.URI类型的类,

String类型的URI接受可变长度的String参数或者Map<String, String>,这个URL字符串也是假定没有被编码(encoded)而且须要被编码的(encoded)的。举例以下:

Java代码 

  1. restTemplate.getForObject("http://example.com/hotel list", String.class);  

这行代码使用GET方式请求http://example.com/hotel%20list。这意味着若是输入的URL字符串已经被编码了(encoded),它将被编码(encoded)两次 -- 好比:http://example.com/hotel%20list会变成http://example.com/hotel%2520list。若是这不是你所但愿的,那就使用java.net.URI,它假定URL已经被编码(encoded)过了,若是你想要将一个单例的URI(fully expanded)复用屡次的话,这个方法也是有用的。

UriComponentsBuilder类(咱们已经见过了,不是吗? 译者)能够构建和编码一个包含了URI模板支持的URI,好比你能够从一个URL字符串开始:

Java代码 

  1. UriComponents uriComponents =   
  2.         UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}/bookings/{booking}").build()  
  3.             .expand("42", "21")  
  4.             .encode();  
  5.               
  6. URI uri = uriComponents.toUri();  

 或者单独地指定每一个URI组件:

Java代码 

  1. UriComponents uriComponents =   
  2.         UriComponentsBuilder.newInstance()  
  3.             .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()  
  4.             .expand("42", "21")  
  5.             .encode();  
  6.   
  7. URI uri = uriComponents.toUri();  

 

 20.9.1.2 处理request和response的头信息(headers)

除了前面已经说到的方法外,RestTemplate还有一个exchange()方法,该方法能够用在基于HttpEntity类的任意HTTP方法的执行上。

也许最重要的一点是,exchange()方法能够用来添加request头信息(headers)以及读response的头信息(headers)。举例:

Java代码 

  1. HttpHeaders requestHeaders = new HttpHeaders();  
  2. requestHeaders.set("MyRequestHeader", "MyValue");  
  3. HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);  
  4.   
  5. HttpEntity<String> response = template.exchange("http://example.com/hotels/{hotel}",  
  6.   HttpMethod.GET, requestEntity, String.class, "42");  
  7.   
  8. String responseHeader = response.getHeaders().getFirst("MyResponseHeader");  
  9. String body = response.getBody();  

在上面这个例子中,咱们首先准备了一个request实体(entity),该实体包含了MyRequestHeader头信息(header)。而后咱们取回(retrieve)response,读到这个MyResponseHeader和返回体(body)。

20.9.2 HTTP Message Conversion (本小节跟本文关系不大,略去 译者)

相关文章
相关标签/搜索