天天看點

Spring MVC-06循序漸進之Converter和Formatter

  • 概述
  • converter
    • Step 1 實作Converter接口
    • Step 2 SpringMVC配置檔案中配置bean及設定conversion-service屬性
    • 小Demo
  • formatter
    • Step 1 實作Formatter接口
    • Step 2 SpringMVC配置檔案中配置bean及設定conversion-service屬性
  • 用registrar注冊formatter
    • Step 1 編寫MyFormatterRegistrar ,注冊自定義Formatter
    • Step 2 編寫自定義Formatter
    • Step 3 SpringMVC配置檔案中配置bean及設定conversion-service屬性
  • converter or formatter 小結
  • 源碼

概述

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

Spring MVC-06循序漸進之Converter和Formatter

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>           
Spring MVC-06循序漸進之Converter和Formatter

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方法與之相反,它是傳回目标對象的字元串表示法

來看下具體的用法

Spring MVC-06循序漸進之Converter和Formatter

編寫自定義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屬性

注冊配置檔案

<?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="formatters">
            <set>
                <bean class="com.artisan.converter.MyFormatter">
                    <constructor-arg type="java.lang.String" value="MM-dd-yyyy" />
                </bean>
            </set>
        </property>
    </bean>


</beans>           

為了在Spring中使用Formatter,需要注冊org.springframework.format.support.FormattingConversionServiceFactoryBean這個bean。

這個bean與converter的

org.springframework.context.support.ConversionServiceFactoryBean 不同,FormattingConversionServiceFactoryBean可以用一個formatters屬性注冊Formatter,也可以用converters屬性注冊converter

Spring MVC-06循序漸進之Converter和Formatter

測試結果同上。

用registrar注冊formatter

注冊Formatter的另外一種方式是使用Registrar.

Step 1 編寫MyFormatterRegistrar ,注冊自定義Formatter

package com.artisan.converter;


import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;

public class MyFormatterRegistrar implements FormatterRegistrar {

    private String dataPattern;

    public MyFormatterRegistrar(String dataPattern) {
        super();
        this.dataPattern = dataPattern;
    }


    @Override
    public void registerFormatters(FormatterRegistry registry) {
        registry.addFormatter(new MyFormatter(dataPattern));
        // u can registry more formatters here 
    }


}
           

Step 2 編寫自定義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 + "\"");
        }
    }

}
           

修改SpringMVC配置檔案

有了Registrar,就不用再SpringMVC配置檔案中注冊任何Formatter了,隻要在SpringMVC配置檔案中注冊Registrar就可以了

Spring MVC-06循序漸進之Converter和Formatter

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

源碼

繼續閱讀