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 (本小節跟本文關係不大,略去 譯者)

相關文章
相關標籤/搜索