一文讀懂SpringMVC中的數據綁定

本文是對 SpringMVC 中數據綁定的總結。

1、SpringMVC 和 Struts2 的區別

Struts2 和 SpringMVC 都是 Web 開發中視圖層的框架,兩者都實現了數據的自動綁定,都不需要我們手動獲取參數然後關聯到對應的屬性上,下面就談談兩者的區別。

  • Spring MVC 是基於方法的,通過形參接收參數;Struts2 是基於類的,通過模型驅動封裝接收參數。
  • SpringMVC 將 url 和 controller 類中的方法映射,生成一個 Handler 對象來執行 method 方法;Struts2 根據配置文件將 url 和 action 類中的方法映射,生成 action 對象來執行 method 方法。
  • SpringMVC 形參接收參數,一個方法獨享 request response 數據,使用單例開發;Struts2 成員變量接收參數,多個方法共享成員變量,必須使用多例開發。
  • SpringMVC 的入口是 Servlet,一個方法對於一個 request 上下文,通過註解將 request 數據注入方法形參;而 Struts2 的入口是 Filter,攔截每個請求,創建一個 Action,調用成員變量的 getter、setter 方法將 reque 數據注入成員變量,兩者實現機制不同。
  • SpringMVC 更加輕量級,Struts2 配置很多,SpringMVC 開發效率和性能都比 Struts2 高。
  • SpringMVC 方法返回的數據更加靈活,使用 AJAX 進行 JSON 交互很方便;Struts2 的標籤數據渲染慢,不如 JSTL 標籤性能高。

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

2、不同類型的數據綁定

在開發中前後臺交互的數據無非是下面幾種:

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

下面就總結一下這些數據在 SpringMVC 中如何綁定到方法形參中。

使用 Maven 來搭建項目,所有的代碼都已上傳到 GitHub 上,有需要的小夥伴可以前往下載,也歡迎你 star 該倉庫哦!

在給方法加上 @ResponseBody 註解後,直接將處理好的數據輸出到響應流中,沒有了試圖解析過程,也就是返回的是 JSON 類型。SpringMVC 這裏使用了適配器模式來處理數據轉換,當我們使用 Jackson 作爲解析 JSON 工具,這裏注意一個大坑,Jackson 內默認的編碼爲 ISO-8859-1(大坑),這就會導致在輸出中文時亂碼,這點可以通過瀏覽器的控制檯查看,解決方法有以下幾種。

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

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

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

方法二、三詳細的配置如下:

<!--特別注意:必須放在mvc:annotation-driven前面,放在內部是不會生效的-->
    <!--json裝換器使用 Jackson 默認編碼是 ISO-8859-1 需要重新設置編碼-->
    <!--<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,默認就是 UTF-8 編碼,不需要再重新設置編碼-->
    <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>
    <!--配置註解驅動-->
    <mvc:annotation-driven/>

說明:項目名爲 springmvc,每個方法上面是測試地址哦。

2.1 基本類型

在傳參時方法中的形參名稱默認要和 url 中的參數名稱保持一致,也可以在方法中加 @RequestParam 註解修改 url 中的參數名稱。

基本類型中的基本數據類型(int,double)設置爲參數是不能爲空,否則將會報錯,而基本數據類型的包裝類型是可以爲 null,也即是沒有傳入時默認值爲 null,這裏也要注意上面提到的中文亂碼哦。

// http://localhost:8080/springmvc/basetype1.do?id=1
// http://localhost:8080/springmvc/basetype1.do?id= 不帶參數報錯
@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= 不帶參數不報錯,參數默認爲null
@RequestMapping(value = "basetype2.do")
@ResponseBody
public String baseType2(Integer id) {
    return "id=" + id;
}
// http://localhost:8080/springmvc/basetype3.do?name='湯姆' 注意中文亂碼問題
// 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 對象類型

實體類說明:

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

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

在傳對象類型的屬性時,url 中參數名稱爲對象的屬性名稱,不加對象名。

如果一個類中的屬性是另一個類,在傳參時,url 中參數名稱爲屬性對象名稱加屬性,如下面的第二個方法。

當傳入的對象類型參數相同時,如果不加以區分,會給同名的屬性都賦值,如下面的第三個方法,這裏的數據綁定就需要我們自定義,@InitBinder("對象名"),在自定義的方法(方法名任意)中設置屬性默認的前綴值,這樣就可以區分不同對象的屬性了。

