SpringMVC数据类型转换器

我们都知道在浏览器中,访问网页都是通过url访问的。所以在web开发中,我们经常会通过url来传递数据。如果我要在url参数上传递一个日期数据,我们是无法在后端通过声明一个Date类型的参数来接收的,会报空指针错误。想要接收日期数据,就得用到SpringMVC中的数据类型转换器了。

在SpringMVC转换中有三种方式可以转换数据类型:

1.在控制中加入一个方法,在该方法上写上@InitBinder注解,并且在方法参数上声明一个WebDataBinder类型参数。这个方法会在控制器中其他方法之前调用,所以在该方法中就可以预先处理数据类型的转换。这里我们需要使用一个实现了PropertyEditor接口或者继承了PropertyEditorSupport类的自定义类型转换器来进行类型的转换。如下示例:

package org.zero01.test;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.SimpleDateFormat;
import java.util.Date;

@Controller
public class TestController {

    @RequestMapping("test.do")
    @ResponseBody
    public String test(Date date) {
        System.out.println("我被调用了-test");
        return date.toString();
    }

    @InitBinder
    public void initDate(WebDataBinder webDataBinder) {
        System.out.println("我被调用了-initDate");
        SimpleDateFormat sig = new SimpleDateFormat("yyy-MM-dd");
        // 第一个参数是DataDateFormat类型的对象,第二个参数指定是否允许为空
        CustomDateEditor cue = new CustomDateEditor(sig, true);
        // 注册自定义的日期转换格式
        webDataBinder.registerCustomEditor(Date.class, cue);
    }
}

浏览器访问http://localhost:8080/test.do?date=2018-01-01,输出结果如下:

Mon Jan 01 00:00:00 CST 2018

控制台打印结果如下:

我被调用了-initDate
我被调用了-test

以上这种转换数据类型的方式只是局部的,也就是说只能在一个控制器中使用,如果希望是全局有效的话,我们就需要在Spring配置文件中注册一个转换器了。但是在这之前我们需要先自定义一个类并实现一个Formatter接口,如下示例:

package org.zero01.test;

import org.springframework.format.Formatter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

// 接口的泛型用于指定转换成什么类型
public class MyDataFormatter implements Formatter<Date> {

    // 在parse定义转换的格式
    public Date parse(String s, Locale locale) throws ParseException {
        SimpleDateFormat sig = new SimpleDateFormat("yyyy-MM-dd");
        return sig.parse(s);
    }

    public String print(Date date, Locale locale) {
        return null;
    }
}

在Spring配置文件中注册formatters转换器:

<mvc:annotation-driven conversion-service="myDateFormatters"/>

<bean id="myDateFormatters" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="org.zero01.test.MyDataFormatter" />
        </set>
    </property>
</bean>

控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;

@Controller
public class TestController {

    @RequestMapping("test.do")
    @ResponseBody
    public String test(Date date) {
        return date.toString();
    }
}

浏览器访问http://localhost:8080/test.do?date=2018-03-28,输出结果如下:

Mon Jan 02 00:00:00 CST 2018

通过实现Formatter接口来实现类型的转换有一个缺点就是无法自定义来源类型,Formatter接口默认的来源类型都是String,而目标类型则可以自定义。如果希望能够自定义来源类型的话,就需要实现Converter接口,通过该接口我们可以指定来源类型以及转换后的目标类型。如下示例:

