1.JSON處理-@ResponseBody
說明:在實際開發中,我們往往需要伺服器傳回的資料都是 JSON 格式。
SpringMVC 提供了 @ResponseBody 注解,用來标注 Controller 方法的傳回的格式為 JSON,将 Java 對象或集合轉為 JSON 格式的資料。
方法傳回的對象通過适當的轉換器轉換為指定的格式之後,寫入到 response 對象的 body 區,通常用來傳回 JSON 資料或者是 XML 資料。
注意:在使用此注解之後不會再走視圖處理器,而是直接将資料寫入到輸入流中,它的效果等同于通過 response對象輸出指定格式的資料。
使用案例
下面是要完成的效果:請求目标方法,目标方法傳回一個json格式的資料
(1)引入處理JSON需要的jar包,注意spring5.x 需要使用jackson-2.9.x.jar 的包。
(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頁面。點選超連結,傳回如下資訊:
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,送出和傳回的資料如下:
背景輸出:
說明後端成功拿到了前端發送的 json 資料,并将其填充到了指定的Javabean對象中。然後又将 Javabean 對象轉成了 json 格式的資料傳回前端。
3.JSON處理-注意事項和細節
- 目标方法正常傳回 Json 需要的資料,可以是一個對象,也可以是一個集合
- @ResponseBody 注解也可以用于修飾于 Controller 上,這樣會對 Controller 中所有方法生效
- @ResponseBody 和 @Controller 可以直接寫成一個注解 @RestController
4.HttpMessageConverter< T>
- 基本說明
SpringMVC 處理 JSON 底層實作是依靠 HttpMessageConverter<T>來進行轉換的
- 工作機制簡圖
整個過程為:
請求封包發送到後端,HttpInputMessage 的實作子類會将請求封包封裝起來,然後找到對應的轉換器進行轉換,然後封裝成對應的 Java 對象給到目标方法。
目标方法傳回,找到指定格式對應的消息轉換器,消息轉換器将傳回的資料再進行轉換,将轉換後的資料封裝到 HttpOutputMessage ,然後傳回給用戶端。
這裡的 HttpMessageConverter(消息轉換器),HttpInputMessage 和 HttpOutputMessage 都是接口,下面有很多實作子類,會根據請求/響應封包頭來找到比對子類。
- 處理JSON-底層實作(HttpMessageConverter< T>)
- 使用 HttpMessageConverter<T> 将請求資訊轉換并綁定到處理方法的入參中,或将響應結果轉為對應類型的響應資訊,Spring 提供了兩種途徑:使用 @RequestBody / @ResponseBody 對目标方法或方法參數進行标注使用 HttpEntity<T> / ResponseEntity<T> 作為目标方法的入參或傳回值
- 當控制器處理方法使用到 @RequestBody / @ResponseBody 或 HttpEntity<T> / ResponseEntity<T>時,Spring 首先根據請求頭或響應頭的 Accept 屬性選擇比對的 HttpMessageConverter,進而根據參數類型或泛型類型的過濾得到比對的消息轉換器 HttpMessageConverter,若找不到可用的消息轉換器,将會報錯。
debug源碼
以處理JSON-@RequestBody的例子為案例:
(1)快捷鍵 ctrl+n 在 IDEA 中搜尋 AbstractJackson2HttpMessageConverter 類,在該類的 readJavaType() 方法中打上斷點,點選 debug
(2)浏覽器通路 json.jsp 頁面,填寫資料并點選按鈕
(3)背景光标跳轉到斷點處,此時方法參數 inputMessage 的資料如下,說明此時 http 請求的内容已經被封裝到了給對象中。
方法參數 javaType 用于指定 inputMessage 的相應資料填充到什麼樣的 Java 類型中
(4)點選 step over,方法首先擷取http請求資料指定的類型 contentType
(5)在擷取了指定的資料類型後,将資料進行轉換
(6)點選 step over:
(7)在目标方法中添加斷點,點選 resume,可以看到光标跳轉到斷點處,此時目标方法就拿到了由 json 資料填充後的 Javabean 對象
(8)在 AbstractJackson2HttpMessageConverter 類的 writeInternal() 方法中打上斷點,點選 resume,光标跳轉到如下位置:
由于我們在目标方法使用了 @ResponseBody 注解,是以傳回的時候不再會進入視圖處理器,而是通過消息轉換器,将傳回的 user 對象轉換為指定的 json 格式資料。下圖的 HttpOutputMessage 對象就是用于存放轉換後的資料。
擷取要轉換的格式:
使用 selectObjectMapper 方法将對象轉換為指定格式:
後面就是進行一些處理,然後将資料傳回給用戶端。
(9)然後浏覽器獲得指定格式的資料:
5.檔案下載下傳-ResponseEntity< T>
- 說明
在SpringMVC 中,通過傳回 ResponseEntity<T> 的類型,可以實作檔案的下載下傳功能
使用 ResponseEntity< T> 下載下傳檔案,底層仍然是上面 HttpMessageConverter< T> 的機制。
使用案例
實作效果如下:在前端頁面點選超連結,可以實作檔案下載下傳。
在項目的web目錄下建立一個img目錄,放入一張圖檔作為要下載下傳的檔案
(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 目錄下存在該檔案,才能下載下傳成功
6.練習
- 将之前的資料格式化、驗證以及國際化、json 處理、檔案下載下傳相關代碼自己過一遍
- 将 debug 過的 HttpMessageConverter 源碼再走一遍,加深了解
- 畫出資料類型轉換校驗核心類 DataBinder 的工作機制示意圖
- debug 一下 validate 得到驗證 errors 資訊,加深了解