一文讀懂SpringMVC中的數(shù)據(jù)綁定

本文是對(duì) SpringMVC 中數(shù)據(jù)綁定的總結(jié)。

1、SpringMVC 和 Struts2 的區(qū)別

Struts2 和 SpringMVC 都是 Web 開(kāi)發(fā)中視圖層的框架,兩者都實(shí)現(xiàn)了數(shù)據(jù)的自動(dòng)綁定,都不需要我們手動(dòng)獲取參數(shù)然后關(guān)聯(lián)到對(duì)應(yīng)的屬性上,下面就談?wù)剝烧叩膮^(qū)別。

  • Spring MVC 是基于方法的,通過(guò)形參接收參數(shù);Struts2 是基于類的,通過(guò)模型驅(qū)動(dòng)封裝接收參數(shù)。
  • SpringMVC 將 url 和 controller 類中的方法映射,生成一個(gè) Handler 對(duì)象來(lái)執(zhí)行 method 方法;Struts2 根據(jù)配置文件將 url 和 action 類中的方法映射,生成 action 對(duì)象來(lái)執(zhí)行 method 方法。
  • SpringMVC 形參接收參數(shù),一個(gè)方法獨(dú)享 request response 數(shù)據(jù),使用單例開(kāi)發(fā);Struts2 成員變量接收參數(shù),多個(gè)方法共享成員變量,必須使用多例開(kāi)發(fā)。
  • SpringMVC 的入口是 Servlet,一個(gè)方法對(duì)于一個(gè) request 上下文,通過(guò)注解將 request 數(shù)據(jù)注入方法形參;而 Struts2 的入口是 Filter,攔截每個(gè)請(qǐng)求,創(chuàng)建一個(gè) Action,調(diào)用成員變量的 getter、setter 方法將 reque 數(shù)據(jù)注入成員變量,兩者實(shí)現(xiàn)機(jī)制不同。
  • SpringMVC 更加輕量級(jí),Struts2 配置很多,SpringMVC 開(kāi)發(fā)效率和性能都比 Struts2 高。
  • SpringMVC 方法返回的數(shù)據(jù)更加靈活,使用 AJAX 進(jìn)行 JSON 交互很方便;Struts2 的標(biāo)簽數(shù)據(jù)渲染慢,不如 JSTL 標(biāo)簽性能高。

這兩個(gè)框架我都用過(guò),這里僅是個(gè)人看法,Struts2 的配置真的是寫死人,類的限制使得使用也不夠靈活,與一些前端框架的結(jié)合也不是很方便,個(gè)人是放棄 Struts2 框架了。

2、不同類型的數(shù)據(jù)綁定

在開(kāi)發(fā)中前后臺(tái)交互的數(shù)據(jù)無(wú)非是下面幾種:

  • 基本類型(int、double、Integer、String 等)
  • 對(duì)象(類)類型(自定義的實(shí)體類)
  • 日期類型(java.util.Date)
  • 復(fù)雜類型(對(duì)象數(shù)組、List、Set、Map 等)
  • 特殊文本類型(JSON、XML 等)

下面就總結(jié)一下這些數(shù)據(jù)在 SpringMVC 中如何綁定到方法形參中。

使用 Maven 來(lái)搭建項(xiàng)目,所有的代碼都已上傳到 GitHub 上,有需要的小伙伴可以前往下載,也歡迎你 star 該倉(cāng)庫(kù)哦!

在給方法加上 @ResponseBody 注解后,直接將處理好的數(shù)據(jù)輸出到響應(yīng)流中,沒(méi)有了試圖解析過(guò)程,也就是返回的是 JSON 類型。SpringMVC 這里使用了適配器模式來(lái)處理數(shù)據(jù)轉(zhuǎn)換,當(dāng)我們使用 Jackson 作為解析 JSON 工具,這里注意一個(gè)大坑,Jackson 內(nèi)默認(rèn)的編碼為 ISO-8859-1(大坑),這就會(huì)導(dǎo)致在輸出中文時(shí)亂碼,這點(diǎn)可以通過(guò)瀏覽器的控制臺(tái)查看,解決方法有以下幾種。

1、在每個(gè)方法上加上編碼設(shè)置@RequestMapping(value = "basetype3.do", produces = "application/json; charset=utf-8")

2、在 SpringMVC 配置文件中修改 Jackson 的默認(rèn)編碼為 UTF-8,注意要放在 <mvc:annotation-driven/> 前面,放在內(nèi)部是不生效的。

