Struts2 和 SpringMVC 都是 Web 开发中视图层的框架,两者都实现了数据的自动绑定,都不需要我们手动获取参数然后关联到对应的属性上,下面就谈谈两者的区别。
这两个框架我都用过,这里仅是个人看法,Struts2 的配置真的是写死人,类的限制使得使用也不够灵活,与一些前端框架的结合也不是很方便,个人是放弃 Struts2 框架了。
在开发中前后台交互的数据无非是下面几种:
下面就总结一下这些数据在 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,每个方法上面是测试地址哦。
在传参时方法中的形参名称默认要和 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; } 复制代码
实体类说明:
类中生成属性的 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."); } 复制代码
大多数情况下,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(); } 复制代码
复杂类型包括数组和集合类型,像 List、Set、Map。
数组类型用于传入多个参数名称相同的值,如接收页面上的复选框参数时。
SpringMVC 对于复杂类型的支持并不是很好,因为对于复杂类型,我们更多都是使用 JSON、XML等数据格式来传参。对于 List、Set、Map 这些类型,还需要单独设置一个包装类,属性设置为对应的集合类型,方法的参数为包装类型,比较繁琐。SpringMVC 对复杂类型的数据绑定的功能,基本上就是鸡肋。
类说明:
private List<User> users; private Set<User> users = new HashSet<>(); 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(); } 复制代码
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; } 复制代码
RESTful 风格的 API 已经受到业界的肯定,在当今的分布式架构中更是如鱼得水。很多 Web 框架也都支持 RESTful 风格的 API编写,当然也包括 SpringMVC ,这里简单介绍一下 RESTful 风格。
RESTful 是 Re source Re presentional S tate T ransfer 的缩写,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; } 复制代码
欢迎关注下方的微信公众号哦,里面有各种学习资料免费分享哦!