Spring MVC學習(六)-------註解式控制器詳解3

6.6.五、生產者、消費者限定html

6.6.5.一、基本概念

首先讓咱們看一下經過HTTP協議傳輸的媒體類型及如何表示媒體類型:java

 

1、Media Type:web

互聯網媒體類型,通常就是咱們所說的MIME類型,用來肯定請求的內容類型或響應的內容類型。spring

 寫道
媒體類型格式:type/subtype(;parameter)?
type主類型,任意的字符串,如text,若是是*號表明全部;
subtype 子類型,任意的字符串,如html,若是是*號表明全部;
parameter 可選,一些參數,如Accept請求頭的q參數, Content-Type的 charset參數。

詳見http://tools.ietf.org/html/rfc2616#section-3.7

 常見媒體類型:chrome

 

text/html : HTML格式          text/plain :純文本格式             text/xml :XML格式json

image/gif :gif圖片格式          image/jpeg :jpg圖片格式          image/png:png圖片格式瀏覽器

 

application/x-www-form-urlencoded : <form encType=」」>中默認的encType,form表單數據被編碼爲key/value格式發送到服務器(表單默認的提交數據的格式)。tomcat

multipart/form-data : 當你須要在表單中進行文件上傳時,就須要使用該格式;服務器

 

application/xhtml+xml :XHTML格式               application/xml     : XML數據格式 mvc

application/atom+xml  :Atom XML聚合格式    application/json    : JSON數據格式

application/pdf       :pdf格式                        application/msword  : Word文檔格式

application/octet-stream : 二進制流數據(如常見的文件下載)。

 

在如tomcat服務器的 「conf/web.xml」中指定了擴展名到媒體類型的映射,在此咱們能夠看到服務器支持的媒體類型。

 

 

2、Content-Type:內容類型,即請求/響應的內容區數據的媒體類型;

2.一、請求頭的內容類型,表示發送到服務器的內容數據的媒體類型;

request中設置請求頭「Content-Type: application/x-www-form-urlencoded」表示請求的數據爲key/value數據;

