天天看點

JOSN處理和HttpMessageConverter< T>

作者:黑芝麻湯圓他爹

1.JSON處理-@ResponseBody

說明:在實際開發中,我們往往需要伺服器傳回的資料都是 JSON 格式。

SpringMVC 提供了 @ResponseBody 注解,用來标注 Controller 方法的傳回的格式為 JSON,将 Java 對象或集合轉為 JSON 格式的資料。

方法傳回的對象通過适當的轉換器轉換為指定的格式之後,寫入到 response 對象的 body 區,通常用來傳回 JSON 資料或者是 XML 資料。

注意:在使用此注解之後不會再走視圖處理器,而是直接将資料寫入到輸入流中,它的效果等同于通過 response對象輸出指定格式的資料。

使用案例

下面是要完成的效果:請求目标方法,目标方法傳回一個json格式的資料

JOSN處理和HttpMessageConverter< T>
JOSN處理和HttpMessageConverter< T>

(1)引入處理JSON需要的jar包,注意spring5.x 需要使用jackson-2.9.x.jar 的包。

JOSN處理和HttpMessageConverter< T>

(2)建立 json.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>json送出</title>    <!--引入jquery-->    <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>    <!--使用jquery的ajax-->    <script type="text/javascript">        $(function () {            //給id=getJson的超連結綁定一個事件            $("#getJson").click(function () {                var href = this.href;                var args = {"time": new Date()};                $.get(                    href,//請求url                    args,//發送時間,防止浏覽器緩存                    function (data) {                        console.log("data=", data)                    },                    "json"//指定傳回的格式                )                return false;//超連結不跳轉            })        })    </script></head><body><h1>請求一個json資料</h1><a href="<%=request.getContextPath()%>/json/dog" id="getJson">點選擷取json資料</a></body></html>           

(3)建立 Javabean 作為傳回的資料

package com.li.web.json.entity; /** * @author 李 * @version 1.0 */public class Dog {    private String name;    private String address;     public Dog() {    }     public Dog(String name, String address) {        this.name = name;        this.address = address;    }     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public String getAddress() {        return address;    }     public void setAddress(String address) {        this.address = address;    }     @Override    public String toString() {        return "Dog{" +                "name='" + name + '\'' +                ", address='" + address + '\'' +                '}';    }}           

(4)建立 JsonHandler 處理請求

package com.li.web.json; import com.li.web.json.entity.Dog;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody; /** * @author 李 * @version 1.0 */@Controllerpublic class JsonHandler {    /**     * 1.目标方法的 @ResponseBody注解表示傳回的資料是json格式     * 2.SpringMVC 底層根據目标方法@ResponseBody,傳回指定格式     *  傳回的格式根據http請求來處理     * 3.底層原理之前在實作SpringMVC底層機制時講過,     *  這裡原生的SpringMVC使用了轉換器 HttpMessageConverter     * @return     */    @RequestMapping(value = "/json/dog")    @ResponseBody    public Dog getJson(){        //傳回對象        //SpringMVC 會根據你的設定,轉成json格式資料傳回        Dog dog = new Dog();        dog.setName("大黃狗");        dog.setAddress("小新的家");        return dog;    }}           

(5)啟動tomcat,通路json.jsp頁面。點選超連結,傳回如下資訊:

JOSN處理和HttpMessageConverter&lt; T&gt;

2.JSON處理-@RequestBody

和 @ResponseBody 注解相反,@RequestBody 注解是将用戶端送出的 json資料,封裝成 Javabean 對象。

注意:@RequestBody 用于修飾參數。

應用案例

在前端頁面發出一個json資料,後端接收資料,并使用 @RequestBody 注解将 json 資料轉成 Javabean 對象,然後使用 @ResponseBody 注解将該 Javabean 對象轉回 json 資料,傳回給前端。

(1)修改json.jsp,增加發送 json 資料代碼

這裡使用 ajax 請求的 contentType 指定發送格式為 json,發送請求時會被封裝到請求頭中。這樣後端在接收時,根據 contentType 能知道資料是 json 格式的。

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>json送出</title>    <!--引入jquery-->    <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>    <!--使用jquery的ajax-->    <script type="text/javascript">        $(function () {            //綁定按鍵點選事件,送出json資料            $("button[name='but1']").click(function () {                var url = "/springmvc/save2";                var username = $("#username").val();                var age = $("#age").val();                //将 username和 age封裝成 json字元串                var args = {"username": username, "age": age};//json對象                //将json對象封裝成json字元串                var jsonString = JSON.stringify(args);//json字元串                $.ajax({                    url:url,                    data:jsonString,                    type:"POST",                    success:function (data) {                        console.log("傳回的data=",data)                    },                    //指定發送資料的編碼和格式                    contentType:"application/json;charset=utf-8"                })            })        })    </script></head><body><h1>發出一個json資料</h1>u:<input id="username" type="text"/><br/>a:<input id="age" type="text"/><br/><button name="but1">添加使用者</button></body></html>           

(2)User.java

package com.li.web.json.entity; /** * @author 李 * @version 1.0 */public class User {    private String username;    private Integer age;     public User() {    }     public User(String username, Integer age) {        this.username = username;        this.age = age;    }     public String getUsername() {        return username;    }     public void setUsername(String username) {        this.username = username;    }     public Integer getAge() {        return age;    }     public void setAge(Integer age) {        this.age = age;    }     @Override    public String toString() {        return "User{" +                "username='" + username + '\'' +                ", age=" + age +                '}';    }}           

(3)JsonHandler.java

package com.li.web.json; import com.li.web.json.entity.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody; /** * @author 李 * @version 1.0 */@Controllerpublic class JsonHandler {    /**     * 1.在形參指定了 @RequestBody 注解     * 2.SpringMVC 就會将送出的json字元串資料填充給指定的Javabean     * @param user 指定的Javabean對象     * @return 這裡使用了 @ResponseBody 注解,是以傳回的也是json     */    @RequestMapping(value = "/save2")    @ResponseBody    public User save2(@RequestBody User user) {        //将前端傳過來的資料以json的格式傳回浏覽器        System.out.println("user=" + user);        return user;    }}           

(4)啟動tomcat,通路json.jsp,送出和傳回的資料如下:

JOSN處理和HttpMessageConverter&lt; T&gt;

背景輸出:

JOSN處理和HttpMessageConverter&lt; T&gt;

說明後端成功拿到了前端發送的 json 資料,并将其填充到了指定的Javabean對象中。然後又将 Javabean 對象轉成了 json 格式的資料傳回前端。

3.JSON處理-注意事項和細節

  1. 目标方法正常傳回 Json 需要的資料,可以是一個對象,也可以是一個集合
  2. @ResponseBody 注解也可以用于修飾于 Controller 上,這樣會對 Controller 中所有方法生效
  3. @ResponseBody 和 @Controller 可以直接寫成一個注解 @RestController

4.HttpMessageConverter< T>

  • 基本說明

SpringMVC 處理 JSON 底層實作是依靠 HttpMessageConverter<T>來進行轉換的

  • 工作機制簡圖
JOSN處理和HttpMessageConverter&lt; T&gt;

整個過程為:

請求封包發送到後端,HttpInputMessage 的實作子類會将請求封包封裝起來,然後找到對應的轉換器進行轉換,然後封裝成對應的 Java 對象給到目标方法。

目标方法傳回,找到指定格式對應的消息轉換器,消息轉換器将傳回的資料再進行轉換,将轉換後的資料封裝到 HttpOutputMessage ,然後傳回給用戶端。

這裡的 HttpMessageConverter(消息轉換器),HttpInputMessage 和 HttpOutputMessage 都是接口,下面有很多實作子類,會根據請求/響應封包頭來找到比對子類。
  • 處理JSON-底層實作(HttpMessageConverter< T>)
  1. 使用 HttpMessageConverter<T> 将請求資訊轉換并綁定到處理方法的入參中,或将響應結果轉為對應類型的響應資訊,Spring 提供了兩種途徑:使用 @RequestBody / @ResponseBody 對目标方法或方法參數進行标注使用 HttpEntity<T> / ResponseEntity<T> 作為目标方法的入參或傳回值
  2. 當控制器處理方法使用到 @RequestBody / @ResponseBody 或 HttpEntity<T> / ResponseEntity<T>時,Spring 首先根據請求頭或響應頭的 Accept 屬性選擇比對的 HttpMessageConverter,進而根據參數類型或泛型類型的過濾得到比對的消息轉換器 HttpMessageConverter,若找不到可用的消息轉換器,将會報錯。
debug源碼

以處理JSON-@RequestBody的例子為案例:

(1)快捷鍵 ctrl+n 在 IDEA 中搜尋 AbstractJackson2HttpMessageConverter 類,在該類的 readJavaType() 方法中打上斷點,點選 debug

JOSN處理和HttpMessageConverter&lt; T&gt;

(2)浏覽器通路 json.jsp 頁面,填寫資料并點選按鈕

JOSN處理和HttpMessageConverter&lt; T&gt;

(3)背景光标跳轉到斷點處,此時方法參數 inputMessage 的資料如下,說明此時 http 請求的内容已經被封裝到了給對象中。

JOSN處理和HttpMessageConverter&lt; T&gt;

方法參數 javaType 用于指定 inputMessage 的相應資料填充到什麼樣的 Java 類型中

JOSN處理和HttpMessageConverter&lt; T&gt;

(4)點選 step over,方法首先擷取http請求資料指定的類型 contentType

JOSN處理和HttpMessageConverter&lt; T&gt;

(5)在擷取了指定的資料類型後,将資料進行轉換

JOSN處理和HttpMessageConverter&lt; T&gt;

(6)點選 step over:

JOSN處理和HttpMessageConverter&lt; T&gt;

(7)在目标方法中添加斷點,點選 resume,可以看到光标跳轉到斷點處,此時目标方法就拿到了由 json 資料填充後的 Javabean 對象

JOSN處理和HttpMessageConverter&lt; T&gt;
JOSN處理和HttpMessageConverter&lt; T&gt;

(8)在 AbstractJackson2HttpMessageConverter 類的 writeInternal() 方法中打上斷點,點選 resume,光标跳轉到如下位置:

由于我們在目标方法使用了 @ResponseBody 注解,是以傳回的時候不再會進入視圖處理器,而是通過消息轉換器,将傳回的 user 對象轉換為指定的 json 格式資料。下圖的 HttpOutputMessage 對象就是用于存放轉換後的資料。

JOSN處理和HttpMessageConverter&lt; T&gt;

擷取要轉換的格式:

JOSN處理和HttpMessageConverter&lt; T&gt;

使用 selectObjectMapper 方法将對象轉換為指定格式:

JOSN處理和HttpMessageConverter&lt; T&gt;

後面就是進行一些處理,然後将資料傳回給用戶端。

(9)然後浏覽器獲得指定格式的資料:

JOSN處理和HttpMessageConverter&lt; T&gt;

5.檔案下載下傳-ResponseEntity< T>

  • 說明

在SpringMVC 中,通過傳回 ResponseEntity<T> 的類型,可以實作檔案的下載下傳功能

使用 ResponseEntity< T> 下載下傳檔案,底層仍然是上面 HttpMessageConverter< T> 的機制。
使用案例

實作效果如下:在前端頁面點選超連結,可以實作檔案下載下傳。

JOSN處理和HttpMessageConverter&lt; T&gt;

在項目的web目錄下建立一個img目錄,放入一張圖檔作為要下載下傳的檔案

JOSN處理和HttpMessageConverter&lt; T&gt;

(1)修改 json.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>檔案下載下傳</title></head><body><h1>下載下傳檔案的測試</h1><a href="<%=request.getContextPath()%>/downFile">點選下載下傳檔案</a></body></html>           

(2)修改 JsonHandler.java,增加方法,用于響應下載下傳檔案的請求

檔案下載下傳響應頭的設定:

  • content-type:設定響應内容的格式
  • content-disposition:設定如何處理響應的内容,一般有兩種方式:
    • inline:直接在頁面顯示
    • attchment:以附件形式下載下傳
package com.li.web.json; import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession;import java.io.IOException;import java.io.InputStream; /** * @author 李 * @version 1.0 */@Controllerpublic class JsonHandler {    /**     * 這裡的建構形參session主要是為了擷取輸出流     *     * @param session     * @return     */    @RequestMapping(value = "/downFile")    public ResponseEntity<byte[]> downFile(HttpSession session) throws IOException {        //先擷取下載下傳檔案的 InputStream        InputStream resourceAsStream =                session.getServletContext().getResourceAsStream("/img/view.jpg");        //開辟 byte 數組        //resourceAsStream.available() 傳回可讀取的位元組數的估計值        byte[] bytes = new byte[resourceAsStream.available()];        //将要下載下傳的檔案資料儲存到 bytes 數組        resourceAsStream.read(bytes);        /**         * ResponseEntity 的構造器:         * public ResponseEntity(@Nullable T body,         *                      @Nullable MultiValueMap<String, String> headers,         *                      HttpStatus status)         *     {         *        this(body, headers, (Object) status);         *     }         *  body 就是要回送的資料内容         *  headers 就是回送資料的編碼以及應該以何種方式進行處理         *  status 代表狀态         */        //1.建立傳回的 HttpStatus status        HttpStatus httpStatus = HttpStatus.OK;        //2.建立 headers,指定浏覽器應當以附件形式操作傳回的資料        HttpHeaders httpHeaders = new HttpHeaders();        httpHeaders.add("Content-Disposition", "attachment;filename=view.jpg");        //3.建構 ResponseEntity 對象        ResponseEntity<byte[]> responseEntity =                new ResponseEntity<>(bytes, httpHeaders, httpStatus);        return responseEntity;    }}           

(3)測試,點選下載下傳連結,顯示成功下載下傳圖檔

tomcat 真正的工作目錄是 out,要保證 out 目錄下存在該檔案,才能下載下傳成功
JOSN處理和HttpMessageConverter&lt; T&gt;
JOSN處理和HttpMessageConverter&lt; T&gt;

6.練習

  1. 将之前的資料格式化、驗證以及國際化、json 處理、檔案下載下傳相關代碼自己過一遍
  2. 将 debug 過的 HttpMessageConverter 源碼再走一遍,加深了解
  3. 畫出資料類型轉換校驗核心類 DataBinder 的工作機制示意圖
  4. debug 一下 validate 得到驗證 errors 資訊,加深了解

繼續閱讀