一文读懂SpringMVC中的数据绑定

本文是对 SpringMVC 中数据绑定的总结。html

一、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 框架了。java

二、不一样类型的数据绑定

在开发中先后台交互的数据无非是下面几种:git

  • 基本类型(int、double、Integer、String 等)
  • 对象(类)类型(自定义的实体类)
  • 日期类型(java.util.Date)
  • 复杂类型(对象数组、List、Set、Map 等)
  • 特殊文本类型(JSON、XML 等)

下面就总结一下这些数据在 SpringMVC 中如何绑定到方法形参中。github

使用 Maven 来搭建项目,全部的代码都已上传到 GitHub 上,有须要的小伙伴能够前往下载,也欢迎你 star 该仓库哦!web

在给方法加上 @ResponseBody 注解后,直接将处理好的数据输出到响应流中,没有了试图解析过程,也就是返回的是 JSON 类型。SpringMVC 这里使用了适配器模式来处理数据转换,当咱们使用 Jackson 做为解析 JSON 工具,这里注意一个大坑,Jackson 内默认的编码为 ISO-8859-1(大坑),这就会致使在输出中文时乱码,这点能够经过浏览器的控制台查看,解决方法有如下几种。spring

一、在每一个方法上加上编码设置@RequestMapping(value = "basetype3.do", produces = "application/json; charset=utf-8")编程

二、在 SpringMVC 配置文件中修改 Jackson 的默认编码为 UTF-8,注意要放在 <mvc:annotation-driven/> 前面,放在内部是不生效的。json

三、更改 JSON 解析工具,推荐使用阿里的 fastjson,默认编码就是 UTF-8,解析速度也比 Jackson 快。后端

方法2、三详细的配置以下:

<!--特别注意:必须放在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;
}
复制代码

欢迎关注下方的微信公众号哦,里面有各类学习资料免费分享哦!

编程心路
相关文章
相关标签/搜索