SpringMVC數(shù)據(jù)綁定
基本類型
對(duì)于基本類型是用int型還是Integer型?
如果傳入int型參數(shù):
1. 只能是int類型的,如果傳入age="abc",如訪問http://localhost/root?age=abc,會(huì)報(bào)400參數(shù)異常
2. int型:key是必傳的,如果不傳入,如訪問http://localhost/root,會(huì)報(bào)500內(nèi)部錯(cuò)誤異常。
如果傳入Integer類型,這是一個(gè)對(duì)int的包裝類,本質(zhì)上是一個(gè)對(duì)象:
可以不傳key,那么訪問http://localhost/root,則age屬性為null即`age: null`
對(duì)于可能為空的數(shù)據(jù),最好使用包裝類型。還有一點(diǎn),int型的默認(rèn)值為0,Integer的默認(rèn)值為null,如果數(shù)據(jù)庫某字段允許NULL值,使用int型的話,存入的將是0而不是NULL。
舉個(gè)例子,有這么一個(gè)方法。
@PostMapping("/getAge")
public String getAge(int age) {
return "age: " + age;
}
當(dāng)我們不傳age字段時(shí),因?yàn)閰?shù)是基本數(shù)據(jù)類型,當(dāng)然會(huì)報(bào)500內(nèi)部錯(cuò)誤;當(dāng)表單傳輸?shù)淖侄蚊麨閍ge時(shí),會(huì)自動(dòng)綁定到Controller方法中的同名參數(shù)。如果把參數(shù)名改一改,
@PostMapping("/getAge")
public String getAge(int a) {
return "age: " + a;
}
當(dāng)表單傳入age=66,此時(shí)在方法中找不到同名參數(shù),自動(dòng)綁定就會(huì)失效。如果像上面一樣用的是int型,則會(huì)報(bào)500錯(cuò)誤。并給出很貼心的錯(cuò)誤提示如下
Optional int parameter 'age' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
大概意思是說不能將age轉(zhuǎn)換成null值,因?yàn)樗宦暶鳛橐粋€(gè)原始類型了。并提示我們可以使用其相應(yīng)的包裝類。好,按照提示改成Integer,再運(yùn)行上面程序。
不報(bào)錯(cuò)了,響應(yīng)如下內(nèi)容,說明數(shù)據(jù)綁定失效,雖然傳入了age=66,但找不到同名參數(shù),不能綁定到方法參數(shù)a上,所以參數(shù)a只好綁定null。而且,即使我們不傳入age字段,也不會(huì)報(bào)錯(cuò),響應(yīng)內(nèi)容依然和下面一樣。(這就是推薦使用Integer而不使用int的原因之一)
age: null
可以使用@RequstParam將請(qǐng)求參數(shù)綁定到參數(shù)上,value屬性明確指定表單字段的名稱。
@PostMapping("/getAge")
public String getAge(@RequestParam(value = "age") Integer a) {
return "age: " + a;
}
用value來明確指定表單傳入的字段名,這個(gè)注解的意思是,將名為age字段值綁定到參數(shù)a上,使用@RequestParam不要求方法參數(shù)名和字段名一樣,使得數(shù)據(jù)綁定時(shí)候更加靈活。注意當(dāng)只有一個(gè)value屬性時(shí),可省略value =。該注解還有幾個(gè)比較重要的屬性
- name:是value的別名,用哪個(gè)都可以
- required:該字段是否必傳,默認(rèn)是true,如果不傳會(huì)報(bào)錯(cuò);改為false說明在前臺(tái)不必傳該子彈。
- defaultValue:字段的默認(rèn)值
來試一下,
@PostMapping("/getAge")
public String getAge(@RequestParam(value = "age", required = false, defaultValue = "5") Integer a) {
return "age: " + a;
}
當(dāng)訪問http://localhost:8080/getAge,由于不必傳age字段,而且指定了age的默認(rèn)值為5,所以響應(yīng)是
age: 5
數(shù)組
當(dāng)傳入的多個(gè)字段key都是同一個(gè)名稱如下面的name,則會(huì)綁定到方法中的數(shù)組參數(shù)中。

