SpringMVC數(shù)據(jù)綁定

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ù)中。

Snipaste_2018-10-06_17-40-00
@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ù)

Snipaste_2018-10-06_20-46-17

響應(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.nameadmin.name賦予了不同的值

Snipaste_2018-10-06_22-11-34

有了前綴的區(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的。就像下面那樣

Snipaste_2018-10-06_22-21-36

響應(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,以此類推。

Snipaste_2018-10-07_09-36-56

可以看到,在表單中傳入字段時(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ù)即可。

Snipaste_2018-10-07_10-45-22

響應(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • 這是我在 慕課網(wǎng) 觀看 SpringMVC 數(shù)據(jù)綁定入門 所做的學(xué)習(xí)筆記其中包含對(duì) **List,Set,Map,...
    jnil閱讀 1,282評(píng)論 0 2
  • 既然找到這么一個(gè)寫作的平臺(tái),我一不小心厚顏無恥地想到了自己先前的一本書。 這本書是幾年前的一部懸疑作品,也是紫金 ...
    作家李禹東閱讀 2,389評(píng)論 27 69
  • 文/一木 (一) 無法得賜一雙寫詩的手 只能把我母親深深地埋怨 只能憑空想象 那些優(yōu)秀詩人們手指修長而純凈 然而,...
    一木與你看世界閱讀 399評(píng)論 0 0
  • 其實(shí)最開始寫下的是不悲觀,不絕望。后來一字一字刪掉。 你是一個(gè)悲觀主義者,這一點(diǎn)你從不否認(rèn),也不懷疑。 你賭下千年...
    寶劍與高跟鞋閱讀 440評(píng)論 0 0

友情鏈接更多精彩內(nèi)容