(一、控制器cn.javass.chapter6.web.controller.consumesproduces.contenttype.RequestContentTypeController

 

Java代碼   收藏代碼
  1.     @RequestMapping(value = "/ContentType", method = RequestMethod.GET)  
  2.     public String showForm() throws IOException {  
  3.         //form表單,使用application/x-www-form-urlencoded編碼方式提交表單  
  4.         return "consumesproduces/Content-Type";  
  5.     }  
  6.       
  7.     @RequestMapping(value = "/ContentType", method = RequestMethod.POST,   
  8. headers = "Content-Type=application/x-www-form-urlencoded")  
  9.     public String request1(HttpServletRequest request) throws IOException {  
  10.         //①獲得請求的內容區數據的類型  
  11.         String contentType = request.getContentType();   
  12.         System.out.println("========ContentType:" + contentType);  
  13.         //②獲得請求的內容區數據的編碼方式,若是請求中沒有指定則爲null  
  14.         //注意,咱們的CharacterEncodingFilter這個過濾器設置了編碼(UTF-8)  
  15.         //編碼只能被指定一次,即若是客戶端設置了編碼,則過濾器不會再設置  
  16.         String characterEncoding = request.getCharacterEncoding();  
  17.         System.out.println("========CharacterEncoding:" + characterEncoding);  
  18.           
  19.         //③表示請求的內容區數據爲form表單提交的參數,此時咱們能夠經過request.getParameter獲得數據(key=value)  
  20.         System.out.println(request.getParameter("realname"));  
  21.         System.out.println(request.getParameter("username"));  
  22.         return "success";  
  23.     }  

 showForm功能處理方式:展現表單,且form的enctype="application/x-www-form-urlencoded",在提交時請求的內容類型頭爲「Content-Type:application/x-www-form-urlencoded」;

 

 

request1功能處理方法:只對請求頭爲「Content-Type:application/x-www-form-urlencoded」的請求進行處理(即消費請求內容區數據);

      request.getContentType()能夠獲得請求頭的內容區數據類型(即Content-Type頭的值)

      request.getCharacterEncoding()如「Content-Type:application/json;charset=GBK」,則獲得的編碼爲「GBK」,不然若是你設置過濾器(CharacterEncodingFilter)則獲得它設置的編碼,不然返回null。

      request.getParameter()由於請求的內容區數據爲application/x-www-form-urlencoded格式的數據,所以咱們能夠經過request.getParameter()獲得相應參數數據。

 

request中設置請求頭「Content-Type:application/json;charset=GBK」表示請求的內容區數據爲json類型數據,且內容區的數據以GBK進行編碼;

 

(一、控制器cn.javass.chapter6.web.controller.consumesproduces.contenttype.RequestContentTypeController

Java代碼   收藏代碼
  1. @RequestMapping(value = "/request/ContentType", method = RequestMethod.POST,   
  2. headers = "Content-Type=application/json")  
  3.     public String request2(HttpServletRequest request) throws IOException {          
  4.         //①表示請求的內容區數據爲json數據  
  5.         InputStream is = request.getInputStream();  
  6.         byte bytes[] = new byte[request.getContentLength()];  
  7.         is.read(bytes);  
  8.         //②獲得請求中的內容區數據(以CharacterEncoding解碼)  
  9.         //此處獲得數據後你能夠經過如json-lib轉換爲其餘對象  
  10.         String jsonStr = new String(bytes, request.getCharacterEncoding());  
  11.         System.out.println("json data:" + jsonStr);  
  12.         return "success";  
  13.     }   

  request2功能處理方法:只對請求頭爲「Content-Type:application/json」的進行請求處理(即消費請求內容區數據);

      request.getContentLength()能夠獲得請求頭的內容區數據的長度;

      request.getCharacterEncoding()如「Content-Type:application/json;charset=GBK」,則獲得的編碼爲「GBK」,不然若是你設置過濾器(CharacterEncodingFilter)則獲得它設置的編碼,不然返回null。

     

      咱們獲得json的字符串形式後就能很簡單的轉換爲JSON相關的對象。

 

(二、客戶端發送json數據請求

 

Java代碼   收藏代碼
  1. //請求的地址  
  2. String url = "http://localhost:9080/springmvc-chapter6/request/ContentType";  
  3. //①建立Http Request(內部使用HttpURLConnection)  
  4. ClientHttpRequest request =   
  5.     new SimpleClientHttpRequestFactory().     
  6.         createRequest(new URI(url), HttpMethod.POST);  
  7. //②設置請求頭的內容類型頭和內容編碼(GBK)  
  8. request.getHeaders().set("Content-Type""application/json;charset=gbk");  
  9. //③以GBK編碼寫出請求內容體  
  10. String jsonData = "{\"username\":\"zhang\", \"password\":\"123\"}";  
  11. request.getBody().write(jsonData.getBytes("gbk"));  
  12. //④發送請求並獲得響應  
  13. ClientHttpResponse response = request.execute();  
  14. System.out.println(response.getStatusCode());  

 此處咱們使用Spring提供的Http客戶端API SimpleClientHttpRequestFactory建立了請求並設置了請求的Content-Type和編碼並在響應體中寫回了json數據(即生產json類型的數據),此處是硬編碼,實際工做可使用json-lib等工具進行轉換。

 

具體代碼在cn.javass.chapter6.web.controller.consumesproduces.contenttype.RequestContentTypeClient。

 

2.二、響應頭的內容類型,表示發送到客戶端的內容數據類型,和請求頭的內容類型相似,只是方向相反。

Java代碼   收藏代碼
  1. @RequestMapping("/response/ContentType")  
  2. public void response1(HttpServletResponse response) throws IOException {  
  3.     //①表示響應的內容區數據的媒體類型爲html格式,且編碼爲utf-8(客戶端應該以utf-8解碼)  
  4.     response.setContentType("text/html;charset=utf-8");  
  5.     //②寫出響應體內容  
  6.     response.getWriter().write("<font style='color:red'>hello</font>");  
  7. }  

  <!--[endif]-->

如上所示,經過response.setContentType("text/html;charset=utf-8") 告訴客戶端響應體媒體類型爲html,編碼爲utf-8,你們能夠經過chrome工具查看響應頭爲Content-Type:text/html;charset=utf-8,還一個Content-Length:36表示響應體大小。

 

代碼在cn.javass.chapter6.web.controller.consumesproduces.contenttype.ResponseContentTypeController。

 

 

如上代碼能夠看出Content-Type能夠指定請求/響應的內容體的媒體格式和可選的編碼方式。如圖6-9 


 

①客戶端—發送請求—服務器:客戶端經過請求頭Content-Type指定內容體的媒體類型(即客戶端此時是生產者),服務器根據Content-Type消費內容體數據(即服務器此時是消費者);

②服務器—發送請求—客戶端:服務器生產響應頭Content-Type指定的響應體數據(即服務器此時是生產者),客戶端根據Content-Type消費內容體數據(即客戶端此時是消費者)。

 

問題:

①服務器端能夠經過指定【headers = "Content-Type=application/json"】來聲明可處理(可消費)的媒體類型,即只消費Content-Type指定的請求內容體數據;

②客戶端如何告訴服務器端它只消費什麼媒體類型的數據呢?即客戶端接受(須要)什麼類型的數據呢?服務器應該生產什麼類型的數據?此時咱們能夠請求的Accept請求頭來實現這個功能。

 

3、Accept:用來指定什麼媒體類型的響應是可接受的,即告訴服務器我須要什麼媒體類型的數據,此時服務器應該根據Accept請求頭生產指定媒體類型的數據。

 2.一、json數據

(一、服務器端控制器

 

Java代碼   收藏代碼
  1. @RequestMapping(value = "/response/ContentType", headers = "Accept=application/json")  
  2. public void response2(HttpServletResponse response) throws IOException {  
  3.     //①表示響應的內容區數據的媒體類型爲json格式,且編碼爲utf-8(客戶端應該以utf-8解碼)  
  4.     response.setContentType("application/json;charset=utf-8");  
  5.     //②寫出響應體內容  
  6.     String jsonData = "{\"username\":\"zhang\", \"password\":\"123\"}";  
  7.     response.getWriter().write(jsonData);  
  8. }  

  服務器根據請求頭「Accept=application/json」生產json數據。

 

(二、客戶端端接收服務器端json數據響應

 

使用瀏覽器測試(Ajax場景使用該方式)

請求地址爲:http://localhost:9080/springmvc-chapter6/response/ContentType,且把修改請求頭Accept改成「Accept=application/json」:


你們能夠下載chrome的JSONView插件來以更好看的方式查看json數據,安裝地址:https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc

 

 

使用普通客戶端測試(服務器之間通訊可以使用該方式)

 

Java代碼   收藏代碼
  1. private static void jsonRequest() throws IOException, URISyntaxException {  
  2.     //請求的地址  
  3.     String url = "http://localhost:9080/springmvc-chapter6/response/ContentType";  
  4.     //①建立Http Request(內部使用HttpURLConnection)  
  5.     ClientHttpRequest request =   
  6.         new SimpleClientHttpRequestFactory().     
  7.             createRequest(new URI(url), HttpMethod.POST);  
  8.     //②設置客戶端可接受的媒體類型(即須要什麼類型的響應體數據)  
  9.     request.getHeaders().set("Accept""application/json");          
  10.     //③發送請求並獲得響應  
  11.     ClientHttpResponse response = request.execute();  
  12.     //④獲得響應體的編碼方式  
  13.     Charset charset = response.getHeaders().getContentType().getCharSet();          
  14.     //⑤獲得響應體的內容          
  15.     InputStream is = response.getBody();  
  16.     byte bytes[] = new byte[(int)response.getHeaders().getContentLength()];  
  17.     is.read(bytes);  
  18.     String jsonData = new String(bytes, charset);  
  19.     System.out.println("charset : " + charset + ", json data : " + jsonData);  
  20. }  

 request.getHeaders().set("Accept", "application/json")表示客戶端只接受(即只消費)json格式的響應數據;

response.getHeaders()能夠獲得響應頭,從而能夠獲得響應體的內容類型和編碼、內容長度。

2.二、xml數據

(一、服務器端控制器

 

Java代碼   收藏代碼
  1. @RequestMapping(value = "/response/ContentType", headers = "Accept=application/xml")  
  2. public void response3(HttpServletResponse response) throws IOException {  
  3.     //①表示響應的內容區數據的媒體類型爲xml格式,且編碼爲utf-8(客戶端應該以utf-8解碼)  
  4.     response.setContentType("application/xml;charset=utf-8");  
  5.     //②寫出響應體內容  
  6.     String xmlData = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";  
  7.     xmlData += "<user><username>zhang</username><password>123</password></user>";  
  8.     response.getWriter().write(xmlData);  
  9. }  

  和生產json數據惟一不一樣的兩點:請求頭爲「Accept=application/xml」,響應體數據爲xml。

 

(二、客戶端端接收服務器端xml數據響應

 

使用瀏覽器測試(Ajax場景使用該方式)

請求地址爲:http://localhost:9080/springmvc-chapter6/response/ContentType,且把修改請求頭Accept改成「Accept=application/xml」,和json方式相似,此處再也不重複。

 

使用普通客戶端測試(服務器之間通訊可以使用該方式)

 

Java代碼   收藏代碼
  1. private static void xmlRequest() throws IOException, URISyntaxException {  
  2.     //請求的地址  
  3.     String url = "http://localhost:9080/springmvc-chapter6/response/ContentType";  
  4.     //①建立Http Request(內部使用HttpURLConnection)  
  5.     ClientHttpRequest request =   
  6.         new SimpleClientHttpRequestFactory().     
  7.             createRequest(new URI(url), HttpMethod.POST);  
  8.     //②設置客戶端可接受的媒體類型(即須要什麼類型的響應體數據)  
  9.     request.getHeaders().set("Accept""application/xml");  
  10.     //③發送請求並獲得響應  
  11.     ClientHttpResponse response = request.execute();          
  12.     //④獲得響應體的編碼方式  
  13.     Charset charset = response.getHeaders().getContentType().getCharSet();  
  14.     //⑤獲得響應體的內容          
  15.     InputStream is = response.getBody();  
  16.     byte bytes[] = new byte[(int)response.getHeaders().getContentLength()];  
  17.     is.read(bytes);  
  18.     String xmlData = new String(bytes, charset);  
  19.     System.out.println("charset : " + charset + ", xml data : " + xmlData);  
  20. }  

 request.getHeaders().set("Accept", "application/xml")表示客戶端只接受(即只消費)xml格式的響應數據;

response.getHeaders()能夠獲得響應頭,從而能夠獲得響應體的內容類型和編碼、內容長度。

 

許多開放平臺,都提供了同一種數據的多種不一樣的表現形式,此時咱們能夠根據Accept請求頭告訴它們咱們須要什麼類型的數據,他們根據咱們的Accept來判斷須要返回什麼類型的數據。

 

實際項目使用Accept請求頭是比較麻煩的,如今大多數開放平臺(國內的新浪微博、淘寶、騰訊等開放平臺)使用以下兩種方式:

擴展名:如response/ContentType.json response/ContentType.xml方式,使用擴展名錶示須要什麼類型的數據;

參數:如response/ContentType?format=json response/ContentType?format=xml,使用參數表示須要什麼類型的數據;

 

也就是說,目前咱們可使用如上三種方式實現來告訴服務器咱們須要什麼類型的數據,但麻煩的是如今有三種實現方式,難道咱們爲了支持三種類型的數據就要分別進行三種實現嗎?固然不要這麼麻煩,後續咱們會學ContentNegotiatingViewResolver,它能幫助咱們作到這一點。

 

6.6.5.二、生產者消費者流程圖

生產者消費者流程,如圖6-10:


 

從圖6-10能夠看出:

請求階段:客戶端是生產者【生產Content-Type媒體類型的請求內容區數據】,服務器是消費者【消費客戶端生產的Content-Type媒體類型的請求內容區數據】;

響應階段:服務器是生產者【生產客戶端請求頭參數Accept指定的響應體數據】,客戶端是消費者【消費服務器根據Accept請求頭生產的響應體數據】。

 

 

如上生產者/消費者寫法沒法很好的體現咱們分析的生產者/消費者模式,Spring3.1爲生產者/消費者模式提供了簡化支持,接下來咱們學習一下如何在Spring3.1中來實現生產者/消費者模式吧。

 

 

6.6.5.三、生產者、消費者限定

Spring3.1開始支持消費者、生產者限定,並且必須使用以下HandlerMapping和HandlerAdapter才支持:

 

Java代碼   收藏代碼
  1. <!--Spring3.1開始的註解 HandlerMapping -->  
  2. <bean   
  3. class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>   
  4. <!--Spring3.1開始的註解 HandlerAdapter -->  
  5. <bean   
  6. class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>   

 1、功能處理方法是消費者

@RequestMapping(value = "/consumes", consumes = {"application/json"}):此處使用consumes來指定功能處理方法能消費的媒體類型,其經過請求頭的「Content-Type」來判斷。

 

此種方式相對使用@RequestMapping的「headers = "Content-Type=application/json"」更能代表你的目的。

 

服務器控制器代碼詳解cn.javass.chapter6.web.controller.consumesproduces.ConsumesController;

客戶端代碼相似於以前的Content-Type中的客戶端,詳見ConsumesClient.java代碼。

 

 

2、功能處理方法是生產者

@RequestMapping(value = "/produces", produces = "application/json"):表示將功能處理方法將生產json格式的數據,此時根據請求頭中的Accept進行匹配,如請求頭「Accept:application/json」時便可匹配;

@RequestMapping(value = "/produces", produces = "application/xml"):表示將功能處理方法將生產xml格式的數據,此時根據請求頭中的Accept進行匹配,如請求頭「Accept:application/xml」時便可匹配。

   

此種方式相對使用@RequestMapping的「headers = "Accept=application/json"」更能代表你的目的。

 

服務器控制器代碼詳解cn.javass.chapter6.web.controller.consumesproduces.ProducesController;

客戶端代碼相似於以前的Content-Type中的客戶端,詳見ProducesController.java代碼。

 

當你有以下Accept頭:

①Accept:text/html,application/xml,application/json

      將按照以下順序進行produces的匹配 ①text/html ②application/xml ③application/json

②Accept:application/xml;q=0.5,application/json;q=0.9,text/html

      將按照以下順序進行produces的匹配 ①text/html ②application/json ③application/xml

      q參數爲媒體類型的質量因子,越大則優先權越高(從0到1)

③Accept:*/*,text/*,text/html

      將按照以下順序進行produces的匹配 ①text/html ②text/* ③*/*

 

即匹配規則爲:最明確的優先匹配。

 

代碼詳見ProducesPrecedenceController一、ProducesPrecedenceController二、ProducesPrecedenceController3。

Accept詳細信息,請參考http://tools.ietf.org/html/rfc2616#section-14.1

 

 

3、窄化時是覆蓋 而 非繼承

如類級別的映射爲 @RequestMapping(value="/narrow", produces="text/html"),方法級別的爲@RequestMapping(produces="application/xml"),此時方法級別的映射將覆蓋類級別的,所以請求頭「Accept:application/xml」是成功的,而「text/html」將報406錯誤碼,表示不支持的請求媒體類型。

 

詳見cn.javass.chapter6.web.controller.consumesproduces.NarrowController。

 

只有生產者/消費者 模式 是 覆蓋,其餘的使用方法是繼承,如headers、params等都是繼承。

 

4、組合使用是「或」的關係

@RequestMapping(produces={"text/html", "application/json"}) :將匹配「Accept:text/html」或「Accept:application/json」。

 

5、問題

消費的數據,如JSON數據、XML數據都是由咱們讀取請求的InputStream並根據須要本身轉換爲相應的模型數據,比較麻煩;

生產的數據,如JSON數據、XML數據都是由咱們本身先把模型數據轉換爲json/xml等數據,而後輸出響應流,也是比較麻煩的。

相關文章
相關標籤/搜索