@PostMapping("/array")
public String array(@RequestParam("name") String[] names) {
StringBuilder sb = new StringBuilder();
for (String name : names) {
sb.append(name).append(" ");
}
return sb.toString();
}
響應(yīng)結(jié)果如下
abc def ghi
當(dāng)然如果是GET方式請(qǐng)求,訪問http://localhost/array?name=abc&name=def&name=ghi 也是可以的。
SpringMVC可以將同一個(gè)字段key的多個(gè)值映射到一個(gè)數(shù)組中,那自然也能映射到List中。將參數(shù)String[] names改成List<String> names,完全沒問題。
對(duì)象以及多層級(jí)對(duì)象
SpringMVC可以將表單的字段與Controller方法參數(shù)中的對(duì)象屬性進(jìn)行映射,前提是兩者的名稱和類型一致。
比如表單中傳入name=Jim&age=33,方法參數(shù)中的User對(duì)象也恰好有String name與Integer age屬性,SpringMVC將完成字段 --> 對(duì)象屬性的映射并將數(shù)據(jù)綁定到User對(duì)象的對(duì)應(yīng)屬性上。
這里User類,有name和age字段,還有一個(gè)對(duì)象ContactInfo。
package com.shy.springmvcbingding.domain;
public class User {
private String name;
private Integer age;
private ContactInfo contactInfo;
// getter和setter方法省略
}
package com.shy.springmvcbingding.domain;
public class ContactInfo {
private String address;
private String phone;
// getter和setter方法省略
}
在Controller中,方法參數(shù)可以是對(duì)象
// Controller被@RestController修飾
@PostMapping("/user")
public User getUser(User user) {
return user;
}
如果我們傳入以下參數(shù)

響應(yīng)輸出為
{
"name": "Jim",
"age": 22,
"contactInfo": {
"address": "China",
"phone": "13345678910"
}
}
SpringMVC將我們輸入字段的類型、名稱與方法參數(shù)中User對(duì)象中的屬性一一對(duì)比,如果吻合,將會(huì)自動(dòng)映射過去并封裝成對(duì)象,如果某些子彈和對(duì)象的屬性不能匹配,封裝成的對(duì)象中該屬性為null。比如上面不小心將contactInfo.address寫成了contactInfo.addr,響應(yīng)是下面這樣的,可以看到contactInfo的address字段為null了。
{
"name": "Jim",
"age": 33,
"contactInfo": {
"address": null,
"phone": "13345678910"
}
}
對(duì)于多層級(jí)的對(duì)象,如User中的ContactInfo類,該類又有address和phone兩個(gè)屬性。對(duì)于這種多層級(jí)的對(duì)象,需要使用.取到具體的屬性。比如user.contactInfo.phone表示user實(shí)體中cantactInfo中的phone字段。因此在請(qǐng)求getUser方法時(shí),若要將字段值注入到user中contactInfo的phone屬性,需要傳入contactInfo.phone。
擁有同屬性的多個(gè)對(duì)象
假如現(xiàn)在有一個(gè)Admin類,擁有和User一樣的屬性
package com.shy.springmvcbingding.domain;
/**
* @author Haiyu
* @date 2018/10/6 21:39
*/
public class Admin {
private String name;
private Integer age;
private ContactInfo contactInfo;
// getter setter方法省略
}
同時(shí)新增一個(gè)方法
@PostMapping("/userAndAdmin")
public String getUser(User user, Admin admin) {
return user.toString() + " "+ admin.toString();
}
方法參數(shù)中既有User又有Admin,且兩者擁有一樣的屬性。SpringMVC是如何進(jìn)行綁定的呢?
User{name='Bob', age=22, contactInfo=ContactInfo{address='China', phone='13345678910'}} Admin{name='Bob', age=22, contactInfo=ContactInfo{address='China', phone='13345678910'}}
可以看到,它不做區(qū)分的給User和Admin進(jìn)行了數(shù)據(jù)綁定。假如我們想有區(qū)分的進(jìn)行綁定,要怎么做呢?可以使用@InitBinder注解
@InitBinder用于初始化WebDataBinder,在當(dāng)前Controller類中有效。被@InitBinder注解的方法需要有WebDataBinder方法,且返回值一般是void,可以@RequstMapping方法調(diào)用之前,就對(duì)一些屬性進(jìn)行初始化。
如下是使用@InitBinder的例子
@InitBinder("user")
public void initUser(WebDataBinder binder) {
binder.setFieldDefaultPrefix("user.");
}
@InitBinder("admin")
public void initAdmin(WebDataBinder binder) {
binder.setFieldDefaultPrefix("admin.");
}
它有一個(gè)屬性value,不指定的話,默認(rèn)作用于當(dāng)前Controller的所有表單屬性和請(qǐng)求參數(shù)(可以認(rèn)為是@ModelAttribute和@RequstParam),如果指定了value值,就像上面那樣,那么只針對(duì)參數(shù)是value值的attribute和param生效。比如@InitBinder("user")只對(duì)@ModelAttribute User user有效,@InitBinder("admin")只對(duì)@ModelAttribute Admin admin有效。綜上,這兩個(gè)方法的意義在于給參數(shù)user、admin分別設(shè)置參數(shù)前綴user.和admin.這樣不同的init-binder可以作用與不同的request param或model attribute
這下再來訪問/userAndAdmin,可以看到我們給user.name和admin.name賦予了不同的值

