8.1 数据格式化
如何从格式化的数据中获取真正的数据以完成数据绑定,并将处理完成的数据输出为格式化的数据是 Spring 格式化框架需要解决的问题。Spring 从 3.0 开始引入格式化转换框架,该框架位于 org.springframework.format 包。其中,最重要的是 Formatter<T> 接口。
Formatter 完成任意 Object 与 String 之间的类型转换,即格式化和解析,其支持细粒度和字段级别的格式化/解析。Formatter 只能将 String 转换成另一种 Java 类型,因此 Formatter 更适用于 Web 层的数据转换。
8.1.1 使用 Formatter 格式化数据
- 创建 DateFormatter 类,实现 org.springframework.format.Formatter 接口。
package formatter;
import org.springframework.format.Formatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
// 实现接口。
public class DateFormatter implements Formatter<Date> {
// 日期类型模板:如 yyyy-MM-dd。
private String datePattern;
// 日期格式化对象。
private SimpleDateFormat dateFormat;
// 构造器,通过依赖注入的日期类型创建日期格式化对象。
public DateFormatter(String datePattern){
this.datePattern = datePattern;
this.dateFormat = new SimpleDateFormat(datePattern);
}
// 解析文本字符串,返回一个 Formatter<T> 的 T 类型对象。
@Override
public Date parse(String source, Locale locale) throws ParseException {
try{
return dateFormat.parse(source);
}catch (Exception e){
throw new IllegalArgumentException();
}
}
// 显示 Formatter<T> 的 T 类型对象。
@Override
public String print(Date date, Locale locale) {
return dateFormat.format(date);
}
}
DateFormatter 类实现了接口中的两个方法:parse 方法,使用指定的 Locale 将一个 String 解析成目标 T 类型;print 方法,用于返回 T 类型的字符串表示形式。DateFormatter 类中使用了 SimpleDateFormat 对象将 String 转换成 Date 类型,日期类型模板 yyyy-MM-dd 之后会通过配置文件的依赖注入设置。
- 在 Spring MVC 配置文件中加入自定义格式化转换器。
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="controller"/>
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
<!-- 自定义的类型转换器 -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<list>
<bean class="formatter.DateFormatter" c:_0="yyyy-MM-dd"/>
</list>
</property>
</bean>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 后缀 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionServece 实现类,该类既具有类型转换功能,又具有格式化的功能。而 FormattingConversionServiceFactoryBean 工厂类正是用于 Spring 上下文中构造一个 FormattingConversionService 对象,通过这个工厂类,既可以注册自定义的转换器,还可以注册自定义的注解驱动逻辑。
以上配置使用 FormattingConversionServiceFactoryBean 对自定义的格式转换器 DateFormatter 进行了注册。FormattingConversionServiceFactoryBean 类有一个属性 converters。可以用它注册 Converter;有一个属性 formatters ,可以用它注册 Formatter。
注:<mvc:annotation-driven/>
标签内部默认创建的 ConversionService 实例 就是一个 FormattingConversionServiceFactoryBean,有了 FormattingConversionServiceFactoryBean 之后,Spring MVC 对处理方法的参数就绑定格式化功能了。
Spring 本身提供了很多常用的 Formatter 实现。在 org.springframework.format.datetime 包中提供了一个用于时间对象格式化的 DateFormatter 实现类。
在 org.springframework.format.number 包中提供了3个用于数字对象格式化的实现类:
- NumberFormatter。用于数字类型对象的格式化。
- CurrencyFormatter。用于货币类型对象的格式化。
- PercentFormatter。用于百分数数字类型对象的格式化。
8.1.2 使用 FormatterRegistrar 注册 Formatter
注册 Formatter 的另一种方法是使用 FormatterRegistrar。
- 新建 MyFormatterRegistrar 类,实现 FormatterRegistrar 接口,只需要实现一个 registerFormatters 方法,在该方法中添加需要注册的 Formatter。
package formatter;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;
public class MyFormatterRegistrar implements FormatterRegistrar {
private DateFormatter dateFormatter;
public void setDateFormatter(DateFormatter dateFormatter){
this.dateFormatter = dateFormatter;
}
@Override
public void registerFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addFormatter(dateFormatter);
}
}
- 修改 Spring MVC 配置文件,注册 Registrar。
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="controller"/>
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="dateFormatter" class="formatter.DateFormatter" c:_0="yyyy-MM-dd"/>
<!-- 自定义的类型转换器 -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatterRegistrars">
<set>
<bean class="formatter.MyFormatterRegistrar" p:dateFormatter-ref="dateFormatter"/>
</set>
</property>
</bean>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 后缀 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
8.2.1 使用 AnnotationFormatterFactory<A extends Annotation> 格式化数据
Spring 为开发者提供了注解驱动的属性对象格式化功能;在 Bean 属性中设置、Spring MVC 处理方法参数绑定数据、模型数据输出时自动通过注解应用格式化的功能。
在 org.springframework.format.annotation 包下面定义了两个格式化的注解类型:
1. DateTimeFormat
@DateTimeFormat 注解可以对 java.util.Date、java.util.Calendar 等时间类型的属性进行标注。它支持以下几个互斥的属性:
- iso。类型为 DateTimeFormat.ISO。以下是几个常用的可选值。
DateTimeFormat.ISO.DATE:格式为 yyyy-MM-dd。
DateTimeFormat.ISO.DATE_TIME:格式为 yyyy-MM-dd hh:mm:ss .SSSZ。
DateTimeFormat.ISO.TIME:格式为 hh:mm:ss .SSSZ。
DateTimeFormat.ISO.NONE:表示不使用 ISO 格式的时间。 - pattern。类型为 String,使用自定义的时间格式化字符串,如“yyyy-MM-dd hh:mm:ss”。
- style。类型为 String,通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的样式,第二位表示时间的格式,以下是几个常用的可选值。
S:短日期/时间的样式。
M:中日期/时间的样式。
L:长日期/时间的样式。
F:完整日期/时间的样式。
-:忽略日期/时间的样式。
2. NumberFormat
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性,具体说明如下:
- Pattern。类型为 String,使用自定义的数字格式化串,如“##,###。##”
- style。类型为 NumberFormat.Style,以下是几个常用的可选值:
NumberFormat.CURRENCY:货币类型。
NumberFormat.NUMBER:正常数字类型。
NumberFormat.PERCENT:百分数类型。
8.2.2 示例:使用 AnnotationFormatterFactory<A extends Annotation> 格式化数据
- 新建测试表单 testForm.jsp。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试表单数据格式化</title>
</head>
<body>
<form action="test" method="post">
<table>
<tr>
<td><label>日期类型:</label></td>
<td><input type="text" id="birthday" name="birthday"></td>
</tr>
<tr>
<td><label>整数类型:</label></td>
<td><input type="text" id="total" name="total"></td>
</tr>
<tr>
<td><label>百分数类型:</label></td>
<td><input type="text" id="discount" name="discount"></td>
</tr>
<tr>
<td>货币类型:</td>
<td><input type="text" id="money" name="money"></td>
</tr>
<tr>
<td><input id="submit" type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
</html>
- 新建 User 类负责接收页面数据。
package domain;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
// 日期类型
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
// 正常数字类型
@NumberFormat(style = NumberFormat.Style.NUMBER,pattern = "#,###")
private int total;
// 百分数类型
@NumberFormat(style = NumberFormat.Style.PERCENT)
private double discount;
// 货币类型
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private double money;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public double getDiscount() {
return discount;
}
public void setDiscount(double discount) {
this.discount = discount;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
User 类的多个属性使用了 DateTimeFormat 和 NumberFormat 注解,用于将页面传递的 String 转换成对应的格式化数据。
- 创建 UserController 类控制页面跳转。
package controller;
import domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class UserController {
private static final Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value = "/{formName}")
public String loginForm(@PathVariable String formName){
//动态跳转页面
return formName;
}
@RequestMapping(value = "/test",method = RequestMethod.POST)
public String test(@ModelAttribute User user, Model model){
logger.info(user);
model.addAttribute("user",user);
return "success";
}
}
- 新建 success.jsp 页面显示格式化后的数据。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试表单数据格式化</title>
</head>
<body>
<form:form modelAttribute="user" method="post" action="">
<table>
<tr>
<td>日期类型:</td>
<td><form:input path="birthday"/></td>
</tr>
<tr>
<td>整数类型:</td>
<td><form:input path="total"/></td>
</tr>
<tr>
<td>百分数类型:</td>
<td><form:input path="discount"/></td>
</tr>
<tr>
<td>货币类型:</td>
<td><form:input path="money"/></td>
</tr>
</table>
</form:form>
</body>
</html>
如果希望在视图页面中将模型属性数据以格式化的方式进行渲染,则需要使用 Spring 的页面标签显示模型数据。所以 success.jsp 中使用了 <form:form modelAttribute="user" >
标签,并且绑定了 User 对象。
- 修改 Spring MVC 配置文件。
<?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: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
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="controller"/>
<!-- 装配自定义的类型转换器 -->
<mvc:annotation-driven/>
<!-- 使用默认的 Servlet 来响应静态文件 -->
<mvc:default-servlet-handler/>
<!-- 视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix">
<value>/WEB-INF/</value>
</property>
<!-- 后缀 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
在配置文件中只是使用了默认的 <mvc:annotation-driven/>
标签,该标签内部默认创建的 ConversionService 实例就是一个 FormattingConversionServiceFactoryBean,这样就可以支持注解驱动的格式化功能了。
- 修改 web.xml 配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署 Web 应用。
在浏览器中输入如下 URL 来测试应用:
http://localhost:8080/AnnotationFormatterTest/testForm
结果如下图所示:
提交后格式化结果如下:
数据已经被格式化并输出在视图页面当中。