SpringMVC
数据转换
-
springMVC
数据绑定流程 - 使用
ConversionService
转换数据 - 使用
PropertyEditor
、@InitBinder
转换数据 - 使用
WebBindingInitializer
转换数据
1. springMVC
数据绑定流程
-
Spring MVC
主框架将ServletRequest
对象及目标方法的入参实例传递给WebDataBinderFactory
实例,以创建DataBinder
实例对象。 -
DataBinder
调用装配在Spring Web
上下文的ConversionService
组件进行 数据类型转换、数据格式化 工作。将ServletRequest
中的请求信息填充到入参对象中。 - 调用
Validtor
组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingResult
对象。 -
Spring MVC
抽取BindingResult
中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。
-
-
Spring MVC
通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder
,运行机制如图片所示:
2 数据转换异常
2.1 实体类中的变量为java.util.Date
类型
- 未配置自定义字符转换器时报的异常:
Field error in object 'user' on field 'birthday': rejected value [1995-10-10]; codes [typeMismatch.user.birthday,typeMismatch.birthday,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.birthday,birthday]; arguments []; default message [birthday]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthday'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '1995-10-10'; nested exception is java.lang.IllegalArgumentException]]
- 解决方式:
springmvc-config.xml
中添加自定义字符转换器配置即可解决。传入字符串1995-10-10
,实体变量接收到转换的数据应为:Tue Oct 10 00:00:00 CST 1995
2.2 实体类中的变量为java.sql.Date
类型
正常情况下实体类参数不应该为java.sql.Date
类型。
- 传入字符串
1995-10-10
,实体类接收为birthday=1995-10-10
,不报错。 - 传入其他格式
1995-10-10 14:30:30
、1995/10/10
等均报错。
查看源码可知,只支持传入(yyyy-[m]m-[d]d)
格式:
/**
* Converts a string in JDBC date escape format to
* a <code>Date</code> value.
*
* @param s a <code>String</code> object representing a date in
* in the format "yyyy-[m]m-[d]d". The leading zero for <code>mm</code>
* and <code>dd</code> may also be omitted.
* @return a <code>java.sql.Date</code> object representing the
* given date
* @throws IllegalArgumentException if the date given is not in the
* JDBC date escape format (yyyy-[m]m-[d]d)
*/
public static Date valueOf(String s) {
final int YEAR_LENGTH = 4;
final int MONTH_LENGTH = 2;
final int DAY_LENGTH = 2;
final int MAX_MONTH = 12;
final int MAX_DAY = 31;
int firstDash;
int secondDash;
Date d = null;
if (s == null) {
throw new java.lang.IllegalArgumentException();
}
firstDash = s.indexOf('-');
secondDash = s.indexOf('-', firstDash + 1);
if ((firstDash > 0) && (secondDash > 0) && (secondDash < s.length() - 1)) {
String yyyy = s.substring(0, firstDash);
String mm = s.substring(firstDash + 1, secondDash);
String dd = s.substring(secondDash + 1);
if (yyyy.length() == YEAR_LENGTH &&
(mm.length() >= 1 && mm.length() <= MONTH_LENGTH) &&
(dd.length() >= 1 && dd.length() <= DAY_LENGTH)) {
int year = Integer.parseInt(yyyy);
int month = Integer.parseInt(mm);
int day = Integer.parseInt(dd);
if ((month >= 1 && month <= MAX_MONTH) && (day >= 1 && day <= MAX_DAY)) {
d = new Date(year - 1900, month - 1, day);
}
}
}
if (d == null) {
throw new java.lang.IllegalArgumentException();
}
return d;
}
3. 使用ConversionService
转换数据
3.1 自定义类型转换器实现Converter<S,T>
接口
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
// 实现Converter<S,T>接口
public class StringToDateConverter implements Converter<String, Date> {
//日期类型模板。如:"yyyy-MM-dd"
private String datePattern;
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
// Converter<S,T>接口的类型转换方法
@Override
public Date convert(String date) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(this.datePattern);
// 将日期字符串转换成Date类型返回
return sdf.parse(date);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("日期转换失败!");
return null;
}
}
}
3.2 springMVC
配置文件中配置
springmvc-config.xml
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
<!-- 自定义的类型转换器 -->
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.zhougl.web.converter.StringToDateConverter"
p:datePattern="yyyy-MM-dd"></bean>
</list>
</property>
</bean>
- 注必须引入声明
xmlns:p="http://www.springframework.org/schema/p"
,否则会报错:与元素类型 "bean" 相关联的属性 "p:datePattern" 的前缀 "p" 未绑定。
3.3 测试代码
3.3.1 实体类
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 8393614516445523837L;
private String loginName;
private Date birthday;
public User() {
super();
}
public User(String loginName,Date birthday) {
super();
this.loginName = loginName;
this.birthday = birthday;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
3.3.2 Controller
控制层
@Controller
@RequestMapping(value="/converterTest")
public class UserController {
private static final Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value="/{formName}")
public String loginForm(@PathVariable String formName) {
//动态跳转页面
return "converterTest/"+formName;
}
@RequestMapping(value="/register",method=RequestMethod.POST)
public String register(@ModelAttribute User user,Model model) {
logger.info(user);
model.addAttribute("user", user);
return "converterTest/success";
}
}
3.3.3 jsp
/converterTest/registerForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试数据转换器</title>
</head>
<body>
<h3>注册页面</h3>
<form action="register" method="post">
<table>
<tr>
<td><label>登录名:</label></td>
<td><input type="text" id="loginName" name="loginName"></td>
</tr>
<tr>
<td><label>生日:</label></td>
<td><input type="text" id="birthday" name="birthday"></td>
</tr>
<tr>
<td><input type="submit" id="submit" value="登录"></td>
</tr>
</table>
</form>
</body>
</html>
/converterTest/success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试ConversionService</title>
</head>
<body>
登录名:${requestScope.user.loginName }<br>
生日:<fmt:formatDate value="${requestScope.user.birthday}"
pattern="yyyy年MM月dd日"/><br>
</body>
</html>
- 注:使用
jstl
标签需要引入jstl.jar
,否则会报错:org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/fmt cannot be resolved in either web.xml or the jar files deployed with this application
4. 使用PropertyEditor
转换数据
PropertyEditor
是属性编辑器的接口,它规定了将外部设置值转换为内部JavaBean
属性值的转换接口方法。PropertyEditor
主要的接口方法说明如下:
-
Object getValue()
:返回属性的当前值。基本类型被封装成对应的包装类实例; -
void setValue(Object newValue)
:设置属性的值,基本类型以包装类传入(自动装箱); -
String getAsText()
:将属性对象用一个字符串表示,以便外部的属性编辑器能以可视化的方式显示。缺省返回null,表示该属性不能以字符串表示; -
void setAsText(String text)
:用一个字符串去更新属性的内部值,这个字符串一般从外部属性编辑器传入; -
String[] getTags()
:返回表示有效属性值的字符串数组(如boolean
属性对应的有效Tag
为true
和false
),以便属性编辑器能以下拉框的方式显示出来。缺省返回null
,表示属性没有匹配的字符值有限集合; -
String getJavaInitializationString()
:为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值。
4.1 自定义属性编辑器继承PropertyEditorSupport
类
//自定义属性编辑器
public class DateEditor extends PropertyEditorSupport {
private String datePattern = "yyyy-MM-dd";
public DateEditor(String strPattern) {
this.datePattern = strPattern;
}
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
// 将传如的字符串数据转换成Date类型
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat sdf = new SimpleDateFormat(this.datePattern);
try {
Date date = sdf.parse(text);
super.setValue(date);//设置属性的值
} catch (ParseException e) {
e.printStackTrace();
}
}
}
4.2 使用@InitBinder
在控制层中初始化编辑器
在Controller层增加
// 在控制器初始化时注册属性编辑器
@InitBinder
public void initBinder(WebDataBinder binder){
// 注册自定义编辑器
//binder.registerCustomEditor(Date.class, new DateEditor());
binder.registerCustomEditor(Date.class, new DateEditor("yyyy-MM-dd HH:mm:ss"));
}
4.3 利用CustomEditorConfigurer
类配置初始化
CustomEditorConfigurer
类配置初始化
CustomEditorConfigurer
类 用于实现在Spring
中注册自己定义的编辑器 。它是Spring
当中一个非常有用的工厂后处理类(工厂后处理通过Spring
的BeanFactoryPostProcessor
接口实现, 它是在Spring
容器启动并初始化之后进行对Spring
容器的操作类)。在Spring
中已经注册了不少编辑器类,他们都用于String
类型转换为其他的数据类型,如URL,Date
等。
配置
CustomEditorConfigurer
类:
CustomEditorConfigurer
类中有一个customEditor
属性,它是一个Map
类型。通过配置它便实现了自定义的编辑器注册。这个Map
的键值对对应着转换类型和编辑器(转换类型是Key
,编辑器是Value
)。
自定义编辑器可以简化
Spring
的装配Bean
。使其更加的简单。不容易发生配置错误。 PS:如果使用Spring
的ApplicationContext
容器,那么只需在Spring
的配置文件中进行简单的装配,而对于Bean
工厂可能需要手动的注册才能使用。
DateEditor.java
//自定义属性编辑器
public class DateEditor extends PropertyEditorSupport {
private String datePattern = "yyyy-MM-dd";
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
// 将传如的字符串数据转换成Date类型
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat sdf = new SimpleDateFormat(this.datePattern);
try {
Date date = sdf.parse(text);
super.setValue(date);//设置属性的值
} catch (ParseException e) {
e.printStackTrace();
}
}
public DateEditor(String datePattern) {
super();
this.datePattern = datePattern;
}
}
DateEditorRegistrar.java
import java.beans.PropertyEditor;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
public class DateEditorRegistrar implements PropertyEditorRegistrar {
private Map<Class<?>, PropertyEditor> customEditors;
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
if (customEditors != null) {
Set<Map.Entry<Class<?>, PropertyEditor>> entries = customEditors.entrySet();
for (Map.Entry<Class<?>, PropertyEditor> entry : entries) {
registry.registerCustomEditor(entry.getKey(), entry.getValue());
}
}
}
public Map<Class<?>, PropertyEditor> getCustomEditors() {
return customEditors;
}
public void setCustomEditors(Map<Class<?>, PropertyEditor> customEditors) {
this.customEditors = customEditors;
}
}
- spring 配置1
<bean id="customEditorRegistrar"
class="com.zhougl.web.converter.DateEditorRegistrar">
<property name="customEditors">
<map>
<entry key="java.util.Date" value-ref="customDateEditor" />
</map>
</property>
</bean>
<bean id="customDateEditor"
class="com.zhougl.web.converter.DateEditor">
<constructor-arg type="String"
value="yyyy-MM-dd HH:mm:ss" />
</bean>
<bean id="customEditorConfigurer"
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customEditorRegistrar" />
</list>
</property>
</bean>
- spring 配置2
<bean id="user" class="com.zhougl.web.bean.User">
<property name="loginName" value="Lokesh" />
<property name="password" value="Gupta" />
<property name="userName" value="Manager" />
<property name="birthday" value="2007-09-30 14:30:30" />
</bean>
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date" value="com.zhougl.web.converter.DateEditor"/>
</map>
</property>
</bean>
- 测试类
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user.getBirthday());
System.out.println(user.toString());
}
总结:
以上配置,在项目中均不起作用,只有配置2在测试类中起作用,配置1不起作用。有待研究。
5. 使用WebBindingInitializer
转换数据
可以通过实现
WebBindingInitializer
接口,实现类中的自定义编辑器,注册全局自定义编辑器转换数据。
5.1 实现WebBindingInitializer
接口
//实现WebBindingInitializer接口
public class DateBindingInitializer implements WebBindingInitializer {
@Override
public void initBinder(WebDataBinder binder) {
// 注册自定义编辑器
binder.registerCustomEditor(Date.class, new DateEditor("yyyy-MM-dd HH:mm:ss"));
}
}
-
DateEditor
同4.1
5.2 springmvc配置
spring-webmvc-4.2.0
<!-- 通过AnnotationMethodHandlerAdapter装配自定义编辑器 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="com.zhougl.web.binding.DateBindingInitializer" />
</property>
</bean>
-
spring5
之后AnnotationMethodHandlerAdapter
被org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
替代;使用<mvc:annotation-driven>
扫描时,自动注册了该类,<mvc:annotation-driven />
配置中已经包含了webBindingInitializer
的配置,所以该配置放在<mvc:annotation-driven />
后面不会起作用。 -
<mvc:annotation-driven />
必须放到最后,否则全局binder不起作用。 - 与
@InitBinder
功能一致,只是此处为注册全局变量。
6. 多种转换器的优先顺序
- 查询通过
@InitBinder
装配的自定义编辑器 - 查询通过
ConversionService
装配的自定义转换器 - 查询通过
WebBindingInitializer
接口装配的全局自定义编辑器
- 查询通过
总结:
使用时
<mvc:annotation-driven />
配置时,使用conversion-service来注册自定义的converter实现自定义的类型转换,即采用3小节使用的方法
网友评论