3、更改 JSON 解析工具,推薦使用阿里的 fastjson,默認(rèn)編碼就是 UTF-8,解析速度也比 Jackson 快。

方法二、三詳細(xì)的配置如下:

<!--特別注意:必須放在mvc:annotation-driven前面,放在內(nèi)部是不會(huì)生效的-->
    <!--json裝換器使用 Jackson 默認(rèn)編碼是 ISO-8859-1 需要重新設(shè)置編碼-->
    <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">-->
        <!--<property name="messageConverters">-->
            <!--<list>-->
                <!--<bean class="org.springframework.http.converter.StringHttpMessageConverter">-->
                    <!--<property name="supportedMediaTypes">-->
                        <!--<list>-->
                            <!--<value>text/plain;charset=UTF-8</value>-->
                            <!--<value>text/html;charset=UTF-8</value>-->
                            <!--<value>applicaiton/json;charset=UTF-8</value>-->
                        <!--</list>-->
                    <!--</property>-->
                <!--</bean>-->
            <!--</list>-->
        <!--</property>-->
    <!--</bean>-->
    <!-- json裝換器使用 fastjson,默認(rèn)就是 UTF-8 編碼,不需要再重新設(shè)置編碼-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
            </list>
        </property>
    </bean>
    <!--配置注解驅(qū)動(dòng)-->
    <mvc:annotation-driven/>

說(shuō)明:項(xiàng)目名為 springmvc,每個(gè)方法上面是測(cè)試地址哦。

2.1 基本類型

在傳參時(shí)方法中的形參名稱默認(rèn)要和 url 中的參數(shù)名稱保持一致,也可以在方法中加 @RequestParam 注解修改 url 中的參數(shù)名稱。

基本類型中的基本數(shù)據(jù)類型(int,double)設(shè)置為參數(shù)是不能為空,否則將會(huì)報(bào)錯(cuò),而基本數(shù)據(jù)類型的包裝類型是可以為 null,也即是沒(méi)有傳入時(shí)默認(rèn)值為 null,這里也要注意上面提到的中文亂碼哦。

// http://localhost:8080/springmvc/basetype1.do?id=1
// http://localhost:8080/springmvc/basetype1.do?id= 不帶參數(shù)報(bào)錯(cuò)
@RequestMapping(value = "basetype1.do")
@ResponseBody
public String baseType1(int id) {
    return "id=" + id;
}

// http://localhost:8080/springmvc/basetype2.do?id=1
// http://localhost:8080/springmvc/basetype2.do?id= 不帶參數(shù)不報(bào)錯(cuò),參數(shù)默認(rèn)為null
@RequestMapping(value = "basetype2.do")
@ResponseBody
public String baseType2(Integer id) {
    return "id=" + id;
}
// http://localhost:8080/springmvc/basetype3.do?name='湯姆' 注意中文亂碼問(wèn)題
// http://localhost:8080/springmvc/basetype3.do?name='tom'
@RequestMapping(value = "basetype3.do")
@ResponseBody
public String baseType3(String name) {
    return "name=" + name;
}

// http://localhost:8080/springmvc/basetype4.do?xid=1
@RequestMapping(value = "basetype4.do")
@ResponseBody
public String baseType4(@RequestParam(value = "xid") Integer id) {
    return "id=" + id;
}

2.2 對(duì)象類型

實(shí)體類說(shuō)明:

  • User 類中只有兩個(gè)屬性,一個(gè)是 String 類型的 name,一個(gè)是 Integer 類型的 age。
  • Order 類中也只有兩個(gè)屬性,一個(gè)是 String 類型的 id,一個(gè)是 User 類型的 user。
  • People 類中的屬性和 User 類中的完全一樣。

類中生成屬性的 getter 和 setter 方法以及 toString 方法。

在傳對(duì)象類型的屬性時(shí),url 中參數(shù)名稱為對(duì)象的屬性名稱,不加對(duì)象名。

如果一個(gè)類中的屬性是另一個(gè)類,在傳參時(shí),url 中參數(shù)名稱為屬性對(duì)象名稱加屬性,如下面的第二個(gè)方法。

當(dāng)傳入的對(duì)象類型參數(shù)相同時(shí),如果不加以區(qū)分,會(huì)給同名的屬性都賦值,如下面的第三個(gè)方法,這里的數(shù)據(jù)綁定就需要我們自定義,@InitBinder("對(duì)象名"),在自定義的方法(方法名任意)中設(shè)置屬性默認(rèn)的前綴值,這樣就可以區(qū)分不同對(duì)象的屬性了。

