Spring MVC的數(shù)據(jù)轉(zhuǎn)換及數(shù)據(jù)格式化

Spring MVC會(huì)根據(jù)請(qǐng)求方法簽名不同场钉,將請(qǐng)求消息中信息以一定方式轉(zhuǎn)換并綁定到請(qǐng)求方法的參數(shù)中阻桅。

1.數(shù)據(jù)綁定流程

Spring MVC通過反射機(jī)制對(duì)目標(biāo)處理方法的簽名進(jìn)行分析冰单,并將請(qǐng)求消息綁定到處理方法的參數(shù)上。數(shù)據(jù)綁定的核心部件是Databinder

Spring MVC數(shù)據(jù)綁定機(jī)制
  • 1.Spring MVC框架將ServletRequest對(duì)象及處理方法的參數(shù)實(shí)例傳遞給DataBinder斤程。

  • 2.DataBinder調(diào)用裝配在Spring Web上下文中的ConversionService組件進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換榆浓、
    數(shù)據(jù)格式化工作,并將ServletRequest中的消息填充到參數(shù)對(duì)象中弱贼。

  • 3.然后再調(diào)用Validator組件對(duì)已經(jīng)綁定的請(qǐng)求消息數(shù)據(jù)的參數(shù)對(duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn)蒸苇。

  • 4.最終生成數(shù)據(jù)綁定結(jié)果BindingResult對(duì)象,BindingResult包含已完成數(shù)據(jù)綁定的參數(shù)對(duì)象吮旅,還包含相應(yīng)的校驗(yàn)錯(cuò)誤的對(duì)象溪烤。

  • 5.Spring MVC抽取BindingResult中的參數(shù)對(duì)象及校驗(yàn)對(duì)象,將它們賦給處理方法的相應(yīng)參數(shù)庇勃。

2.數(shù)據(jù)轉(zhuǎn)換(ConversionService)

在Java語言中檬嘀,在java.beans包中提供了一個(gè)PropertyEditor接口來進(jìn)行數(shù)據(jù)轉(zhuǎn)換(只能用于字符串和Java對(duì)象的轉(zhuǎn)換)。其功能就是將一個(gè)字符串轉(zhuǎn)換為一個(gè)Java對(duì)象责嚷。

Spring 3.0鸳兽,添加了一個(gè)通用的類型轉(zhuǎn)換模塊,位于org.springframework.core.convert包中罕拂。

2.1 ConversionServiceboolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)

org.springframework.core.convert.ConversionService是Spring類型轉(zhuǎn)換的核心接口揍异。

  • boolean canConvert(Class<?> sourceType, Class<?> targetType)

判斷是否可以將一個(gè)Java類轉(zhuǎn)換為另一個(gè)Java類

  • boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)

TypeDescriptor不但描述了需要轉(zhuǎn)換類的信息,還描述了類的上下文信息爆班。這樣可以利用這些信息做出更多的各種靈活的控制衷掷。

  • <T> T convert(Object source, Class<T> targetType)

將源類型對(duì)象轉(zhuǎn)換為目標(biāo)類型對(duì)象

  • Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)

將源類型從源類型對(duì)象轉(zhuǎn)換為目標(biāo)類型對(duì)象,通常利用到類中的上下文信息

可以利用org.springframework.context.support.ConversionServiceFactoryBean在Spring的上下文中定義一個(gè)ConversionService柿菩。Spring將自動(dòng)識(shí)別出上下文種的ConversionService戚嗅,并在Spring MVC處理方法的參數(shù)綁定中使用它進(jìn)行數(shù)據(jù)轉(zhuǎn)換。

<bean class="org.springframework.context.support.ConversionServiceFactoryBean"/>

2.2 Spring支持的轉(zhuǎn)換器

Spring 在org.springframework.core.convert.converter包中定義了3種類型的轉(zhuǎn)換器接口枢舶,我們可以實(shí)現(xiàn)其中任意一種轉(zhuǎn)換接口懦胞,并將它作為自定義轉(zhuǎn)換器注冊(cè)到ConversionServiceFactoryBean當(dāng)中。

  • Converter<S, T>

Spring中最簡(jiǎn)單的一個(gè)轉(zhuǎn)換器接口祟辟。該方法負(fù)責(zé)將S類型轉(zhuǎn)換為T類型的對(duì)象医瘫。

  • ConverterFactory<S, R>

如果希望將一種類型的對(duì)象轉(zhuǎn)換為另一種類型及其子類對(duì)象,比如將String類型轉(zhuǎn)換為Number以及Number的子類Integer旧困、Double等對(duì)象醇份。就需要一系列的Converter。該接口的作用就是將這一系列的相同的Converter封裝在一起吼具。

  • GenericConverter

