概述
Spring MVC-05循序渐进之数据绑定和form标签库(上) 和 Spring MVC-05循序渐进之数据绑定和form标签库(下) 实战从0到1 我们已经学习了数据绑定,见识了数据绑定的方便性。
但是Spring的数据绑定并非没有任何限制, 比如Spring总是试图使用more的语言区域将日期输入绑定到java.uti.Date上,假设我们想让Spring使用不同的格式日期,就需要一个Converter或者Formatter来协助完成了。
本篇博文将重点讨论Converter和Formatter的内容。 这两者均可以用于将一种对象类型转换成另外一种对象类型。 Converter是通用元件,可以在应用程序的任意层使用,而Formatter则是专门为Web层设计的
converter
Spring 的Converter是可以将一种类型转换成另外一种类型的一个对象。
举个例子,用户输入的日期格式可能有许多种,比如“January 10,2018”、“10/01/2018”、“2018-01-10”,这些都表示同一个日期。 默认情况下,Spring会将期待用户输入的日期样式和当前语言区域的日期样式相同。
比如US用户,就是月/日/年的格式。 如果希望Spring在将输入的日期字符串绑定到Date时使用不同的日期格式,则需要编写一个Converter,才能将字符串转换成日期。
Step 1 实现Converter接口
为了创建自定义的Converter需要实现 org.springframework.core.convert.converter.Converter接口
我们来看下该接口
package org.springframework.core.convert.converter; /** * A converter converts a source object of type {@code S} to a target of type {@code T}. * * <p>Implementations of this interface are thread-safe and can be shared. * * <p>Implementations may additionally implement {@link ConditionalConverter}. * * @author Keith Donald * @since 3.0 * @param <S> the source type * @param <T> the target type */ public interface Converter<S, T> { /** * Convert the source object of type {@code S} to target type {@code T}. * @param source the source object to convert, which must be an instance of {@code S} (never {@code null}) * @return the converted object, which must be an instance of {@code T} (potentially {@code null}) * @throws IllegalArgumentException if the source cannot be converted to the desired target type */ T convert(S source); }
这里的泛型 S表示源类型, 泛型T表示目标类型。 比如为了创建一个可以将String转为Date的Converter,可以像下面这样声明
public class MyConverter implements Converter<String, Date> { }
当然了要重写convert方法。
@Override public Date convert(String source){ }
该例中的代码如下
MyConverter.java
package com.artisan.converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.core.convert.converter.Converter; /** * * @ClassName: MyConverter * @Description: 自定义Converter,将String类型的数据转换为指定格式的Date * @author Mr.Yang * @date 2018年2月10日 * */ public class MyConverter implements Converter<String, Date> { private String datePattern; private Date targetFormateDate; /** * * 创建一个新的实例 MyConverter. 默认构造函数 * */ public MyConverter() { super(); } /** * * 创建一个新的实例 MyConverter. 实例化时指定日期格式 * * @param datePattern */ public MyConverter(String datePattern) { super(); this.datePattern = datePattern; } /** * 重写convert方法 */ @Override public Date convert(String source) { try { SimpleDateFormat sdf = new SimpleDateFormat(datePattern); // 是否严格解析日期,设置false禁止SimpleDateFormat的自动计算功能 sdf.setLenient(false); targetFormateDate = sdf.parse(source); } catch (ParseException e) { // the error message will be displayed when using <form:errors> e.printStackTrace(); throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + datePattern + "\""); } return targetFormateDate; } } /** * 如果设置为true,假设你输入的日期不合法,它会先进行一定的计算.计算出能有合法的值,就以计算后的值为真正的值. * * 比如说当你使用的时候有2012-02-31,2012-14-03这样数据去format, * 如果setLenient(true).那么它就会自动解析为2012-03-02和2013-02-03这样的日期. * 如果setLenient(false),2012-14-03就会出现解析异常,因为去掉了计算,而这样的数据又是不合法的 * */
Step 2 SpringMVC配置文件中配置bean及设置conversion-service属性
为了在Spring MVC中使用自定义的Converter,需要在SpringMVC的配置文件中配置一个conversionService ,该Bean的名字必须为
org.springframework.context.support.ConversionServiceFactoryBean 。同时必须包含一个converters属性,它将列出要在应用程序中使用的所有定制的Converter.
紧接着要给annotation-driven元素的conversion-service属性赋bean名称。
完整配置文件如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描控制层的注解,使其成为Spring管理的Bean --> <context:component-scan base-package="com.artisan.controller"/> <!-- 静态资源文件 --> <!-- (2)将自定义的convert设置给conversion-service属性 --> <mvc:annotation-driven conversion-service="conversionService"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/*.jsp" location="/"/> <!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- (1)自定义converter --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.artisan.converter.MyConverter"> <constructor-arg type="java.lang.String" value="MM-dd-yyyy"/> </bean> </list> </property> </bean> </beans>
小Demo
Domain类
package com.artisan.domain; import java.io.Serializable; import java.util.Date; public class Artisan implements Serializable { private static final long serialVersionUID = -908L; private long id; private String firstName; private String lastName; private Date birthDate; private int salaryLevel; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public int getSalaryLevel() { return salaryLevel; } public void setSalaryLevel(int salaryLevel) { this.salaryLevel = salaryLevel; } }
Controller
package com.artisan.controller; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.artisan.domain.Artisan; /** * * @ClassName: ArtisanController * @Description: @Controller标注的Artisan控制层 * @author Mr.Yang * @date 2018年2月10日 * */ @Controller public class ArtisanController { private Logger logger = Logger.getLogger(ArtisanController.class); @RequestMapping(value="/artisan_input") public String inputArtisan(Model model) { model.addAttribute(new Artisan()); return "ArtisanForm"; } @RequestMapping(value="/artisan_save") public String saveArtisan(@ModelAttribute Artisan artisan, BindingResult bindingResult, Model model) { // 如果输入错误,跳转到ArtisanForm页面 if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); logger.info("Code:" + fieldError.getCode() + ", field:" + fieldError.getField()); return "ArtisanForm"; } // save Artisan here model.addAttribute("artisan", artisan); return "ArtisanDetails"; } }
前台JSP
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Add Artisan Form</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <form:form commandName="artisan" action="artisan_save" method="post"> <fieldset> <legend>Add an Artisan</legend> <p> <label for="firstName">First Name: </label> <form:input path="firstName" tabindex="1"/> </p> <p> <label for="lastName">Last Name: </label> <form:input path="lastName" tabindex="2"/> </p> <p> <!-- 接收显示错误信息 --> <form:errors path="birthDate" cssClass="error"/> </p> <p> <label for="birthDate">Date Of Birth: </label> <form:input path="birthDate" tabindex="3" /> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Artisan"> </p> </fieldset> </form:form> </div> </body> </html>
展示页面
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML> <html> <head> <title>Save Artisan</title> <style type="text/css">@import url("<c:url value="/css/main.css"/>");</style> </head> <body> <div id="global"> <h4>The Artisan details have been saved.</h4> <p> <h5>Details:</h5> First Name: ${artisan.firstName}<br/> Last Name: ${artisan.lastName}<br/> Date of Birth: ${artisan.birthDate} </p> </div> </body> </html>
formatter
Formatter就像Converter一样,也是将一种类型转换为另外一种类型,但是Formatter的源类型必须是String,而Converter的源类型可以是任意类型。
Formatter更加适合Web层,而Converter则可以在任意层中。
为了转换SpringMVC应用程序中的表单的用户输入,始终应该选择Formatter而不是Converter
Step 1 实现Formatter接口
我们先看下
org.springframework.format.Formatter的源码
public interface Formatter<T> extends Printer<T>, Parser<T> { }
public interface Printer<T> { /** * Print the object of type T for display. * @param object the instance to print * @param locale the current user locale * @return the printed text string */ String print(T object, Locale locale); }
public interface Parser<T> { /** * Parse a text String to produce a T. * @param text the text string * @param locale the current user locale * @return an instance of T * @throws ParseException when a parse exception occurs in a java.text parsing library * @throws IllegalArgumentException when a parse exception occurs */ T parse(String text, Locale locale) throws ParseException; }
可以知道需要重写两个方法
- parse方法利用指定Locale将一个String解析为目标类型
- print方法与之相反,它是返回目标对象的字符串表示法
来看下具体的用法
编写自定义Formatter类
package com.artisan.converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import org.springframework.format.Formatter; public class MyFormatter implements Formatter<Date> { private String datePattern; private SimpleDateFormat dateFormat; public MyFormatter(String datePattern) { this.datePattern = datePattern; dateFormat = new SimpleDateFormat(datePattern); dateFormat.setLenient(false); } @Override public String print(Date date, Locale locale) { return dateFormat.format(date); } @Override public Date parse(String s, Locale locale) throws ParseException { try { return dateFormat.parse(s); } catch (ParseException e) { e.printStackTrace(); // the error message will be displayed when using <form:errors> throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + datePattern + "\""); } } }
Step 2 SpringMVC配置文件中配置bean及设置conversion-service属性
注册配置文件
package com.artisan.converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import org.springframework.format.Formatter; public class MyFormatter implements Formatter<Date> { private String datePattern; private SimpleDateFormat dateFormat; public MyFormatter(String datePattern) { this.datePattern = datePattern; dateFormat = new SimpleDateFormat(datePattern); dateFormat.setLenient(false); } @Override public String print(Date date, Locale locale) { return dateFormat.format(date); } @Override public Date parse(String s, Locale locale) throws ParseException { try { return dateFormat.parse(s); } catch (ParseException e) { e.printStackTrace(); // the error message will be displayed when using <form:errors> throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + datePattern + "\""); } } }
修改SpringMVC配置文件
有了Registrar,就不用再SpringMVC配置文件中注册任何Formatter了,只要在SpringMVC配置文件中注册Registrar就可以了
Step 3 SpringMVC配置文件中配置bean及设置conversion-service属性
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描控制层的注解,使其成为Spring管理的Bean --> <context:component-scan base-package="com.artisan.controller"/> <!-- 静态资源文件 --> <!-- (2)将自定义的convert设置给conversion-service属性 --> <mvc:annotation-driven conversion-service="conversionService"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/*.jsp" location="/"/> <!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- (1)自定义fromatter --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatterRegistrars"> <set> <bean class="com.artisan.converter.MyFormatterRegistrar"> <constructor-arg type="java.lang.String" value="MM-dd-yyyy" /> </bean> </set> </property> </bean> </beans>
测试结果同上。
converter or formatter 小结
Converter 是一般工具 ,可以将将一种类型转换为另外一种类型,比如将String 转为Date , Long 转为Date .既可以适应在Web层,也可以使用在其他层中。
Formatter的源类型必须是String,比如将String 转为Date , 但是不能将Long 转为Date
**为了转换SpringMVC应用程序中的表单的用户输入,始终应该选择Formatter而不是Converter**Formatter更加使用Web层。
源码
代码已提交到github