// http://localhost:8080/springmvc/objecttype1.do?name='tom'&age=1
// http://localhost:8080/springmvc/objecttype1.do?name='tom'&age=
@RequestMapping(value = "objecttype1.do")
@ResponseBody
public String objecttype1(User user) {
    return "user=" + user;
}

// http://localhost:8080/springmvc/objecttype2.do?id='123'&user.name='tom'&user.age=1
// http://localhost:8080/springmvc/objecttype2.do?id='123'&user.name='tom'&user.age=
// http://localhost:8080/springmvc/objecttype2.do?id='123'
@RequestMapping(value = "objecttype2.do")
@ResponseBody
public String objecttype2(Order order) {
    return "order=" + order;
}

// http://localhost:8080/springmvc/objecttype3.do?people.name=Tom&user.name=Lucy
// http://localhost:8080/springmvc/objecttype3.do?people.name=Tom
// http://localhost:8080/springmvc/objecttype3.do?name=Tom
@RequestMapping(value = "objecttype3.do")
@ResponseBody
public String objecttype3(People people, User user) {
    return "people=" + people + ",user=" + user;
}

@InitBinder("people")
public void initPeople(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("people.");
}

@InitBinder("user")
public void initUser(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("user.");
}

2.3 日期類型

大多數(shù)情況下,SpringMVC 的數(shù)據(jù)綁定以及可以滿足我們的使用了,但是對(duì)于一些特殊數(shù)據(jù)類型,如 java.util.Date 類型。字符串轉(zhuǎn) Date 類型,需要我們自定義轉(zhuǎn)換器(Converter)或格式化(Formatter)來(lái)進(jìn)行數(shù)據(jù)綁定。

下面的方法一使用綁定數(shù)據(jù)時(shí)會(huì)按照用戶設(shè)置的格式初始化,但這種方法只對(duì)單個(gè)方法生效,我們可以自定義類型轉(zhuǎn)換類,轉(zhuǎn)換類需要實(shí)現(xiàn) Converter 或者 Formatter 接口,具體的代碼如下。

實(shí)現(xiàn) Converter 接口需要指定接口的兩個(gè)泛型,前者為要轉(zhuǎn)換的類型,后者為轉(zhuǎn)換后的類型,并且需要實(shí)現(xiàn)接口中的 convert() 方法,方法中的參數(shù)為要轉(zhuǎn)換的類型,返回值為轉(zhuǎn)換后的類型。

實(shí)現(xiàn) Formatter 接口只需要指定接口的一個(gè)泛型,即轉(zhuǎn)換后的類型,但是要實(shí)現(xiàn)接口中的 parse() 方法和 print() 方法,前一個(gè)方法是將要轉(zhuǎn)換的類型轉(zhuǎn)換為我們指定的類型,后一個(gè)方法是規(guī)定如何輸出轉(zhuǎn)換后的類型。

// DateConverter
public class DateConverter implements Converter<String, Date> {
    // 定義日期格式
    private String dataPattern = "yyyy-MM-dd HH:mm:ss";

    @Override
    public Date convert(String s) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dataPattern);
        try {
            return simpleDateFormat.parse(s);
        } catch (ParseException e) {
            throw new IllegalArgumentException("無(wú)效的日期格式,請(qǐng)使用" + dataPattern + "格式的日期");
        }
    }
}
// DateFormatter 類
public class DateFormatter implements Formatter<Date> {
    // 定義日期格式
    private String dataPattern = "yyyy-MM-dd HH:mm:ss";

    @Override
    public Date parse(String s, Locale locale) throws ParseException {
        return new SimpleDateFormat(dataPattern).parse(s);
    }

    @Override
    public String print(Date date, Locale locale) {
        return new SimpleDateFormat().format(date);
    }
}

寫完自定義轉(zhuǎn)換類后,還需要在 SprinMVC 的配置文件中配置,這樣對(duì)所有的方法都生效,具體配置如下:

<!--配置自定義的日期類型轉(zhuǎn)換器-->
    <mvc:annotation-driven conversion-service="dataConverterService"/>
    <!--使用 Convert 接口-->
    <bean id="dataConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.wenshixin.convert.DateConverter"/>
            </set>
        </property>
    </bean>
    <!--使用 Formatter 接口-->
    <!--<bean id="dataConverterService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">-->
    <!--<property name="formatters">-->
    <!--<set>-->
    <!--<bean class="com.wenshixin.convert.DateFormatter"/>-->
    <!--</set>-->
    <!--</property>-->
    <!--</bean>-->
// http://localhost:8080/springmvc/datetype1.do?date=2018-09-19
@RequestMapping(value = "datetype1.do")
@ResponseBody
public String datetype1(Date date1) {
    return date1.toString();
}
@InitBinder("date1")
public void initDate(WebDataBinder binder) {
    binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}

// http://localhost:8080/springmvc/datetype2.do?date2=2018-09-10 22:50:10
@RequestMapping(value = "datetype2.do")
@ResponseBody
public String datetype2(Date date2) {
    return date2.toString();
}

2.4 復(fù)雜類型

復(fù)雜類型包括數(shù)組和集合類型,像 List、Set、Map。

數(shù)組類型用于傳入多個(gè)參數(shù)名稱相同的值,如接收頁(yè)面上的復(fù)選框參數(shù)時(shí)。

SpringMVC 對(duì)于復(fù)雜類型的支持并不是很好,因?yàn)閷?duì)于復(fù)雜類型,我們更多都是使用 JSON、XML等數(shù)據(jù)格式來(lái)傳參。對(duì)于 List、Set、Map 這些類型,還需要單獨(dú)設(shè)置一個(gè)包裝類,屬性設(shè)置為對(duì)應(yīng)的集合類型,方法的參數(shù)為包裝類型,比較繁瑣。SpringMVC 對(duì)復(fù)雜類型的數(shù)據(jù)綁定的功能,基本上就是雞肋。

類說(shuō)明:

  • UserList 為 User 對(duì)應(yīng)的 List 集合包裝類,只有一個(gè)屬性,private List<User> users;。
  • UserList 為 User 對(duì)應(yīng)的 Set 集合包裝類,只有一個(gè)屬性,private Set<User> users = new HashSet<>();,并且需要在該類的構(gòu)造函數(shù)中初始化 Set 集合的大小,不能動(dòng)態(tài)改變 Set 集合大小,在傳值時(shí),對(duì)象的個(gè)數(shù)不能超過(guò)這個(gè)大小。
  • UserList 為 User 對(duì)應(yīng)的 Map 集合包裝類,只有一個(gè)屬性,private Map<String, User> users;。
// http://localhost:8080/springmvc/complextype1.do?ids=1&ids=2
@RequestMapping(value = "complextype1.do")
@ResponseBody
public String objecttype1(String[] ids) {
    System.out.println(ids.length);
    StringBuilder stringBuilder = new StringBuilder();
    for(String id : ids) {
        stringBuilder.append(id + " ");
    }
    return stringBuilder.toString();
}

// http://localhost:8080/springmvc/complextype2.do?users%5B0%5D.name=Tom&users%5B1%5D.name=Lucy 注意特殊字符[]的轉(zhuǎn)義,不然會(huì)報(bào)錯(cuò)
// http://localhost:8080/springmvc/complextype2.do?users%5B0%5D.name=Tom&users%5B1%5D.name=Lucy&users%5B6%5D.name=Mary 注意特殊字符[]的轉(zhuǎn)義,不然會(huì)報(bào)錯(cuò)
@RequestMapping(value = "complextype2.do")
@ResponseBody
public String objecttype2(UserList userList) {
    return userList.toString();
}

// http://localhost:8080/springmvc/complextype2.do?users%5B0%5D.name=Tom&users%5B1%5D.name=Lucy&users%5B2%5D.name=Mary 注意特殊字符[]的轉(zhuǎn)義,不然會(huì)報(bào)錯(cuò)
@RequestMapping(value = "complextype3.do")
@ResponseBody
public String objecttype3(UserSet userSet) {
    System.out.println(userSet.getUsers().size());
    return userSet.toString();
}

// http://localhost:8080/springmvc/complextype4.do?users%5B%270%27%5D.name=Tom&users%5B%271%27%5D.name=Lucy&users%5B%272%27%5D.name=Mary
@RequestMapping(value = "complextype4.do")
@ResponseBody
public String objecttype4(UserMap userMap) {
    System.out.println(userMap.getUsers().size());
    return userMap.toString();
}

2.5 特殊類型

SpringMVC 更適合現(xiàn)今前后端分離的數(shù)據(jù)傳輸,對(duì)于現(xiàn)在流行的格式化數(shù)據(jù)類型 JSON,支持很好,只需要 @RequestBody(傳參)和 @ResponseBody(輸出)兩個(gè)注解,使用起來(lái)很方便。

對(duì)于編寫 API,SpringMVC 無(wú)疑是比 Struts2 的有優(yōu)勢(shì)。

// json 格式
/*
  {
    "name":"Tom",
    "age":1
  }
*/
@RequestMapping(value = "jsontype.do")
@ResponseBody
public User jsontype(@RequestBody User user) {
    System.out.println(user);
    return user;
}

// xml 格式
/*
  <?xml version="1.0" encoding="UTF-8" ?>
  <user>
    <name>Jim</name>
    <age>16</age>
  </user>
*/
@RequestMapping(value = "xmltype.do")
@ResponseBody
public User xmltype(@RequestBody User user) {
    System.out.println(user);
    return user;
}

2.6 RESTful 風(fēng)格

RESTful 風(fēng)格的 API 已經(jīng)受到業(yè)界的肯定,在當(dāng)今的分布式架構(gòu)中更是如魚得水。很多 Web 框架也都支持 RESTful 風(fēng)格的 API編寫,當(dāng)然也包括 SpringMVC ,這里簡(jiǎn)單介紹一下 RESTful 風(fēng)格。

RESTful 是 Resource Representional State Transfer 的縮寫,RE 是前面兩個(gè)單詞的簡(jiǎn)寫,第一個(gè)單詞經(jīng)常被省略,而這個(gè)單詞其實(shí)才是 RESTful 的核心思想,中文翻譯為 資源表現(xiàn)層狀態(tài)轉(zhuǎn)換。

RESTful 的作者也是 HTTP 協(xié)議的設(shè)計(jì)者,他將 HTTP 中的 URI 的思想引入到 API 編程中,每一個(gè)資源都有一個(gè)存放的位置,對(duì)資源的操作(請(qǐng)求)就是資源在表現(xiàn)層的轉(zhuǎn)態(tài)轉(zhuǎn)換,如常見(jiàn)的 GET、POST,還有不常用 PUT、DELETE 等。

RESTful 風(fēng)格有更加簡(jiǎn)短的資源地址,和一般的 API 地址直接對(duì)資源進(jìn)行操作,如 add、select 不同,RESTful 風(fēng)格的主體是資源,對(duì)資源的操作體現(xiàn)在請(qǐng)求方式上,如 DELETE。不同的請(qǐng)求方式對(duì)應(yīng)不同的操作,如同一個(gè)地址,如果是 GET 方式,就直接返回頁(yè)面,如果是 POST 方式,就是提交頁(yè)面上的數(shù)據(jù),這樣地址也更少,使得訪問(wèn)也更加安全。

下面的代碼展示了 RESTful 風(fēng)格的 API 如何使用,API 的測(cè)試,用瀏覽器并不方便,可以使用 Postman 等網(wǎng)絡(luò)工具。

@RequestMapping(value = "/user/{name}", method = RequestMethod.GET)
@ResponseBody
public String findUserByGET(@PathVariable("name") String name) {
    return "GET name=" + name;
}

@RequestMapping(value = "/user/{name}", method = RequestMethod.POST)
@ResponseBody
public String findUserByPOST(@PathVariable("name") String name) {
    return "POST name=" + name;
}

歡迎關(guān)注下方的微信公眾號(hào)哦,里面有各種學(xué)習(xí)資料免費(fèi)分享哦!

編程心路
最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,445評(píng)論 6 13
  • 冬夏春秋好, 華章博世繪。 學(xué)習(xí)助成長(zhǎng), 樂(lè)思育棟梁! 注: 陳總——陳冬華,杭州...
    河北南和劉志玉閱讀 183評(píng)論 0 5
  • 青春總是那么的明媚張狂,關(guān)于青春,關(guān)于友情,總是那么得美好。高中時(shí)代的我們,總是一起夢(mèng)想著一起上大學(xué),一起學(xué)習(xí),一...
    就是愛(ài)吃糖閱讀 291評(píng)論 0 1
  • 去你媽的
    走好每一天閱讀 185評(píng)論 0 0
  • 我一直堅(jiān)持手寫日記,在一學(xué)習(xí)軟件上看見(jiàn)了“簡(jiǎn)書”的廣告,因?yàn)槊趾寐?tīng),就下載了,開(kāi)始研究這個(gè)產(chǎn)品的意義,由于初...
    可愛(ài)的狗閱讀 197評(píng)論 0 0

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