本篇文章给大家谈谈springMVC的Converter转换器和Formatter,以及springmvc类型转换器的知识点,同时本文还将给你拓展11.SpringMVC之HttpMessageCon
本篇文章给大家谈谈springMVC 的 Converter 转换器 和 Formatter,以及springmvc类型转换器的知识点,同时本文还将给你拓展11.SpringMVC 之 HttpMessageConverter、12.Spring MVC类型转换器(Converter)、3. 盘点 springmvc 的常用接口之 HttpMessageConverter、java MVC 自定义类型转换器 (Formatter、AnnotationFormatterFactory)等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:- springMVC 的 Converter 转换器 和 Formatter(springmvc类型转换器)
- 11.SpringMVC 之 HttpMessageConverter
- 12.Spring MVC类型转换器(Converter)
- 3. 盘点 springmvc 的常用接口之 HttpMessageConverter
- java MVC 自定义类型转换器 (Formatter、AnnotationFormatterFactory)
springMVC 的 Converter 转换器 和 Formatter(springmvc类型转换器)
Converter 转换器
spring 的 Converter 是可以将一种类型转换成另一种类型的一个对象,自定义 Converter 需要实现 Converter 接口
日期转换器
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/**
* 字符串日期格式转换器
*
*/
public class CustomGlobalStrToDataConverter implements Converter<String, Date>{
private String datePattern;//日期格式
//创建对象,并传入构造参数
public CustomGlobalStrToDataConverter(String datePattern){
this.datePattern = datePattern;
}
@Override
public Date convert(String source) {
try {
Date date = new SimpleDateFormat(datePattern).parse(source);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
使用 SpringMVC 自定义的 Converter, 需要在 SpringMVC 的配置文件中加入如下配置
<!-- 注解驱动:替我们显示的配置了最新版的注解的处理器映射器和处理器适配器 -->
<mvc:annotation-driven conversion-service="myConversionService"/>
<!-- 配置自定义转换器 注意: 一定要将自定义的转换器配置到注解驱动上,id不能使用conversionService,不然会出现ArrayList<?>的异常-->
<bean id="myConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 指定自定义转换器的全路径名称 -->
<bean class="com.guorong.controller.converter.CustomGlobalStrToDataConverter">
<constructor-arg name="datePattern" type="java.lang.String" value="yyyy-MM-dd hh:mm:ss"/>
</bean>
</set>
</property>
</bean>
Formatter
Formatter 和 Converter 一样,是将一种类型转换成另一种类型,但是,Formatter 的源类型必须是一个 String, 目标类型是 java 类型.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.format.Formatter;
public class DateFormatter implements Formatter<Date>{
private String datePattern;//日期格式字符串
private SimpleDateFormat dateFormat;//日期格式类
public DateFormatter(String datePattern) {
this.datePattern = datePattern;
dateFormat = new SimpleDateFormat(datePattern);
}
//将Date格式化为指定日期字符串,返回目标对象的字符串表示法
@Override
public String print(Date date, Locale locale) {
return dateFormat.format(date);
}
//将字符串日期解析成Date对象
@Override
public Date parse(String source, Locale locale) throws ParseException {
return dateFormat.parse(source);
}
}
springMVC 配置文件
<!-- 注解驱动: 替我们显示的配置了最新版的注解的处理器映射器和处理器适配器 -->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="com.guorong.controller.converter.DateFormatter">
<constructor-arg name="datePattern" type="java.lang.String" value="yyyy-MM-dd"/>
</bean>
</set>
</property>
</bean>
选择 Converter, 还是 Formatter
Converter 是一般工具,可以将一种类型转换成另一种类型,例如,将 String 转换成 Date, 或者 Long 转换成 Date, Conveter 既可以用在 web 层,也可以用在其他层中, Formatter 只能讲 String 转换层另一种 java 类型,例如,将 String 转换成 Date, 但它不可能将 Long 转换成 Date 类型,因此 Formatter 适用于 web 层,因此,SpringMVC 应用程序中,选择 Formatter 比选择 Converter 更合适.
11.SpringMVC 之 HttpMessageConverter
HttpMessageConverter 简介
HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。但是,使用 Spring,controller 类中的方法返回纯 String 类型或其他 Java 内建对象。如何将对象转换成字节流进行传输?
在报文到达 SpringMVC 和从 SpringMVC 出去,都存在一个字节流到 java 对象的转换问题。在 SpringMVC 中,它是由 HttpMessageConverter 来处理的。
当请求报文来到 java 中,它会被封装成为一个 ServletInputStream 的输入流,供我们读取报文。响应报文则是通过一个 ServletOutputStream 的输出流,来输出响应报文。
我们可以用下图来加深理解。
HttpMessageConverter 接口
在 Spring 中,内置了大量的 HttpMessageConverter。通过请求头信息中的 MIME 类型,选择相应的 HttpMessageConverter。
// 数据转换器 -> 将数据转换成 requests 或 response 中的数据
public interface HttpMessageConverter<T> {
// 指定的 class 是否支持读取(MediaType 指数据的格式)
boolean canRead(Class<?> clazz, MediaType mediaType);
// 传入 class 与 MediaType -> 看 HttpMessageConverter 是否支持写数据到数据流中
boolean canWrite(Class<?> clazz, MediaType mediaType);
// 返回 HttpMessageConverter 支持的 MediaType
List<MediaType> getSupportedMediaTypes();
// 从 HttpInputMessage 中读取数据流, 并转化成 T 这站类型
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
// 将 T 里面的数据信息写入到 HttpOutputMessage 的数据流中
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
HttpMessageConverter 接口的定义中出现了成对的 canRead (),read () 和 canWrite (),write () 方法。MediaType 是对请求的 Media Type 属性的封装。
read 方法中有一个 HttpInputMessage,我们查看它的源码如下。
public interface HttpInputMessageextends HttpMessage {
InputStreamgetBody() throws IOException;
}
HttpInputMessage 提供的接口就是将 body 中的数据转为输入流。
write 方法中有一个 HttpOutputMessage,我们查看它的源码如下。
public interface HttpOutputMessageextends HttpMessage {
OutputStreamgetBody() throws IOException;
}
HttpOutputMessage 提供的接口就是将 body 中的数据转为输出流。
它们拥有相同的父接口 HttpMessage。
public interface HttpMessage {
HttpHeadersgetHeaders();
}
HttpMessage 提供的方法是读取头部中的信息。
Spring 中内置的一部分 HttpMessageConverter
1. FormHttpMessageConverter
支持 MultiValueMap 类型, 并且 MediaType 类型是 "multipart/form-data", 从 InputStream 里面读取数据, 并通过&符号分割, 最后转换成 MultiValueMap, 或 将 MultiValueMap转换成 & 符号连接的字符串, 最后转换成字节流, 输出到远端
2. BufferedImageHttpMessageConverter
支持 BufferedImgae 的 HttpMessageConverter, 通过 ImageReader 将 HttpBody 里面的数据转换成 BufferedImage, 或ImageWriter 将ImageReader 转换成字节流输出到 OutputMessage
3. StringHttpMessageConverter
支持数据是 String 类型的, 从 InputMessage 中读取指定格式的 str, 或 将数据编码成指定的格式输出到 OutputMessage
4. SourceHttpMessageConverter
支持 DOMSource, SAXSource, StAXSource, StreamSource, Source 类型的消息转换器, 在读取的时候, 从 HttpBody 里面读取对应的数据流转换成对应对应, 输出时通过 TransformerFactory 转换成指定格式输出
5. ResourceHttpMessageConverter
支持数据类型是 Resource 的数据, 从 HttpBody 中读取数据流转换成 InputStreamResource|ByteArrayResource, 或从 Resource 中读取数据流, 输出到远端
6. ProtobufHttpMessageConverter
支持数据类型是 com.google.protobuf.Message, 通过 com.google.protobuf.Message.Builder 将 HttpBody 中的数据流转换成指定格式的 Message, 通过 ProtobufFormatter 将 com.google.protobuf.Message 转换成字节流输出到远端
7. ObjectToStringHttpMessageConverter
支持 MediaType是 text/plain 类型, 从 InputMessage 读取数据转换成字符串, 通过 ConversionService 将字符串转换成自定类型的 Object; 或将 Obj 转换成 String, 最后 将 String 转换成数据流
8. ByteArrayHttpMessageConverter
支持格式是 byte 类型, 从 InputMessage 中读取指定长度的字节流, 或将 OutputMessage 转换成字节流
9. AbstractXmlHttpMessageConverter及其子类
支持从 xml 与 Object 之间进行数据转换的 HttpMessageConverter
10. AbstractGenericHttpMessageConverter
支持从 Json 与 Object 之间进行数据转换的 HttpMessageConverter (PS: 主要通过 JackSon 或 Gson)
11. GsonHttpMessageConverter
支持 application/*++json 格式的数据, 并通过 Gson, 将字符串转换成对应的数据
12. MappingJackson2XmlHttpMessageConverter
持 application/*++json/*+xml 格式的数据, 并通过 JackSon, 将字符串转换成对应的数据
HttpMessageConverter 在整个 SpringMVC 中起着根据 MediaType 类型将 HttpServletRequest 中的数据转换成 指定对象的转换器,或将对象转换成指定格式的数据 (PS: byte/String/xml/json 等);
自定义 HttpMessageConverter
1. 通过继承 AbstractHttpMessageConverter 自定义 HttpMessageConverter
配置自定义 HttpMessageConverter
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--spring可以自动去扫描base-pack下面的包或者子包下面的java文件,
如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean-->
<context:component-scan base-package="com.wen.controller"/>
<!--设置默认的Servlet来响应静态文件-->
<mvc:default-servlet-handler/>
<!--设置配置方案,会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean-->
<mvc:annotation-driven>
<!--设置不使用默认的消息转换器-->
<mvc:message-converters register-defaults="false">
<!--配置Spring的转换器-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
<!--配置fastjson中实现HttpMessageConverter接口的转换器-->
<!--FastJsonHttpMessageConverter是fastjson中实现了HttpMessageConverter接口的类-->
<bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!--加入支持的媒体类型:返回contentType-->
<property name="supportedMediaTypes">
<list>
<!--这里顺序一定不能写反,不然IE会出现下载提示-->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
12.Spring MVC类型转换器(Converter)
我想您一定十分诧异,为什么仅仅通过一些注解,控制器方法就能够得到各种类型的参数,其实这都要归功于 Spring MVC 的类型转换机制。
Spring 提供了一种 Converter(类型转换器)的类型转换工具。在 Spring MVC 中,它的作用是在控制器方法对请求进行处理前,先获取到请求发送过来的参数,并将其转换为控制器方法指定的数据类型,然后再将转换后的参数值传递给控制器方法的形参,这样后台的控制器方法就可以正确地获取请求中携带的参数了。
内置的类型转换器
Spring MVC 框架默认提供了许多内置的类型转换器,主要包括以下几种类型。
1)标量转换器
名称 | 作用 |
---|---|
StringToBooleanConverter | String 到 boolean 类型转换 |
ObjectToStringConverter | Object 到 String 转换,调用 toString 方法转换 |
StringToNumberConverterFactory | String 到数字转换(例如 Integer、Long 等) |
NumberToNumberConverterFactory | 数字子类型(基本类型)到数字类型(包装类型)转换 |
StringtocharacterConverter | String 到 Character 转换,取字符串中的第一个字符 |
NumbertocharacterConverter | 数字子类型到 Character 转换 |
CharacterToNumberFactory | Character 到数字子类型转换 |
StringToEnumConverterFactory | String 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型 |
EnumToStringConverter | 枚举类型到 String 转换,返回枚举对象的 name 值 |
StringTolocaleconverter | String 到 java.util.Locale 转换 |
PropertiesToStringConverter | java.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码 |
StringToPropertiesConverter | String 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码 |
2)集合、数组相关转换器
名称 | 作用 |
---|---|
ArrayToCollectionConverter | 任意数组到任意集合(List、Set)转换 |
CollectionToArrayConverter | 任意集合到任意数组转换 |
ArrayToArrayConverter | 任意数组到任意数组转换 |
CollectionToCollectionConverter | 集合之间的类型转换 |
MapToMapConverter | Map之间的类型转换 |
ArrayToStringConverter | 任意数组到 String 转换 |
StringToArrayConverter | 字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim) |
ArrayToObjectConverter | 任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换 |
ObjectToArrayConverter | Object 到单元素数组转换 |
CollectionToStringConverter | 任意集合(List、Set)到 String 转换 |
StringToCollectionConverter | String 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim) |
CollectionToObjectConverter | 任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换 |
ObjectToCollectionConverter | Object 到单元素集合的类型转换 |
Spring MVC 对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。因此,通常情况下 Spring MVC 提供的这些类型转换器可以满足开发人员大多数的类型转换需求的。
注意:在使用内置类型转换器时,请求参数输入值需要与接收参数类型相兼容,否则会报 400 错误。
自定义类型转换器
一般情况下,Spring MVC 内置的类型转换器就可以满足我们日常的开发需求,但对于一些较为复杂类型的转换,例如 String 转换 Date 类型,以及开发人员自定义格式的数据的转换等,就需要我们根据自身的需求开发自定义类型转换器来转换了。
1. 创建自定义类型转换器类
Spring 在 org.springframework.core.convert.converter 包中定义了 3 种类型的转换器接口,如下表。
接口 | 说明 |
---|---|
Converter<S,T> | 该接口使用了泛型,第一个类型 S 表示原类型,第二个类型 T 表示目标类型,里面定义了一个 convert() 方法,能够将原类型对象作为参数传入,进行转换之后返回目标类型对象。 |
ConverterFactory | 如果我们希望将一种类型的对象转换为另一种类型及其子类对象,例如将 String 转换为 Number 以及 Number 的子类 Integer、Double 等类型的对象,那么就需要一系列的 Converter,如 StringToInteger、StringTodouble 等。ConverterFactory<S,R> 接口的作用就是将这些相同系列的多个 Converter 封装在一起。 |
GenericConverter | 该接口会根据源类对象及目标类对象的上下文信息进行类型转换。 |
如果我们想要自定义类型转换器,第一步就是要创建一个自定义类型转换器类,并实现以上 3 个接口的中任意一种转换器接口即可。
例如,下面的代码就是一个实现了 Converter<S,T> 接口的自定义类型转换器, 该类型转换器可以将 String 类型转换为 Date 类型,代码如下。
- package net.biancheng.c.converter;
- import org.springframework.core.convert.converter.Converter;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- /**
- * 自定义日期转换器
- */
- public class MyDateConverter implements Converter<String, Date> {
- private String datePatten = "yyyy-MM-dd";
- @Override
- public Date convert(String source) {
- System.out.println("前端页面传递过来的时间为:" + source);
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePatten);
- try {
- return simpleDateFormat.parse(source);
- } catch (ParseException e) {
- throw new IllegalArgumentException("无效的日期格式,请使用正确的日期格式" + datePatten);
- }
- }
- }
2. 配置自定义类型转换器
在创建完自定义类型转换器后,我们还需要在 Spring MVC 的核心配置文件中对它进行配置,这个自定义类型装换器才会生效,示例配置如下。
- <!--显式地装配自定义类型转换器-->
- <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
- <!--自定义类型转换器配置-->
- <bean id="conversionService"https://www.jb51.cc/tag/factorybean/" target="_blank">factorybean">
- <property name="converters">
- <set>
- <bean></bean>
- </set>
- </property>
- </bean>
在上面的配置中,我们共完成以下 2 步配置:
- 通过 Spring MVC 的配置文件,在 Spring 容器中声明一个 org.springframework.context.support.ConversionServicefactorybean 的 Bean(例如示例配置中的名为 conversionService 的 Bean),然后通过其 converters 属性将自定义的类型转换器注册到其中。
- 通过 <mvc:annotation-driven> 标签的 conversion-service 属性,将我们声明的 ConversionServicefactorybean 类型的 Bean 显式地将其默认注册的 ConversionService(FormattingConversionServicefactorybean 类型)覆盖掉。
通常情况下,我们都需要在 Spring MVC 的核心配置文件中配置一个 <mvc:annotation-driven>,它是 Spring MVC 提供的注解驱动标签,使用该标签能够简化 Spring MVC 的相关配置。
<mvc:annotation-driven> 会自动向 Spring MVC 中注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 以及 ExceptionHandlerExceptionResolver 三个 Bean。其中,RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 都是控制器方法对请求进行分发的必须组件,而 ExceptionHandlerExceptionResolver 则是 Spring MVC 的异常处理组件。
除此之外,<mvc:annotation-driven> 标签还默认注册了一个 org.springframework.format.support.FormattingConversionServicefactorybean 类型的 Bean:ConversionService。通过它,可以满足我们大多数的类型转换需求。
示例
我们以 Converter<S,T> 为例,来演示下如何通过实现该接口来自定义类型转换器。
1 新建一个名为 springmvc-converter-demo 的 Web 项目,并将与 Sprng MVC 相关的依赖引入到该工程中,其 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_4_0.xsd"
- version="4.0">
- <!--请求和响应的字符串过滤器-->
- <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>forceResponseEncoding</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>CharacterEncodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!--来处理 PUT 和 DELETE 请求的过滤器-->
- <filter>
- <filter-name>HiddenHttpMethodFilter</filter-name>
- <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>HiddenHttpMethodFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
- <servlet>
- <servlet-name>dispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.dispatcherServlet</servlet-class>
- <!--配置 dispatcherServlet 的一个初始化参数:spring mvc 配置文件按的位置和名称-->
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:springMVC.xml</param-value>
- </init-param>
- <!--作为框架的核心组件,在启动过程中有大量的初始化操作要做
- 而这些操作放在第一次请求时才执行会严重影响访问速度
- 因此需要通过此标签将启动控制dispatcherServlet的初始化时间提前到服务器启动时-->
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispatcherServlet</servlet-name>
- <!--设置springMVC的核心控制器所能处理的请求的请求路径
- /所匹配的请求可以是/login或.html或.js或.css方式的请求路径
- 但是/不能匹配.jsp请求路径的请求-->
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </web-app>
2. 在 src(类路径下)创建一个 Spring MVC 的核心配置文件 springMVC.xml,配置内容如下。
- <?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:context="http://www.springframework.org/schema/context"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context
- https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
- <!--开启组件扫描-->
- <context:component-scan base-package="net.biancheng.c"></context:component-scan>
- <!-- 配置 Thymeleaf 视图解析器 -->
- <bean id="viewResolver"
- https://www.jb51.cc/tag/Spring5/" target="_blank">Spring5.view.ThymeleafViewResolver">
- <property name="order" value="1"/>
- <property name="characterEncoding" value="UTF-8"/>
- <property name="templateEngine">
- <beanhttps://www.jb51.cc/tag/Spring5/" target="_blank">Spring5.SpringTemplateEngine">
- <property name="templateResolver">
- <beanhttps://www.jb51.cc/tag/Spring5/" target="_blank">Spring5.templateresolver.SpringResourceTemplateResolver">
- <!-- 视图前缀 -->
- <property name="prefix" value="/WEB-INF/templates/"/>
- <!-- 视图后缀 -->
- <property name="suffix" value=".html"/>
- <property name="templateMode" value="HTML5"/>
- <property name="characterEncoding" value="UTF-8"/>
- </bean>
- </property>
- </bean>
- </property>
- </bean>
- <!-- view-name:设置请求地址所对应的视图名称-->
- <mvc:view-controller path="/" view-name="user"></mvc:view-controller>
- <!--当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:-->
- <mvc:annotation-driven></mvc:annotation-driven>
- </beans>
3. 在 net.biancheng.c.bean 包下,创建一个名为 User 的实体类,代码如下。
- package net.biancheng.c.bean;
- import java.util.Date;
- /**
- * 实体类 User
- */
- public class User {
- private String userName;
- private Date birth;
- private Double height;
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public Date getBirth() {
- return birth;
- }
- public void setBirth(Date birth) {
- this.birth = birth;
- }
- public Double getHeight() {
- return height;
- }
- public void setHeight(Double height) {
- this.height = height;
- }
- @Override
- public String toString() {
- return "User{" +
- "userName='" + userName + '\'' +
- ", height=" + height +
- ", birth=" + birth +
- '}';
- }
- }
注:从 User 的代码中,我们可以看出 User 共包含 userName(姓名)、birth(生日)和 height(身高)三个属性,其中 userName 为 String 类型,birth 为 Date 类型,height 为 Double 类型。
4. 在 net.biancheng.c.controller 包下,创建一个名为 UserController 的 Controller 类,代码如下。
- package net.biancheng.c.controller;
- import net.biancheng.c.bean.User;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- /**
- * @author C语言中文网
- */
- @Controller
- public class UserController {
- @RequestMapping(value = "/user", method = RequestMethod.POST)
- public String login(User user, Model model) {
- System.out.println(user);
- model.addAttribute("user", user);
- return "success";
- }
- }
5. 在 webapp/WEB-INF/ 下新建一个 templates 目录,并在该目录中创建一个 user.html,代码如下。
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <Meta charset="UTF-8">
- <title>C语言中文网</title>
- </head>
- <body>
- <form th:action="@{/user}" method="post">
- <table>
- <tr>
- <td>姓名:</td>
- <td><input type="text" name="userName" required><br></td>
- </tr>
- <tr>
- <td>生日:</td>
- <td><input type="date" name="birth" required><br></td>
- </tr>
- <tr>
- <td>身高:</td>
- <td><input type="text" name="height" required><br></td>
- </tr>
- <tr>
- <td colspan="2" align="center">
- <input type="submit" value="提交">
- <input type="reset" value="重置">
- </td>
- </tr>
- </table>
- </form>
- </body>
- </html>
6. 在 webapp/WEB-INF/templates 目录下创建一个 success.html,代码如下。
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <Meta charset="UTF-8">
- <title>C语言中文网</title>
- </head>
- <body>
- <table>
- <tr>
- <td>用户名:</td>
- <td th:text="${user.getUserName()}"></td>
- </tr>
- <tr>
- <td>生日:</td>
- <td th:text="${#dates.format(user.getBirth(),'yyyy-MM-dd')}"></td>
- </tr>
- <tr>
- <td>身高:</td>
- <td th:text="${user.getHeight()}"></td>
- </tr>
- </table>
- </body>
- </html>
7. 将 springmvc-converter-demo 部署到 Tomcat 服务器中,启动 Tomcat,使用浏览器“http://localhost:8080/springmvc-converter-demo/”,结果如下图。
图1:用户登记页面
8. 点击“提交”按钮,结果出现错误,如下图。
图2:错误页
9. 查看控制台,错误日志如下图。
14:09:48.585 [http-nio-8080-exec-2] WARN org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<LF>Field error in object 'user' on field 'birth': rejected value [2000-06-21]; codes [typeMismatch.user.birth,typeMismatch.birth,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.birth,birth]; arguments []; default message [birth]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birth'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2000-06-21'; nested exception is java.lang.IllegalArgumentException]]
14:09:48.587 [http-nio-8080-exec-2] DEBUG org.springframework.web.servlet.dispatcherServlet - Completed 400 BAD_REQUEST
注:从控制台输出的日志可以看出,出现该错误的原因是字符串“2000-06-21”从 String 类型转换到 Date 类型时失败。
10. 新建 net.biancheng.c.converter 包,并在该包中创建名一个名为 MyDateConverter 的自定义类型转换器类,代码如下。
- package net.biancheng.c.converter;
- import org.springframework.core.convert.converter.Converter;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- /**
- * 自定义日期转换器
- */
- public class MyDateConverter implements Converter<String, Date> {
- private String datePatten = "yyyy-MM-dd";
- @Override
- public Date convert(String source) {
- System.out.println("前端页面传递过来的时间为:" + source);
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePatten);
- try {
- return simpleDateFormat.parse(source);
- } catch (ParseException e) {
- throw new IllegalArgumentException("无效的日期格式,请使用正确的日期格式" + datePatten);
- }
- }
- }
11. 修改 springMVC.xml 的配置,将我们自定义的类型转换器注册到 Spring 容器中。
- <!--显式地装配自定义类型转换器-->
- <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
- <!--自定义类型转换器配置-->
- <bean id="conversionService"https://www.jb51.cc/tag/factorybean/" target="_blank">factorybean">
- <property name="converters">
- <set>
- <bean></bean>
- </set>
- </property>
- </bean>
12. 重启 Tomcat 服务器,重新执行第 7-8 步,结果如下图。
图3:成功页
3. 盘点 springmvc 的常用接口之 HttpMessageConverter
3. 盘点 springmvc 的常用接口之 HttpMessageConverter###
前言
举例:
POST http://localhost:8080/demo3
传入富文本数据流:Bill Gates
在 controller 中获得 Person 对象并响应 Person 内容:Bill Gates
原始写法:
@RequestMapping(method = RequestMethod.POST)
public void postPerson(HttpServletRequest request, HttpServletResponse response) {
try {
String content = new String(IOUtils.toByteArray(request.getInputStream()));
String[] strs = content.split("\\s");
Person person = new Person(strs[0], strs[1]);
// TODO do something for person
log.info(person.toString());
String text = person.getFirstName() + " " + person.getLastName();
response.getWriter().write(text);
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到原始写法把实体的序列化反序列化过程都堆叠在了 controller 中,造成 controller 的混乱和不易阅读。
在 springmvc 中,我们可以使用 @RequestBody
和 @ResponseBody
两个注解,分别完成请求报文到对象和对象到响应报文的转换。底层这种灵活的消息转换机制,就是 Spring3.x 中新引入的 HttpMessageConverter 即消息转换器机制。
比如我传入的 json 或者 xml 格式的报文数据流,要在服务器进行逻辑处理必须事先转换成实体封装。而在进行数据获取请求时,实体对象又要转换成 json 或 xml 其他格式的数据流输出。类似这样的实体序列化和反序列化的工作正是由 org.springframework.http.converter.HttpMessageConverter
接口实现的。
在 springmvc 中内置了众多的 HttpMessageConverter
实现类,一般情况下无需自定义实现即可满足业务需求。在配置 <mvc:annotation-driven/>
或 spring-boot 的 @EnableWebMvc
注解时自动加载如下实现:
org.springframework.http.converter.ByteArrayHttpMessageConverter
org.springframework.http.converter.StringHttpMessageConverter
org.springframework.http.converter.ResourceHttpMessageConverter
org.springframework.http.converter.xml.SourceHttpMessageConverter
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
springmvc 在使用 @RequestBody
接收数据时会依据请求头 Content-Type 值依次调用上述默认配置的 converter 的 canRead
方法判断该使用哪个转换器。在使用 @ResponseBody
响应数据时会依据请求头 Accept 值依次调用 converter 的 canWrite
方法。所以假如自定义的 HttpMessageConverter
有相同的 MediaType 时需要注册在默认转换器之前。
接口说明
public interface HttpMessageConverter<T> {
//用于检验是否可以读入数据执行read方法
boolean canRead(Class<?> clazz, MediaType mediaType);
//用于检验是否可以写入数据执行write方法
boolean canWrite(Class<?> clazz, MediaType mediaType);
//返回可以支持该消息转换器的Media类型
List<MediaType> getSupportedMediaTypes();
//读入操作,也就是反序列化操作
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//写出操作,也就是序列化操作
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
泛型 T 为实体类型。
示例 1
改写原始写法
package com.demo.mvc.component;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.demo.domain.Person;
public class PersonHttpMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (clazz == Person.class) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
}
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if (clazz == Person.class) {
if (mediaType == null || MediaType.ALL.equals(mediaType)) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.isCompatibleWith(mediaType)) {
return true;
}
}
}
return false;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
MediaType mediaType = new MediaType("text", "person", Charset.forName("UTF-8"));
return Collections.singletonList(mediaType);
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
String[] strs = content.split("\\s");
return new Person(strs[0], strs[1]);
}
@Override
public void write(Person person, MediaType mediaType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String content = person.getFirstName() + " " + person.getLastName();
IOUtils.write(content, outputMessage.getBody());
}
}
注册该消息转换器
spring-boot
package com.demo;
import java.util.List;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import com.demo.mvc.component.PersonHttpMessageConverter;
@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PersonHttpMessageConverter());
}
}
或 xml 配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.demo.mvc.component.PersonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
controller
package com.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.domain.Person;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping("demo3")
public class HttpMessageConverterDemoController {
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public Person postPerson(@RequestBody Person person) {
log.info(person.toString());
return person;
}
}
示例 2
直接实现 HttpMessageConverter
接口比较复杂,需要自行处理判断 MediaType 的逻辑。通常自定义时只需要继承自 org.springframework.http.converter.AbstractHttpMessageConverter
抽象类即可,该类已经帮助我们完成了判断 MediaType 的逻辑。
package com.demo.mvc.component;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.demo.domain.Person;
public class PersonHttpMessageConverterExtendsAbstract extends AbstractHttpMessageConverter<Person> {
public PersonHttpMessageConverterExtendsAbstract() {
super(new MediaType("text", "person", Charset.forName("UTF-8")));
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz == Person.class;
}
@Override
protected Person readInternal(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String content = new String(IOUtils.toByteArray(inputMessage.getBody()));
String[] strs = content.split("\\s");
return new Person(strs[0], strs[1]);
}
@Override
protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String content = person.getFirstName() + " " + person.getLastName();
IOUtils.write(content, outputMessage.getBody());
}
}
该抽象类提供了 supports
方法只需我们验证实体类。
readInternal
方法等同于接口的 read
,参看 AbstractHttpMessageConverter
源码发现 read
直接调用了 readInternal
,而 writeInternal
也差不多等同于 write
,只是当参数 HttpOutputMessage
为 StreamingHttpOutputMessage
时另行处理了。
AbstractHttpMessageConverter
源码:
/**
* This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}.
* Future implementations might add some default behavior, however.
*/
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException {
return readInternal(clazz, inputMessage);
}
/**
* This implementation sets the default headers by calling {@link #addDefaultHeaders},
* and then calls {@link #writeInternal}.
*/
@Override
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage =
(StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream) throws IOException {
writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
});
}
});
}
else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
友情链接:
盘点 springmvc 的常用接口目录
java MVC 自定义类型转换器 (Formatter、AnnotationFormatterFactory)
下面一个事例,是将传入的一个身份证号,转换成一个对象(提取身份证号的地址、出身日期、性别等)
实体类 Person 有三个字段如下:
String province; //地址
Date birthday; //出生日期
String sexual; //性别
Getter... Setter...
验证器实现 PersonFormatter:
实现 Formatter 接口,
public class PersonFormatter implements Formatter<Person> {
@Override
public Person parse(String text, Locale locale) throws ParseException {
//text参数是传进来的字符串,需要对字符串(身份证号)进行解析,并且保存到Person对象返回
return new Person();
}
@Override
public String print(Person object, Locale locale) {
return null;
}
}
自定义 PersonFormatterAnnotation 类,实现 AnnotationFormatterFactory 接口:
public class PersonFormatterAnnotation implements AnnotationFormatterFactory<PersonFormId> {
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> types = new HashSet<>();
types.add(Person.class);
return types;
}
@Override
public Printer<?> getPrinter(PersonFormId annotation, Class<?> fieldType) {
return null;
}
@Override
public Parser<?> getParser(PersonFormId annotation, Class<?> fieldType) {
return getFormatter(annotation);
}
private Formatter getFormatter(PersonFormId annotation) {
return new PersonFormatter();
}
}
spring xml 文件代码:
<!-- 配置类型装换器 -->
<bean name="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="com.nf.lc.formatter.PersonFormatterAnnotation"></bean>
</set>
</property>
</bean>
spring 中注册:
<!-- 启用MVC的常用注解 -->
<mvc:annotation-driven conversion-service="conversionService" />
今天关于springMVC 的 Converter 转换器 和 Formatter和springmvc类型转换器的讲解已经结束,谢谢您的阅读,如果想了解更多关于11.SpringMVC 之 HttpMessageConverter、12.Spring MVC类型转换器(Converter)、3. 盘点 springmvc 的常用接口之 HttpMessageConverter、java MVC 自定义类型转换器 (Formatter、AnnotationFormatterFactory)的相关知识,请在本站搜索。
本文标签: