Spring MVC-06循序渐进之Converter和Formatter

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
简介: Spring MVC-06循序渐进之Converter和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


20180210233651958.jpg


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>



20180210234040634.jpg

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方法与之相反,它是返回目标对象的字符串表示法

来看下具体的用法

20180210234742230.jpg


编写自定义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就可以了


20180210235320966.png

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

https://github.com/yangshangwei/SpringMvcTutorialArtisan

相关文章
|
2月前
|
JSON 前端开发 Java
解决Spring MVC中No converter found for return value of type异常
在Spring MVC开发中遇到`No converter found for return value of type`异常,通常是因缺少消息转换器、返回值类型不支持或转换器优先级配置错误。解决方案包括:1) 添加对应的消息转换器,如`MappingJackson2HttpMessageConverter`;2) 自定义消息转换器并实现`HttpMessageConverter`接口,设置优先级;3) 修改返回值类型为如`ResponseEntity`的合适类型。通过这些方法可确保返回值正确转换为响应内容。
128 1
|
7月前
|
XML Java 数据格式
SpringMVC中类型转换器Converter<S,T>详解
SpringMVC中类型转换器Converter<S,T>详解
158 1
|
机器学习/深度学习 XML 前端开发
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(下)
|
前端开发 Java uml
Spring官网阅读(十五)Spring中的格式化(Formatter)
在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们考虑这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支撑我们的需求了。这个时候,格式化的作用就很明显了,这篇文章我们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.5及3.6小结
256 0
Spring官网阅读(十五)Spring中的格式化(Formatter)
|
前端开发 安全 Java
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(中)
|
机器学习/深度学习 前端开发 Java
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor(上)
|
XML JSON Java
Spring Converter 体系(二)
最近封装 RPC 相关的模块,领导说数据转换可以考虑使用 Spring 原有的 Converter 体系。
224 0
|
机器学习/深度学习 安全 Java
Spring Converter 体系(一)
最近封装 RPC 相关的模块,领导说数据转换可以考虑使用 Spring 原有的 Converter 体系。
167 0
|
Java API Spring
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
|
存储 前端开发 安全
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)