有了前綴的區(qū)分,SpringMVC精準(zhǔn)的將字段綁定到對(duì)應(yīng)的類對(duì)象上了。
User{name='Bob', age=22, contactInfo=null} Admin{name='Lucy', age=21, contactInfo=null}
現(xiàn)在有個(gè)問題,如果我們不配置init-binder,參數(shù)也傳上面那樣的呢?
User{name='null', age=null, contactInfo=null} Admin{name='null', age=null, contactInfo=null}
由于沒有給參數(shù)配置前綴,SpringMVC不認(rèn)識(shí)user.name這樣的屬性了。
當(dāng)然如果Admin的屬性和User不一樣,比如name改成adminName,age改成adminAge,那么是無需配置init-binder的。就像下面那樣

響應(yīng)是
User{name='Lucy', age=21, contactInfo=null} Admin{adminName='Bob', adminAge=22, contactInfo=null}
SpringMVC會(huì)將匹配的字段分別綁定到了User和Admin對(duì)象上。
List、Set、Map
List
要將數(shù)據(jù)綁定到List<Integer>或者List<String>,其實(shí)是可以像下面這樣寫的。
public String array(@RequestParam("name") List<String> names) {...}
public String array(@RequestParam("age") List<Integer> ages) {...}
在表單中只要提交相同的字段即可name=bob&name=jim&name=lucy
但是數(shù)據(jù)要綁定到List<User>上,就不能這樣寫了因?yàn)閁ser中還有其他屬性??梢苑抡丈厦鎁ser中的CantactInfo一樣,用一個(gè)UserList類將List<User>包裹起來,對(duì)list某個(gè)元素的訪問可以使用list[index]的形式。
package com.shy.springmvcbingding.domain;
import java.util.List;
public class UserList {
private List<User> users;
// getter和setter省略
}
相應(yīng)的將Controller中的方法改成下面這樣
@PostMapping("/list")
public UserList userList(UserList userList) {
return userList;
}
因?yàn)閡serList中的List<User>其屬性名是users,所以u(píng)sers[0]表示第一個(gè)user,users[1]表示第二個(gè)user,以此類推。