// 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 日期類型

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

下面的方法一使用綁定數據時會按照用戶設置的格式初始化,但這種方法只對單個方法生效,我們可以自定義類型轉換類,轉換類需要實現 Converter 或者 Formatter 接口,具體的代碼如下。

實現 Converter 接口需要指定接口的兩個泛型,前者爲要轉換的類型,後者爲轉換後的類型,並且需要實現接口中的 convert() 方法,方法中的參數爲要轉換的類型,返回值爲轉換後的類型。

實現 Formatter 接口只需要指定接口的一個泛型,即轉換後的類型,但是要實現接口中的 parse() 方法和 print() 方法,前一個方法是將要轉換的類型轉換爲我們指定的類型,後一個方法是規定如何輸出轉換後的類型。

// 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("無效的日期格式,請使用" + 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);
    }
}

寫完自定義轉換類後,還需要在 SprinMVC 的配置文件中配置,這樣對所有的方法都生效,具體配置如下:

<!--配置自定義的日期類型轉換器-->
    <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 複雜類型

複雜類型包括數組和集合類型,像 List、Set、Map。

數組類型用於傳入多個參數名稱相同的值,如接收頁面上的複選框參數時。

SpringMVC 對於複雜類型的支持並不是很好,因爲對於複雜類型,我們更多都是使用 JSON、XML等數據格式來傳參。對於 List、Set、Map 這些類型,還需要單獨設置一個包裝類,屬性設置爲對應的集合類型,方法的參數爲包裝類型,比較繁瑣。SpringMVC 對複雜類型的數據綁定的功能,基本上就是雞肋。

類說明:

  • UserList 爲 User 對應的 List 集合包裝類,只有一個屬性,private List<User> users;
  • UserList 爲 User 對應的 Set 集合包裝類,只有一個屬性,private Set<User> users = new HashSet<>();,並且需要在該類的構造函數中初始化 Set 集合的大小,不能動態改變 Set 集合大小,在傳值時,對象的個數不能超過這個大小。
  • UserList 爲 User 對應的 Map 集合包裝類,只有一個屬性,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 注意特殊字符[]的轉義,不然會報錯
// http://localhost:8080/springmvc/complextype2.do?users%5B0%5D.name=Tom&users%5B1%5D.name=Lucy&users%5B6%5D.name=Mary 注意特殊字符[]的轉義,不然會報錯
@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 注意特殊字符[]的轉義,不然會報錯
@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 更適合現今前後端分離的數據傳輸,對於現在流行的格式化數據類型 JSON,支持很好,只需要 @RequestBody(傳參)和 @ResponseBody(輸出)兩個註解,使用起來很方便。

對於編寫 API,SpringMVC 無疑是比 Struts2 的有優勢。

// 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 風格

RESTful 風格的 API 已經受到業界的肯定,在當今的分佈式架構中更是如魚得水。很多 Web 框架也都支持 RESTful 風格的 API編寫,當然也包括 SpringMVC ,這裏簡單介紹一下 RESTful 風格。

RESTful 是 Resource Representional State Transfer 的縮寫,RE 是前面兩個單詞的簡寫,第一個單詞經常被省略,而這個單詞其實才是 RESTful 的核心思想,中文翻譯爲 資源表現層狀態轉換

RESTful 的作者也是 HTTP 協議的設計者,他將 HTTP 中的 URI 的思想引入到 API 編程中,每一個資源都有一個存放的位置,對資源的操作(請求)就是資源在表現層的轉態轉換,如常見的 GET、POST,還有不常用 PUT、DELETE 等。

RESTful 風格有更加簡短的資源地址,和一般的 API 地址直接對資源進行操作,如 add、select 不同,RESTful 風格的主體是資源,對資源的操作體現在請求方式上,如 DELETE。不同的請求方式對應不同的操作,如同一個地址,如果是 GET 方式,就直接返回頁面,如果是 POST 方式,就是提交頁面上的數據,這樣地址也更少,使得訪問也更加安全。

下面的代碼展示了 RESTful 風格的 API 如何使用,API 的測試,用瀏覽器並不方便,可以使用 Postman 等網絡工具。

@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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章