Converter<S僚纷,T>只是負(fù)責(zé)將一個(gè)類型的對(duì)象轉(zhuǎn)換為另一個(gè)類型的對(duì)象,它并沒有考慮類型對(duì)象的上下文信息拗盒。因此不能完成復(fù)雜類型的轉(zhuǎn)換工作怖竭。而該接口會(huì)根據(jù)源類型的對(duì)象及其上下文進(jìn)行類型轉(zhuǎn)換。

Code

public class User implements Serializable {
    private String loginname;

    private Date 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;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Sign Up</title>
</head>
<body>
<form action="/user/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>
@RequestMapping(value = "register", method = RequestMethod.POST)
public String register(@ModelAttribute User user, Model model) {
    model.addAttribute("user", user);
    return "success";
}

這時(shí)候陡蝇,前臺(tái)輸入的生日為String格式的痊臭。而User實(shí)體定義的是Date時(shí)間類型的哮肚。那么后臺(tái)再接收的時(shí)候,就會(huì)報(bào)錯(cuò)广匙。

這時(shí)候允趟,我們就自定義類型轉(zhuǎn)換器。實(shí)現(xiàn)ConversionService里面的最簡(jiǎn)單的Converter<S,T>

public class StringToDateConverter implements Converter<String, Date> {

    /**
     * 日期類型模板鸦致,如yyyy-MM-dd
     */
    private String datePattern;

    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }

    @Override
    public Date convert(String date) {
        Date result = null;
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(this.datePattern);
            result = dateFormat.parse(date);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}
  • 定義xml節(jié)點(diǎn)
<!--裝配自定義的類型轉(zhuǎn)換器-->
<mvc:annotation-driven conversion-service="conversionService"/>

<!--自定義Date類型轉(zhuǎn)換器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <bean class="utils.StringToDateConverter" p:datePattern="yyyy-MM-dd"/>
    </property>
</bean>

注冊(cè)方法

  • 1.InitBinder(不推薦使用java.beans.PropertyEditor)

剛才上面的注冊(cè)方式是通過xml配置進(jìn)行的操作潮剪,那么我們可以不借助xml配置,使用@InitBinder添加自定義編輯轉(zhuǎn)換數(shù)據(jù)分唾。這里就用到了Java自身的PropertyEditor類抗碰。

  • 自定義方法實(shí)現(xiàn)PropertyEditor的相關(guān)類
public class DateEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date date = dateFormat.parse(text);
            setValue(date);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
  • 在控制器處進(jìn)行注冊(cè)初始化
@InitBinder
public void initBinder(WebDataBinder binder){
    binder.registerCustomEditor(Date.class,new DateEditor());
}

  • 2.WebBindingInitializer(不推薦使用java.beans.PropertyEditor)

如果這個(gè)數(shù)據(jù)轉(zhuǎn)換需要在系統(tǒng)多處使用,那么這個(gè)自定義轉(zhuǎn)換器方法需要進(jìn)行全局注冊(cè)绽乔。使用WebBindingInitializer進(jìn)行全局范圍的注冊(cè)弧蝇。

3.數(shù)據(jù)格式化(Fommatter<T>)

Spring使用Converter轉(zhuǎn)換器進(jìn)行源類型對(duì)象到目標(biāo)類型對(duì)象的轉(zhuǎn)換。但是Converter并不能夠進(jìn)行輸入或輸出的信息的格式化迄汛。

Spring 3.0引入格式化轉(zhuǎn)換框架捍壤,org.springframework.format,F(xiàn)ormatte<T>為最重要的接口鞍爱。

Converter接口是完成任意Object與Object之間的轉(zhuǎn)換鹃觉,而Formatter是完成任意Object與String的轉(zhuǎn)換。所以Formatter接口更適合在Web層睹逃,處理用戶表單提交的數(shù)據(jù)格式化盗扇。

Formatter<T>接口,完成T類型對(duì)象的格式化和解析功能沉填。

public interface Formatter<T> extends Printer<T>, Parser<T> {

}

3.1重要接口

  • Printer<T>

格式化顯示接口

  • Parser<T>
T parse(String text, Locale locale) throws ParseException;

解析接口疗隶,參考本地信息,將一個(gè)格式化后的字符串轉(zhuǎn)換為T類型的對(duì)象翼闹。

  • Formatter<T>

Formatter<T>繼承上面兩個(gè)接口類斑鼻,具備所繼承接口的所有功能。