可以看到,在表單中傳入字段時(shí),我們故意跳過了users[1],會(huì)報(bào)空指針異常嗎?
{
"users": [
{
"name": "vv",
"age": 33,
"contactInfo": null
},
{
"name": null,
"age": null,
"contactInfo": null
},
{
"name": "F",
"age": 44,
"contactInfo": null
}
]
}
從響應(yīng)中得知,被跳過的索引被設(shè)置成null了,并不會(huì)導(dǎo)致空指針。
Set
對(duì)于Set類型的數(shù)據(jù)綁定。先試著仿照List的做法。
package com.shy.springmvcbingding.domain;
import java.util.Set;
public class UserSet {
Set<User> users;
// getter和setter方法省略
}
Controller中新增方法set
@PostMapping("set")
public UserSet userSet(UserSet userSet) {
return userSet;
}
表單中還是和List一樣傳入相同的字段。運(yùn)行一下會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了
Invalid property 'users[0]' of bean class [com.shy.springmvcbingding.domain.UserSet]: Cannot get element with index 0 from Set of size 0, accessed using property path 'users[0]'
追蹤源碼
else if (value instanceof Set) {
// Apply index to Iterator in case of a Set.
Set<Object> set = (Set<Object>) value;
int index = Integer.parseInt(key);
if (index < 0 || index >= set.size()) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Cannot get element with index " + index + " from Set of size " +
set.size() + ", accessed using property path '" + propertyName + "'");
}
這里用到了set.size()是0,說明Set中沒有任何對(duì)象。好吧,我那們就手動(dòng)add幾個(gè)進(jìn)去。
給UserSet新增一個(gè)構(gòu)造方法
public UserSet() {
users = new LinkedHashSet<>();
users.add(new User());
users.add(new User());
users.add(new User());
}
再運(yùn)行程序就會(huì)得到和List一樣結(jié)果。但是Set的主要功能是去重,當(dāng)name和age一樣時(shí),就認(rèn)為這兩個(gè)對(duì)象是相等的,有個(gè)好的建議是對(duì)于自定義對(duì)象的Set和Map都必須重寫hashCode方法equals方法。
在User類中,重寫這兩個(gè)方法alt + insert讓IDEA自動(dòng)幫我們生成。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) &&
Objects.equals(age, user.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再運(yùn)行程序,又報(bào)錯(cuò)了!原因還是越界了,因?yàn)樵赨serSet方法中連續(xù)add了三次User,但由于我們重寫了hashCode和equals方法,這三個(gè)user是重復(fù)的。三次add后set的實(shí)際size是1?,F(xiàn)在啊只傳users[0].name=vv&users[0].age=33程序就不會(huì)報(bào)錯(cuò)了。
{
"users": [
{
"name": "vv",
"age": 33,
"contactInfo": null
}
]
}
Map
和上面一樣先寫UserMap
package com.shy.springmvcbingding.domain;
import java.util.Map;
public class UserMap {
private Map<String,User> users;
// getter和setter方法省略
}
在Controller中
@PostMapping("map")
public UserMap userMap(UserMap userMap) {
return userMap;
}
然后用map['key']的形式傳入以下參數(shù)即可。