package org.zero01.test;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyDateConvert implements Converter<String, Date> {
    public Date convert(String s) {
        SimpleDateFormat sig = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return sig.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

同样的需要注册转换器,在Spring配置文件中注册converters转换器:

<mvc:annotation-driven conversion-service="myDateConvert"/>

<bean id="myDateConvert" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="org.zero01.test.MyDateConvert" />
        </set>
    </property>
</bean>

控制器代码和之前一样,略。浏览器访问http://localhost:8080/test.do?date=2018-03-28,输出结果如下:

Mon Jan 02 00:00:00 CST 2018

HttpMessageConverter接口

说到转换器,这里不得不再介绍一个HttpMessageConverter,这是Spring3.x中引入的接口,在底层中它作为一个消息转换器存在。SpringMVC使用消息转换器(HttpMessageConverter)实现将请求信息转换为对象、将对象转换为响应信息。

我们在使用SpringMVC时经常会使用到@RequestBody和@ResponseBody两个注解,例如上面的代码就用到了@ResponseBody注解。它们分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是HttpMessageConverter的应用,通过不同的HttpMessageConverter实现类就可以进行不同类型的转换。

默认情况下@ResponseBody注解会把返回的数据转换成普通的文本数据进行处理,而我们如果配置了JSON的转换器的话,就会按照JSON格式进行转换。这也是抽象了HttpMessageConverter接口的好处,可以在不同类型的数据间进行转换。

HttpMessageConverter消息转换器最高层次的接口抽象,描述了一个消息转换器的一般特征,我们可以来看一下HttpMessageConverter接口的源码:

public interface HttpMessageConverter<T> {  

    //判断数据类型是否可读  
    boolean canRead(Class<?> clazz, MediaType mediaType);  

    //判断数据是否可写  
    boolean canWrite(Class<?> clazz, MediaType mediaType);  

    //获取支持的数据类型  
    List<MediaType> getSupportedMediaTypes();  

    //对参数值进行读,转换为需要的类型  
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)  
            throws IOException, HttpMessageNotReadableException;  

    //将返回值发送给请求者  
    void write(T var1, MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;

}  

如果我们的控制器中有这样一个方法:

@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBody String readString(@RequestBody String string) {
    return "Read string '" + string + "'";
}

在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。

当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

我们可以用下面的图,简单描述一下这个过程:
SpringMVC数据类型转换器与国际化配置


springMVC国际化配置和使用

有些时候我们可能会有不同语言之间切换的需求,通过SpringMVC国际化配置,可以实现简单的语言切换,下面使用一个小demo演示一下如何进行国际化的配置。

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_3_1.xsd"
         version="3.1">
    <welcome-file-list>
        <welcome-file>/WEB-INF/index.jsp</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext*.xml,classpath*:applicationContext*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
</web-app>

在Spring配置文件中加入以下内容:

<!-- 默认国际化语言配置  -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="zh_CN"/>
</bean>

<!-- 区域解析类 自动解析 -->
<bean id="ahr" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="/WEB-INF/i18n/message"/>
</bean>

然后创建不同语言的资源文件:
SpringMVC数据类型转换器与国际化配置

文件内容:

message_en_US.properties文件内容如下:
username=UserName
password=Password

message_zh_CN.properties文件内容如下:
username=\u7528\u6237\u540d
password=\u5bc6\u7801

message_zh_TW.properties文件内容如下:
username=\u7528\u6236\u540d
password=\u5bc6\u78bc

编写控制器代码如下:

package org.zero01.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

import javax.servlet.http.HttpSession;
import java.util.Locale;

@Controller
public class TestController {

    @RequestMapping("test.do")
    public String testLocation(String lang, HttpSession session) {
        Locale locale = null;
        if (lang.equals("en") || lang.equals("us")) {
            locale = new Locale("en", "US");
        } else if (lang.equals("tw")) {
            locale = new Locale("zh", "TW");
        } else {
            locale = new Locale("zh", "CN");
        }

        session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale);

        return "index";
    }
}

index.jsp文件内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title>Title</title>
</head>
<style>
    .link{
        color: #888;
        text-decoration: none;
    }
    .link:hover{
        color: #e60023;
    }
</style>
<body>
    <div>
        <spring:message code="username"/> |
        <spring:message code="password"/><br>
        [<a href="test.do?lang=us" class="link"> 英文</a>]
        [<a href="test.do?lang=zh" class="link">中文</a>]
        [<a href="test.do?lang=tw" class="link">繁体</a>]
    </div>
</body>
</html>

运行效果,中文:
SpringMVC数据类型转换器与国际化配置

英文:
SpringMVC数据类型转换器与国际化配置

繁体:
SpringMVC数据类型转换器与国际化配置

这样我们就能实现不同语言之间的切换了。