一、首先要搞明白的一些事情。 1.從客戶端來看,需要搞明白: (1)要發送什麼樣格式的 JSON 數據才能被伺服器端的 SpringMVC 很便捷的處理,怎麼才能讓我們寫更少的代碼,如何做好 JSON 數據和實體之間的對應。 (2)如何組織這些發送的數據。 2.從伺服器端來看,需要搞明白: (1)S ...
一、首先要搞明白的一些事情。
1.從客戶端來看,需要搞明白:
(1)要發送什麼樣格式的 JSON 數據才能被伺服器端的 SpringMVC 很便捷的處理,怎麼才能讓我們寫更少的代碼,如何做好 JSON 數據和實體之間的對應。
(2)如何組織這些發送的數據。
2.從伺服器端來看,需要搞明白:
(1)SpringMVC 如何返回 JSON 數據。
(2)SpringMVC 如何處理請求的複雜數據。
3.$.ajax 的幾個參數:
(1)contentType:
contentType: 'application/json;charset=utf-8',作為請求頭,用來告訴伺服器消息的主體是序列化後的 JSON 字元串。除了低版本的 ie 瀏覽器外,各大瀏覽器都原生支持 JSON.stringify() 對對象進行序列化。
(2)dataType:預期伺服器返回的數據類型。
4.SpringMVC 是如何處理 JSON 數據的
5.總體的思想:
(1)SpringMVC 能完成的,儘量藉助於 SpringMVC,而不是我們手動的去解析。
(2)SpringMVC 解析不了的,儘量藉助於第三方的 Jar 包來解析。
(3)SpringMVC 和 第三方 Jar 包解決不了的時候,我們再自己去解析。
二、想要搞明白第一個問題,前提是先要搞明白第一個問題:SpringMVC 是如何處理 JSON 數據的。
1.使用 HttpMessageConverter<T> 來處理 JSON 數據的。
Spring 的 HttpMessageConverter<T> 負責將請求信息轉換為一個對象,將對象輸出為響應信息。
2.API
(1)boolean canRead(Class<?> clazz, MediaType mediaType);
轉換器是否可將請求信息轉換為 clazz 類型的對象,同時支持指定的 MIME 類型,如: text/html,application/json 等。
(2)boolean canWrite(Class<?> clazz, MediaType mediaType);
轉換器是否可以將 clazz 類型的對象寫到響應中,響應支持的類型在 mediaType 中定義。
(3)List<MediaType> getSupportedMediaTypes();
改轉換器支持的 MediaType 類型。
(4)T read(Class<? extends T> clazz, HttpInputMessage inputMessage);
將請求信息流轉換為 clazz 類型的對象。
(5)void write(T t, MediaType contentType, HttpOutputMessage outputMessage)。
將 T 類型的對象寫到響應輸出流中,同時指定 MediaType。
3.實現類
3.從上圖可以看出,Spring 預設支持使用 Jackson來處理 JSON 問題。添加 Jackson Jar 包後,來看 RequestMappingHadlerAdapter 裝配的 HttpMessageConverter:
導入的 Jackson Jar 包:
4.具體的處理方法:
(1)使用 @RequestBody 和 HttpEntity<T> 對請求進行處理。
(2)使用 @ResponseBody 和 ResponseEntity<T> 對響應進行處理。
(3)@RequestBody 對處理方法的入參進行標註。
(4)@ResponseBody 對處理方法的簽名進行標註。
(5)HttpEntity<T> 和 ResponseEntity<T> 作為處理方法的入參使用。
具體的使用方法會在下麵例子中進行說明。
5.@RequestBody 和 @ResponseBody 是可以同時使用的。
三、上面簡單介紹了 SpringMVC 是怎麼處理 JSON 數據的,現在來看第二個問題:發送什麼樣格式的 JSON 數據才能被伺服器端的 SpringMVC 很便捷的處理,這裡主要指的是請求的 JSON 字元串和實體的映射。
以一個簡單的實體為例:Person
/** * @author solverpeng * @create 2016-08-12-10:50 */ public class Person { private String name; private Integer age; public Person() { } public Person(String name, Integer age) { this.name = name; this.age = age; } @NotBlank(message = "人名不能為空") public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }Person.java
(1)對於簡單的一個Person 對象來說,我們甚至都不需要藉助於 JSON 就可以完成請求的數據與實體之間的映射。
請求:
$("#testJson").click(function () { $.ajax({ url: "testJson", type: "post", data: { name : "abc", age : "23" }, success: function (result) { console.log(result); } }); });
handler 方法:
@RequestMapping("/testJson") public Person testJson(Person person) { System.out.println("person:" + person); return person; }
(2)對於Person數組來說,需要發送什麼樣的格式才能被 SpringMVC 直接處理?
請求:
$("#testJson6").click(function () { $.ajax({ url: "testJson6", type: "post", data:'[{ "name": "Brett", "age":"12" }, { "name": "Jason", "age":"23" }, { "name": "Elliotte", "age":"33" }]', contentType: "application/json; charset=utf-8", success: function (result) { console.log(result); } }); });
handler 方法:
@RequestMapping("/testJson6") public String testJson6(@RequestBody List<Person> persons) { System.out.println("persons:" + persons); return "success"; }
註意:
(1)需要指定 "contentType",同時需要註意的是:發送的請求數據不在 Form data 中,而是在 Request Payload 中。關於 [Request Payload] ,在後面說明。
(2)必須要指定 @RequestBody ,否則無法解析。
四、第三個問題:如何組織這些數據以及SpringMVC 如何處理這些數據,做好映射。
(1)說明:
上面說的兩個例子,僅僅是最簡單的一種形式。現在對其進行擴展,在四里,所說的 SpringMVC 如何處理這些數據,不僅僅指的是SpringMVC,也包括SpringMVC處理不了,使用第三方來處理,或者第三方處理不了,我自己來處理。
同時這裡的數據也不僅僅指的 JSON 類型的數據。
(2)對於非表單的 Ajax 提交,這裡只提供比較簡單的一種方式。還是以上面的 Person 為例。
e1:
數據的組織與請求的發送:
var personList = []; personList.push({name: "李四",age: "23"}); personList.push({name: "張三",age: "12"}); $("#testJson5").click(function () { $.ajax({ type: "POST", url: "testJson5", data: JSON.stringify(personList),//將對象序列化成JSON字元串 contentType: 'application/json;charset=utf-8', //設置請求頭信息 success: function (data) { }, error: function (res) { } }); });
handler 方法:
@RequestMapping("/testJson5") public String testJson5(@RequestBody List<Person> persons) { System.out.println(persons); return "success"; }
(3)基於表單的 Ajax 提交。
提供一個序列化方法:
$.fn.serializeObject = function() { var o = {}; var a = this.serializeArray(); $.each(a, function() { if (o[this.name] !== undefined) { if (!o[this.name].push) { o[this.name] = [o[this.name]]; } o[this.name].push(this.value || ''); } else { o[this.name] = this.value || ''; } }); return o; };
還有一種序列化方式:
★單表單情況:
表單:
<form action="" method="post"> First Name:<input type="text" name="firstName" maxlength="12" size="12"/> <br/> Last Name:<input type="text" name="lastName" maxlength="36" size="12"/> <br/> Gender:<br/> Male:<input type="radio" name="gender" value="1"/><br/> Female:<input type="radio" name="gender" value="0"/><br/> Favorite Food:<br/> Steak:<input type="checkbox" name="foods" value="Steak"/><br/> Pizza:<input type="checkbox" name="foods" value="Pizza"/><br/> Chicken:<input type="checkbox" name="foods" value="Chicken"/><br/> <textarea wrap="physical" cols="20" name="quote" rows="5">Enter your favorite quote!</textarea><br/> Select a Level of Education:<br/> <select name="education"> <option value="Jr.High">Jr.High</option> <option value="HighSchool">HighSchool</option> <option value="College">College</option> </select><br/> Select your favorite time of day:<br/> <select size="3" name="tOfD"> <option value="Morning">Morning</option> <option value="Day">Day</option> <option value="Night">Night</option> </select> <p><input type="submit"/></p> </form>
對應的實體:
/** * @author solverpeng * @create 2016-08-16-11:14 */ public class Student { private String firstName; private String lastName; private Integer gender; private List<String> foods; private String quote; private String education; private String tOfD; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Integer getGender() { return gender; } public void setGender(Integer gender) { this.gender = gender; } public List<String> getFoods() { return foods; } public void setFoods(List<String> foods) { this.foods = foods; } public String getQuote() { return quote; } public void setQuote(String quote) { this.quote = quote; } public String getEducation() { return education; } public void setEducation(String education) { this.education = education; } public String gettOfD() { return tOfD; } public void settOfD(String tOfD) { this.tOfD = tOfD; } @Override public String toString() { return "Student{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", gender=" + gender + ", foods=" + foods + ", quote='" + quote + '\'' + ", education='" + education + '\'' + ", tOfD='" + tOfD + '\'' + '}'; } }Student.java
e1:使用 serializeObject()
序列化後的值:
JSON.stringify($('form').serializeObject()):
{"firstName":"jack","lastName":"lily","gender":"1","foods":["Pizza","Chicken"],"quote":"hello hello","education":"Jr.High","tOfD":"Day"}
請求:
$(function() { $('form').submit(function() { $.ajax({ url : "testStudent", data : JSON.stringify($('form').serializeObject()), contentType : "application/json;charset=utf-8", type : "post", success : function (result) { console.log(result); } }); return false; }); });
e11:SpringMVC自身進行處理
handler 方法:
@RequestMapping("/testStudent") public String testStudent(@RequestBody Student student) { System.out.println(student); return "success"; }
e12:引入第三方 Jar 包進行處理。
準備:
導入 sl4j jar 包,同時添加 JsonUtil 工具類。
public final class JsonUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); /** * 將 POJO 轉換為 JSON */ public static <T> String toJson(T obj) { String json; try { json = OBJECT_MAPPER.writeValueAsString(obj); } catch(JsonProcessingException e) { LOGGER.error("convert POJO to JSON failure", e); throw new RuntimeException(e); } return json; } /** * 將 JSON 轉換為 POJO */ public static <T> T fromJson(String json, Class<T> type) { T pojo; try { pojo = OBJECT_MAPPER.readValue(json, type); } catch(IOException e) { LOGGER.error("convert JSON to POJO failure", e); throw new RuntimeException(e); } return pojo; } }JsonUtil.java
後端處理:
@RequestMapping("/testStudent") public String testStudent(@RequestBody String inputBody) { Student student = JsonUtil.fromJson(inputBody, Student.class); System.out.println(student); return "success"; }
都可以正常列印 Student 對象。
e2:使用 serialize()
序列化後的值:
$('form').serialize():
firstName=jack&lastName=lily&gender=1&foods=Pizza&foods=Chicken"e=hello+hello&education=Jr.High&tOfD=Day
請求:
$(function() { $('form').submit(function() { $.ajax({ url : "testStudent", data : $('form').serialize(), type : "post", success : function (result) { console.log(result); } }); return false; }); });
handler 方法:
@RequestMapping("/testStudent") public String testStudent(Student student) { System.out.println(student); return "success"; }
可以正常列印 Student 對象。
e1 和 e2 對比說明:
e1提交的 JSON 數據,e2 提交的不是 JSON 格式的數據。e1 的請求參數存放在 [Request Payload] 中,而 e2 的請求參數存放在 Form Data 中。
★單表單複雜數據
表單還是上面的 Student 表單,但是在表單外增加了:
<span id="amount">33</span>
需求是:通過 Ajax 發送表單數據的同時,同時發送 "amount" 。
經過測試,我就直接說結論了,有興趣的童鞋可以自行探索,有新的發現歡迎和我交流。
結論:
不能對這樣的數據,指定 "contentType:application/json",否則後端SpringMVC或者第三方的Jar 包 不能進行自動的解析,增加瞭解析的複雜度,所以將 json 串傳入後臺,在後臺進行解析。
e1:serializeObject()
請求:
$(function() { $('form').submit(function() { $.ajax({ url : "testStudent", data : { amount : $("#amount").text(), student : JSON.stringify($('form').serializeObject()) }, type : "post", success : function (result) { console.log(result); } }); return false; }); });
後端處理:使用第三方工具類進行解析
@RequestMapping("/testStudent") public String testStudent(@RequestParam("student") String studentStr, String amount) { Student student = JsonUtil.fromJson(studentStr, Student.class); System.out.println("student:" + student); System.out.println("amount:" + amount); return "success"; }
可以正常列印。
e2:serialize()
請求:
$(function() { $('form').submit(function() { $.ajax({ url : "testStudent", data : { amount : $("#amount").text(), student : $('form').serialize() }, type : "post", success : function (result) { console.log(result); } }); return false; }); });
Handler 方法:
e1:嘗試讓 SpringMVC 來解析:
@RequestMapping("/testStudent") public String testStudent(@RequestParam("student") Student student, String amount) { System.out.println("student:" + student); System.out.println("amount:" + amount); return "success"; }
結果:請求無法到達 handler 方法
e2:
@RequestMapping("/testStudent") public String testStudent(Student student, String amount) { System.out.println("student:" + student); System.out.println("amount:" + amount); return "success"; }
結果:請求可以正常到達目標 Handler 方法,但是無法映射 Student 對象。
方案:自己解析,編寫自定義的類型轉換器:
public class String2StudentConverter implements Converter<String, Student>{ @Override public Student convert(String source) { return InjectUtil.convert2Obj(source, Student.class); } }
這裡我編寫了一個通用的類型轉換器:
用來轉換形如:
firstName=jack&lastName=lily&gender=1&foods=Steak&foods=Pizza"e=Enter+your+favorite+quote!&education=Jr.High&tOfD=Day 到 Student 對象。
/** * @author solverpeng * @create 2016-08-22-17:37 */ public final class InjectUtil<T> { private static final Logger LOGGER = LoggerFactory.getLogger(InjectUtil.class); public static <T> T converter2Obj(String source, Class<T> tClass) { T t = null; try { t = tClass.newInstance(); Map<String, Object> params = new HashMap<String, Object>(); if(source != null && source.length() > 0) { String[] fields = source.split("&"); for(String field : fields) { String[] fieldKeyValue = field.split("\\="); String fieldKey = fieldKeyValue[0]; String fieldValue = fieldKeyValue[1]; if (params.containsKey(fieldKey)) { Object keyValueRetrieved = params.get(fieldKey); if (keyValueRetrieved instanceof String) { ArrayList<String> values = new ArrayList<>(); values.add(keyValueRetrieved.toString()); values.add(fieldValue); params.put(fieldKey, values); } else { ((ArrayList<String>) keyValueRetrieved).add(fieldValue); } } else { params.put(fieldKey, fieldValue); } } } BeanUtils.populate(t, params); } catch(InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); LOGGER.error("String convert to Bean failure!", e); } return t; } }
不要忘記在 SpringMVC 中添加自定義的轉換器。
e3:也可以在 handler 方法中來調用上面我編寫的通用的類型轉換器來完成解析。
@RequestMapping("/testStudent") public String testStudent(@RequestParam("student") String studentStr, String amount) { System.out.println("studentStr:" + studentStr); System.out.println("amount:" + amount); return "success"; }
說明:對於複雜數據來說,我們藉助不了 SpringMVC,只能藉助於第三方,或是自己來編寫解析器來解析。
★多表單一次提交
表單數據:
<form action="" method="post" id="form2"> First Name:<input type="text" name="firstName" maxlength="12" size="12"/> <br/> Last Name:<input type="text" name="lastName" maxlength="36" size="12"/> <br/> Gender:<br/> Male:<input type="radio" name="gender" value="1"/><br/> Female:<input type="radio" name="gender" value="0"/><br/> <%–Favorite Food:<br/> Steak:<input type="checkbox" name="foods" value="Steak"/><br/> Pizza:<input type="checkbox" name="foods" value="Pizza"/><br/> Chicken:<input type="checkbox" name="foods" value="Chicken"/><br/>–%> <textarea wrap="physical" cols="20" name="quote" rows="5">Enter your favorite quote!</textarea><br/> Select a Level of Education:<br/> <select name="education"> <option value="Jr.High">Jr.High</option> <option value="HighSchool">HighSchool</option> <option value="College">College</option> </select><br/> Select your favorite time of day:<br/> <select size="3" name="tOfD"> <option value="Morning">Morning</option> <option value="Day">Day</option> <option value="Night">Night</option> </select> <p><input type="submit"/></p> </form> <form action="" method="post" id="form1"> First Name:<input type="text" name="firstName" maxlength="12" size="12"/> <br/> Last Name:<input type="text" name="lastName" maxlength="36" size="12"/> <br/> Gender:<br/> Male:<input type="radio" name="gender" value="1"/><br/> Female:<input type="radio" name="gender" value="0"/><br/> <%– Favorite Food:<br/> Steak:<input type="checkbox" name="foods" value="Steak"/><br/> Pizza:<input type="checkbox" name="foods" value="Pizza"/><br/> Chicken:<input type="checkbox" name="foods" value="Chicken"/><br/>–%> <textarea wrap="physical" cols="20" name="quote" rows="5">Enter your favorite quote!</textarea><br/> Select a Level of Education:<br/> <select name="education"> <option value="Jr.High">Jr.High</option> <option value="HighSchool">HighSchool</option> <option value="College">College</option> </select><br/> Select your favorite time of day:<br/> <select size="3" name="tOfD">