  • FormatterRegistrar

注冊(cè)格式化轉(zhuǎn)換器猎荠。一般很少單獨(dú)用到坚弱,我們一般用到的FormattingConversionServiceFactoryBean這個(gè)里面已經(jīng)封裝了這個(gè)接口對(duì)象。

  • AnnotationFormatterFactory<A extends Annotation>

注解驅(qū)動(dòng)的字段格式化工廠关摇,用于創(chuàng)建帶注解的對(duì)象字段的Printer和Parser荒叶。即是用于格式化和解析帶注釋的對(duì)象字段。

//注解A的應(yīng)用范圍输虱,哪些屬性類可以標(biāo)注A注解
Set<Class<?>> getFieldTypes();
//根據(jù)A注解些楣,獲取特定屬性類型Printer
Printer<?> getPrinter(A annotation, Class<?> fieldType);
//根據(jù)A注解,獲取特定屬性類型Parser
Parser<?> getParser(A annotation, Class<?> fieldType);

3.2 自定義實(shí)現(xiàn)Formatter接口(了解下實(shí)現(xiàn)方式)

  • 后臺(tái)代碼
public class DateFormatter implements Formatter<Date> {

    private String datePattern;

    private SimpleDateFormat dateFormat;

    public DateFormatter(String datePattern) {
        this.datePattern = datePattern;
        dateFormat = new SimpleDateFormat(datePattern);

    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        Date result = null;
        try {
            result = dateFormat.parse(text);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    @Override
    public String print(Date object, Locale locale) {
        return dateFormat.format(object);
    }
}
  • xml配置
<!--裝配自定義格式轉(zhuǎn)化器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定義格式化轉(zhuǎn)換器bean-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <bean class="utils.DateFormatter" c:datePattern="yyyy-MM-dd"/>
    </property>
</bean>

3.3 使用系統(tǒng)內(nèi)置的轉(zhuǎn)換器(推薦這種方式)

當(dāng)然Spring本身就提供了很多內(nèi)置的轉(zhuǎn)換器,不需要我們?cè)賹懚嘤嗟拇a愁茁。比如上面我們自定義的時(shí)間格式轉(zhuǎn)化器蚕钦。Spring內(nèi)置的org.springframework.format.datetime包中就有對(duì)應(yīng)的DateFormatter實(shí)現(xiàn)類。

我們只需要定義xml就可以了埋市,如下冠桃。

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <bean class="org.springframework.format.datetime.DateFormatter" c:pattern="yyyy-MM-dd"/>
    </property>
</bean>

3.4 自定義使用FormatterRegister注冊(cè)Formatter(只需要了解,無須掌握道宅,太麻煩)

前面我們直接在xml里面注冊(cè)Formatter實(shí)現(xiàn)類,那么我們還可以直接在xml里面注冊(cè)Registrar胸蛛,來替代直接注冊(cè)Formatter污茵。

  • 后臺(tái)代碼

自定義出FormatterRegistrar類

public class CustomerFormatterRegistrar implements FormatterRegistrar {

    private DateFormatter dateFormatter;

    public CustomerFormatterRegistrar(DateFormatter dateFormatter) {
        this.dateFormatter = dateFormatter;
    }

    @Override
    public void registerFormatters(FormatterRegistry registry) {
        registry.addFormatter(dateFormatter);
    }
}

自定義出DateFormmater類

public class DateFormatter implements Formatter<Date> {

    private String datePattern;

    private SimpleDateFormat dateFormat;