響應(yīng)內(nèi)容為
{
"users": {
"X": {
"name": "Jack",
"age": 22,
"contactInfo": null
},
"Y": {
"name": "Lucy",
"age": 20,
"contactInfo": null
}
}
}
Json&XML
使用注解@RequstBody可以處理contentType:"application/json"和“application/xml”編碼的內(nèi)容,并將json或者xml中的數(shù)據(jù)綁定到參數(shù)對(duì)象上。
先來測試Json,把contentType改成application/json,然后訪問/json,
@PostMapping("/json")
public User userJson(@RequestBody User user) {
return user;
}
在request body中傳入以下json內(nèi)容
{
"name":"bob",
"age": 16
}
響應(yīng)內(nèi)容是
{
"name": "bob",
"age": 16,
"contactInfo": null
}
同理,對(duì)于XML,contentType改成application/xml。在User類中,加幾個(gè)xml綁定的注解。
package com.shy.springmvcbingding.domain;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Objects;
@XmlRootElement(name = "user")
public class User {
private String name;
private Integer age;
@XmlElement(name = "name")
public String getName() {
return name;
}
@XmlElement(name = "age")
public Integer getAge() {
return age;
}
}
@XmlElement可以將Java屬性映射成xml節(jié)點(diǎn)。
- 在類上,加@XmlRootElement(name = "user"),表明xml的根元素名為user,如果不指定name,默認(rèn)使用Java類名
- 在getter/setter方法上加@XmlElement,將該屬性映射成xml的對(duì)應(yīng)節(jié)點(diǎn)。同樣的,果不指定name,默認(rèn)使用Java屬性名
然后訪問/xml,傳入以下xml
<?xml version="1.0" encoding="UTF-8"?>
<user>
<nam>Bob</nam>
<age>22</age>
</user>
響應(yīng)內(nèi)容為
{
"name": "Bob",
"age": 22,
"contactInfo": null
}
注意,@XmlElement最好用來getter方法上,如果用在了屬性(Field)上,會(huì)引發(fā)下面的異常。
// @XmlElement(name = "name")
// private String name;
類的兩個(gè)屬性具有相同名稱 "name"
this problem is related to the following location:
at public java.lang.String com.shy.springmvcbingding.domain.User.getName()
at com.shy.springmvcbingding.domain.User
this problem is related to the following location:
at private java.lang.String com.shy.springmvcbingding.domain.User.name
at com.shy.springmvcbingding.domain.User
] with root cause
日期轉(zhuǎn)換
PropertyEditor
如果要綁定的類型是Date型的,在表單中傳入date=2018-10-01,很明顯會(huì)報(bào)錯(cuò),說不能將String轉(zhuǎn)換成Date型。
可以用init-binder為參數(shù)date注冊(cè)一個(gè)PropertyEditor,其中有個(gè)子類CustomDateEditor
@InitBinder("date")
public void initDate(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
@PostMapping("date")
public String getDate(@RequestParam("date") Date date) {
return date.toString();
}
其實(shí)就是將Date使用SimpleDateFormat來解析(parse)和格式化(format)。
來看下CustomDateEditor的源碼,重點(diǎn)關(guān)注它重寫的setAsText和getAsText方法
public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) {
this.dateFormat = dateFormat;
this.allowEmpty = allowEmpty;
this.exactDateLength = exactDateLength;
}
@Override
public void setAsText(@Nullable String text) throws IllegalArgumentException {
if (this.allowEmpty && !StringUtils.hasText(text)) {
// Treat empty String as null value.
setValue(null);
}
else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
throw new IllegalArgumentException(
"Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
}
else {
try {
// 設(shè)置value,設(shè)置的是從String解析后的Date
setValue(this.dateFormat.parse(text));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
}
@Override
public String getAsText() {
// 取出value并格式化
Date value = (Date) getValue();
return (value != null ? this.dateFormat.format(value) : "");
}
public void setValue(Object value) {
this.value = value;
firePropertyChange();
}
public Object getValue() {
return value;
}
可以發(fā)現(xiàn)其實(shí)CustomDateEditor就是對(duì)DateFormat做了一個(gè)封裝而已,參數(shù)allowEmpty表示是否允許text為空,該類會(huì)將空字符串當(dāng)成null值。
現(xiàn)在運(yùn)行程序,響應(yīng)內(nèi)容為
Mon Oct 01 00:00:00 CST 2018
成功將字符串2018-10-01轉(zhuǎn)換成了Date型。
Formatter
在package org.springframework.format下有這么一個(gè)接口
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Spring下有一個(gè)實(shí)現(xiàn)類DateFormatter,可以完成從字符串到日期的轉(zhuǎn)換,直接使用即可。
不過需要在xml中引入FormattingConversionServiceFactoryBean并注入相應(yīng)的property
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="dateFormatter"/>
<bean id="dateFormatter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean id="formatter" class="org.springframework.format.datetime.DateFormatter"/>
</set>
</property>
</bean>
</beans>
定義好dateFormatter這個(gè)bean后,別忘了在mvc注解驅(qū)動(dòng)中加上conversion-service,其屬性值對(duì)應(yīng)了FormattingConversionServiceFactoryBean的bean id。
<mvc:annotation-driven conversion-service="dateFormatter"/>
最后在Java Config中引入該xml文件
package com.shy.springmvcbingding.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
/**
* @author Haiyu
* @date 2018/10/7 22:33
*/
@Configuration
@ImportResource("classpath:bind.xml")
public class BindingConfig {
}
這樣SpringBootApplication會(huì)掃描到這個(gè)配置文件并加載到上下文中。
Converter
FormattingConversionServiceFactoryBean這個(gè)類有兩個(gè)重要的屬性。
@Nullable
private Set<?> converters;
@Nullable
private Set<?> formatters;
可知它既可以實(shí)現(xiàn)Formatter也可以實(shí)現(xiàn)Converter。那么只需把注入的property屬性改成converters即可。
<mvc:annotation-driven conversion-service="dateConverter"/>
<bean id="dateConverter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!--<property name="formatters">-->
<!--<set>-->
<!--<bean id="formatter" class="org.springframework.format.datetime.DateFormatter"/>-->
<!--</set>-->
<!--</property>-->
<property name="converters">
<set>
<bean id="converter" class="com.shy.springmvcbingding.converter.DateConverter">
<constructor-arg name="pattern" value="yyyy-MM-dd" />
</bean>
</set>
</property>
</bean>
DateConverter是自定義類,實(shí)現(xiàn)了Converter<S,T>接口,可以將S類型轉(zhuǎn)換成T類型。如下,DateConverter<String, Date>是一個(gè)將String類型的轉(zhuǎn)換成Date類型的轉(zhuǎn)換器。
package com.shy.springmvcbingding.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Haiyu
* @date 2018/10/7 22:59
*/
public class DateConverter implements Converter<String, Date> {
private String pattern;
public DateConverter(String pattern) {
super();
this.pattern = pattern;
}
@Override
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = null;
try {
date = sdf.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
@DateTimeFormat
將字符串轉(zhuǎn)換成日期最方便的方法其實(shí)是在參數(shù)上添加注解@DateTimeFormat。
還可以為其指定pattern,如下。
@PostMapping("date")
public String getDate(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
return date.toString();
}
比較
- PropertyEditor:局部使用,通常搭配WebDataBinder來使用
- Formatter:全局/局部使用(xml中通過Spring注入的方式實(shí)現(xiàn)了全局使用),只能將String類型轉(zhuǎn)換成其它類型
- Converter:全局/局部使用(xml中通過Spring注入的方式實(shí)現(xiàn)了全局使用),可以將任意類型的轉(zhuǎn)換成其他類型的。
- @DateTimeFormat,局部使用,用在Controller的方法參數(shù)上
RESTful擴(kuò)展
REST(Representational State Transfer)即表現(xiàn)層狀態(tài)轉(zhuǎn)移,如果一個(gè)架構(gòu)符合REST原則,就稱為RESTful架構(gòu)。
資源(resource)包括文本、圖片、音頻、服務(wù)等,資源呈現(xiàn)的形式稱為表現(xiàn)層。
- 文本:txt、xml、json、html、binary
- 圖片:png、jpg
資源表現(xiàn)的形式通過http協(xié)議的content-type和accept來指定。
http://www.bookstore/book
這個(gè)網(wǎng)址只代表資源本身,資源的展現(xiàn)形式是通過content-type和accept來描述的
Accept代表發(fā)送端(客戶端)希望接受的數(shù)據(jù)類型。
比如:Accept:text/xml; 代表客戶端希望接受的數(shù)據(jù)類型是xml類型Content-Type代表發(fā)送端(客戶端|服務(wù)器)發(fā)送的實(shí)體數(shù)據(jù)的數(shù)據(jù)類型。Content-Type:text/html; 代表發(fā)送端發(fā)送的數(shù)據(jù)格式是html。
寫一個(gè)方法查看請(qǐng)求的content-type。
// 方法1
@GetMapping("/contentType")
public String contentType(HttpServletRequest request) {
return request.getContentType();
}
// 方法2
@GetMapping("/contentType")
public String contentType() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
return request.getContentType();
}
在SpringMVC中,獲取HttpServletRequest和HttpServletResponse有兩種方法。
- 在方法的入?yún)⒅兄苯佣x,如上方法1
- 使用
RequestContextHolder.getRequestAttributes(),然后調(diào)用其getRequest()和getResponse()就能獲得HttpServletRequest和HttpServletResponse
HTTP請(qǐng)求方法中,最常用的是POST、DELETE、PUT、GET,可以認(rèn)為分別對(duì)應(yīng)了對(duì)資源的增刪改查。再次之前西安介紹下HTTP中的冪等性:
冪等性:每次HTTP請(qǐng)求相同的參數(shù),相同的URI,產(chǎn)生的結(jié)果是相同的。
- GET-獲取資源。如http://www.bookstore/book/123 表示查詢編號(hào)為123的書籍
- POST-創(chuàng)建資源,不具有冪等性。http://www.bookstore/book 表示新增一本書籍,請(qǐng)求多少次就會(huì)新增多少條記錄,會(huì)響應(yīng)不同的URI,即產(chǎn)生了不同的結(jié)果,所以認(rèn)為POST不具有冪等性
- PUT-創(chuàng)建(更新)資源,具有冪等性。http://www.bookstore/book/123 創(chuàng)建或者更新編號(hào)為123的書籍,如果編號(hào)123書籍已經(jīng)存在那么語義是更新;如果不存在就是新增。但是多次請(qǐng)求的結(jié)果都是對(duì)一個(gè)資源的更新,即多次請(qǐng)求后結(jié)果不變,只會(huì)響應(yīng)有一個(gè)URI,所以認(rèn)為PUT是冪等的
- DELETE-刪除資源。http://www.bookstore/book/123 刪除編號(hào)為123的書籍
以上4個(gè)請(qǐng)求方法中,POST不是冪等的,GET、DELETE、PUT是冪等的。
用RESTful風(fēng)格寫Controller,以上面的book為例
@GetMapping("/bookstore/book/{id}")
public String getBook(@PathVariable("id") Integer id) {
return "Query book " + id;
}
@PostMapping("/bookstore/book")
public String addBook(@RequestParam("id") Integer id) {
return "Add book " + id;
}
@PutMapping("/bookstore/book/{id}")
public String updateBook(@PathVariable("id") Integer id) {
return "Update book " + id;
}
@DeleteMapping("/bookstore/book{id}")
public String deleteBook(@PathVariable("id") Integer id) {
return "Delete book " + id;
}
注:@PathVariable可以將URL路徑中{...}的內(nèi)容綁定到參數(shù)上。
RESTful總結(jié):
- 每一個(gè)URI代表一個(gè)資源。
- 客戶端和服務(wù)端之間,傳遞該資源的一種表現(xiàn)形式。
- 客戶端通過不同的HTTP請(qǐng)求方法,對(duì)服務(wù)端的資源進(jìn)行操作(增刪改查等),實(shí)現(xiàn)表現(xiàn)層狀態(tài)轉(zhuǎn)移。
by @sunhaiyu
2018.10.8