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
結果如下圖所示:
提交後格式化結果如下:
數據已經被格式化並輸出在視圖頁面當中。