    public DateFormatter(String datePattern) {
        this.datePattern = datePattern;
        dateFormat = new SimpleDateFormat(datePattern);

    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        Date result = null;
        try {
            result = dateFormat.parse(text);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    @Override
    public String print(Date object, Locale locale) {
        return dateFormat.format(object);
    }
}
  • xml配置
<!--裝配自定義格式轉(zhuǎn)化器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--在Spring上下文定義出自定義的時(shí)間轉(zhuǎn)化器組件-->
<bean id="dateFormatter" class="utils.DateFormatter" c:datePattern="yyyy-MM-dd"></bean>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatterRegistrars">
        <bean class="utils.CustomerFormatterRegistrar" c:dateFormatter-ref="dateFormatter"></bean>
    </property>
</bean>

3.5 使用注解的方式來進(jìn)行格式化工作(AnnotationFormatterFactory<A extends Annotation>)

前面的例子無論是自定義實(shí)現(xiàn)數(shù)據(jù)格式工作還是使用系統(tǒng)內(nèi)置的類,都需要通過進(jìn)行繁瑣的xml配置≡嵯睿現(xiàn)在我們直接使用注解_Annotation的方式進(jìn)行實(shí)現(xiàn)格式化工作泞当。

org.springframework.format.annotation 定義了兩個(gè)格式化的注解類型

  • 1.DateTimeFormat

@DateTimeFormat 注解可以對(duì)java.util.Date、java.util.Calendar等時(shí)間類型的屬性進(jìn)行標(biāo)注民珍。該類支持下面三種互斥屬性

==互斥屬性指的是只能擁有其一襟士,不然同時(shí)具備。==

//自定義時(shí)間格式
private String pattern;

private String stylePattern;

private ISO iso;

stylePattern

/**
 * Set the two character to use to format date values. The first character used for
 * the date style, the second is for the time style. Supported characters are
 * <ul>
 * <li>'S' = Small</li>短日期/時(shí)間的樣式
 * <li>'M' = Medium</li>中日期/時(shí)間的樣式
 * <li>'L' = Long</li>長(zhǎng)日期/時(shí)間的樣式
 * <li>'F' = Full</li>完整日期/時(shí)間的樣式
 * <li>'-' = Omitted</li>忽略日期/時(shí)間的樣式
 * <ul>
 * This method mimics the styles supported by Joda-Time.
 * @param stylePattern two characters from the set {"S", "M", "L", "F", "-"}
 * @since 3.2
 */
public void setStylePattern(String stylePattern) {
    this.stylePattern = stylePattern;
}

IOS幾種可選值

formats.put(ISO.DATE, "yyyy-MM-dd");
formats.put(ISO.TIME, "HH:mm:ss.SSSZ");
formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
  • 2.NumberFormat

NumberFormat可對(duì)類似數(shù)字類型的屬性進(jìn)行標(biāo)注嚷量,它擁有兩個(gè)互斥的屬性陋桂。

String pattern()

Style style()

style可選枚舉值

enum Style {

    /**
     * The default format for the annotated type: typically 'number' but possibly
     * 'currency' for a money type (e.g. {@code javax.money.MonetaryAmount)}.
     * @since 4.2
     */
    DEFAULT,

    /**
     * The general-purpose number format for the current locale.
     */
    NUMBER,

    /**
     * The percent format for the current locale.
     */
    PERCENT,

    /**
     * The currency format for the current locale.
     */
    CURRENCY
}

代碼演示

  • 后臺(tái)代碼新建需要進(jìn)行數(shù)據(jù)轉(zhuǎn)換及格式化的類。省略了get蝶溶、set方法
public class User implements Serializable {
    private String loginname;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;

    /**
     * 薪水嗜历,以財(cái)務(wù)格式接收
     */
    @NumberFormat(style = NumberFormat.Style.NUMBER, pattern = "#,###")
    private double salary;

    /**
     * 業(yè)績(jī)完成比例
     */
    @NumberFormat(style = NumberFormat.Style.PERCENT)
    private double performance;

    /**
     * 薪水的貨幣類型展示
     */
    @NumberFormat(style = NumberFormat.Style.CURRENCY)
    private double salaryDisplay;
}
  • xml配置
<mvc:annotation-driven/>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抖所,隨后出現(xiàn)的幾起案子梨州,更是在濱河造成了極大的恐慌,老刑警劉巖田轧,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暴匠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡傻粘,警方通過查閱死者的電腦和手機(jī)每窖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抹腿,“玉大人岛请,你說我怎么就攤上這事【ǎ” “怎么了崇败?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我后室,道長(zhǎng)缩膝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任岸霹,我火速辦了婚禮疾层,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贡避。我一直安慰自己痛黎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布刮吧。 她就那樣靜靜地躺著湖饱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杀捻。 梳的紋絲不亂的頭發(fā)上井厌,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音致讥,去河邊找鬼仅仆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛垢袱,可吹牛的內(nèi)容都是我干的墓拜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼惶桐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼撮弧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姚糊,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤贿衍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后救恨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸辈,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年肠槽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了擎淤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秸仙,死狀恐怖嘴拢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寂纪,我是刑警寧澤席吴,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布赌结,位于F島的核電站,受9級(jí)特大地震影響孝冒,放射性物質(zhì)發(fā)生泄漏柬姚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一庄涡、第九天 我趴在偏房一處隱蔽的房頂上張望量承。 院中可真熱鬧,春花似錦穴店、人聲如沸撕捍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卦洽。三九已至,卻和暖如春斜棚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背该窗。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工弟蚀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酗失。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓义钉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親规肴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捶闸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容