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;@Controllerpublic 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;@Controllerpublic 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国际化配置,可以实现简单的语言切换,下面使用一个小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>
然后创建不同语言的资源文件:
文件内容:
message_en_US.properties文件内容如下:username=UserNamepassword=Passwordmessage_zh_CN.properties文件内容如下:username=\u7528\u6237\u540dpassword=\u5bc6\u7801message_zh_TW.properties文件内容如下:username=\u7528\u6236\u540dpassword=\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;@Controllerpublic 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>