在本文中,我们将给您介绍关于【原创】遨游springmvc之Converter的详细内容,此外,我们还将为您提供关于11.SpringMVC之HttpMessageConverter、12.Sprin
在本文中,我们将给您介绍关于【原创】遨游 springmvc 之 Converter的详细内容,此外,我们还将为您提供关于11.SpringMVC 之 HttpMessageConverter、12.Spring MVC类型转换器(Converter)、3. 盘点 springmvc 的常用接口之 HttpMessageConverter、5.盘点springmvc的常用接口之Converter(上篇)的知识。
本文目录一览:- 【原创】遨游 springmvc 之 Converter
- 11.SpringMVC 之 HttpMessageConverter
- 12.Spring MVC类型转换器(Converter)
- 3. 盘点 springmvc 的常用接口之 HttpMessageConverter
- 5.盘点springmvc的常用接口之Converter(上篇)
【原创】遨游 springmvc 之 Converter
1. 前言
在前一篇 WebDataBinder 中讲述了了一个 PropertyEditor, 它通过 setAsText 满足了字符串到指定类型的转换,但是它实现不了从任意类型转换到目标类型,所以在 spring3.x 之后引入了 Converter,它实现了上述需求的转换。
2. 原理
2.1 工作原理图
2.2 原理介绍
①:类型转换:内部的 ConversionService 会根据 S 源类型 / T 目标类型自动选择相应的 Converter SPI 进行类型转换,而且是强类型的,能在任意类型数据之间进行转换;
②:数据验证:支持 JSR-303 验证框架,如将 @Valid 放在需要验证的目标类型上即可;
③:格式化显示:其实就是任意目标类型 ---->String 的转换,完全可以使用 Converter SPI 完成。
Spring 为了更好的诠释格式化 / 解析功能提供了 Formatter SPI,支持根据 Locale 信息进行格式化 / 解析,而且该套 SPI 可以支持字段 / 参数级别的细粒度格式化 / 解析,流程如下:
①:类型解析(转换):String---->T 类型目标对象的解析,和 PropertyEditor 类似;
②:数据验证:支持 JSR-303 验证框架,如将 @Valid 放在需要验证的目标类型上即可;
③:格式化显示:任意目标类型 ---->String 的转换,和 PropertyEditor 类似。
3.Converter
3.1 接口介绍
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);
}
Converter 只提供了一个 convert 方法,其中 S 代表源类型,T 代表目标类型
3.2 实例
3.2.1 TelephoneConverter
public class TelephoneConverter implements Converter<String, Telephone> {
@Override
public Telephone convert(String source) {
if (source.matches("\\d{3,4}-\\d{7,8}")) {
String[] telephoneArray = source.split("-");
return new Telephone(telephoneArray[0], telephoneArray[1]);
}
return null;
}
}
3.2.2 控制器
@RequestMapping (value="/converter/1",method= RequestMethod.GET)
@ResponseBody
public Person demo1(Person p) {
return p;
}
3.2.3 配置
如果我们同时配置了 PropertyEditor 和 Converter,spring 默认先作用 PropertyEditor,再作用 Converter,但是最好不要这样子 2 个都上。
<mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
<bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.kings.template.mvc.TelephoneConverter"></bean>
</set>
</property>
</bean>
3.3 spring 提供的转换器
在 spring-core 下的 org.springframework.core.convert 包下提供了许多内置的 converter
标量转换器
转换器类 | 功能 |
---|---|
StringToBooleanConverter | String----->Boolean true: true/on/yes/1;false: false/off/no/0 |
ObjectToStringConverter | Object----->String 调用 toString 方法转换 |
StringToNumberConverterFactory | String----->Number(如 Integer、Long 等) |
NumberToNumberConverterFactory | Number 子类型 (Integer、Long、Double 等)-----> Number 子类型 (Integer、Long、Double 等) |
StringToCharacterConverter | String----->java.lang.Character 取字符串第一个字符 |
NumberToCharacterConverter | Number 子类型 (Integer、Long、Double 等)-----> java.lang.Character |
CharacterToNumberFactory | java.lang.Character ----->Number 子类型 (Integer、Long、Double 等) |
StringToEnumConverterFactory | String----->enum 类型 |
EnumToStringConverter | enum 类型 ----->String 返回 enum 对象的 name () 值 |
StringToLocaleConverter | String----->java.util.Local |
PropertiesToStringConverter | java.util.Properties----->String |
StringToPropertiesConverter | String----->java.util.Properties |
集合、数组相关转换器
转换器类 | 功能 |
---|---|
ArrayToCollectionConverter | 任意 S 数组 ----> 任意 T 集合(List、Set) |
CollectionToArrayConverter | 任意 T 集合(List、Set)----> 任意 S 数组 |
ArrayToArrayConverter | 任意 S 数组 <----> 任意 T 数组 |
CollectionToCollectionConverter | 任意 T 集合(List、Set)<----> 任意 T 集合(List、Set) |
MapToMapConverter | Map<---->Map 之间的转换 |
ArrayToStringConverter | 任意 S 数组 ---->String 类型 |
StringToArrayConverter | String-----> 数组 默认通过 “,” 分割,且去除字符串的两边空格 (trim) |
ArrayToObjectConverter | 任意 S 数组 ----> 任意 Object 的转换 |
ObjectToArrayConverter | Object-----> 单元素数组 |
CollectionToStringConverter | 任意 T 集合(List、Set)---->String 类型 |
StringToCollectionConverter | String-----> 集合(List、Set) |
CollectionToObjectConverter | 任意 T 集合 ----> 任意 Object 的转换 |
ObjectToCollectionConverter | Object-----> 单元素集合 |
默认(fallback)转换器之前的转换器不能转换时调用
转换器类 | 功能 |
---|---|
ObjectToObjectConverter | Object(S)----->Object(T)首先尝试 valueOf 进行转换、没有则尝试 new 构造器 (S) |
IdToEntityConverter | Id(S)----->Entity(T) |
FallbackObjectToStringConverter | Object----->String 最终转换方法,调用 toString () |
4.ConverterFactory
需求:需要将一个类中的 String 转换成 Enum,而且是有多个,如:
@Data
public class Person {
private String name;
private Telephone telephone;
private Sex sex;
private Race race;
那么我们再通过写一个 SexConverter 和 RaceConverter 是可以实现,但是我们当然有更懒的实现方法。
通过工厂方法,抽象出将 String 转化成枚举的过程,省去每次再去定义一个方法去实现类似业务的转化器。
4.1 接口介绍
public interface ConverterFactory<S, R> {
/**
* Get the converter to convert from S to target type T, where T is also an instance of R.
* @param <T> the target type
* @param targetType the target type to convert to
* @return A converter from S to T
*/
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
4.2 实例
我们来一个覆盖 spring 默认的 StringConvertorFactory
4.2.1 工具类
/**
* <p>
* 功能:将枚举的数字转化成枚举列
* </p>
* @param <T> the type parameter
*
* @author Kings
* @ClassName Convent tag num 2 emum list.
* @Version V1.0.
* @date 2016.03.24 18:46:21
*/
public class ConventNum2Emum<T extends Enum<T>> {
/**
* <p>
* 功能:与或转化
* </p>
* @param status :状态
* @param enumType :枚举类型
*
* @return list
* @author Kings
* @date 2016.03.24 18:46:21
*/
public List<T> convent(Long status,Class enumType) {
List<T> tags = new ArrayList<T>();
if(status != null){
Field[] fields = enumType.getFields();
for (Field f : fields) {
Enum<T> e = Enum.valueOf(enumType,f.getName());
Long eValue = Long.parseLong(e.toString());
if((eValue & status) == eValue){
tags.add((T) e);
}
}
}
return tags;
}
/**
* <p>
* 功能:非与或转换
* </p>
* @param status :状态
* @param enumType :枚举类型
*
* @return list
* @author Kings
* @date 2016.06.15 10:46:35
*/
public T conventNormal(Long status, Class enumType) {
if (status != null) {
Field[] fields = enumType.getFields();
for (Field f : fields) {
Enum<T> e = Enum.valueOf(enumType, f.getName());
Long eVlue = Long.parseLong(e.toString());
if (status.longValue() == eVlue.longValue()) {
return (T) e;
}
}
}
return null;
}
}
4.2.1 自定义 ConverterFactory
public final class MyStringConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new LongToEnum(targetType);
}
private class LongToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
public LongToEnum(Class<T> enumType) {
this.enumType = enumType;
}
@Override
public T convert(String source) {
return (T) new ConventNum2Emum().conventNormal(Long.parseLong(source), enumType);
}
}
}
4.2.2 配置
<mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
<bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.kings.template.mvc.MyStringConverterFactory"></bean>
</set>
</property>
</bean>
5. ConditionalGenericConverter
ConditionalGenericConverter 继承了 2 个重要的接口 GenericConverter 和 ConditionConverter
5.1 接口说明
GenericConverter
/**
* Return the source and target types that this converter can convert between.
* <p>Each entry is a convertible source-to-target type pair.
* <p>For {@link ConditionalConverter conditional converters} this method may return
* {@code null} to indicate all source-to-target pairs should be considered.
*/
Set<ConvertiblePair> getConvertibleTypes();
/**
* Convert the source object to the targetType described by the {@code TypeDescriptor}.
* @param source the source object to convert (may be {@code null})
* @param sourceType the type descriptor of the field we are converting from
* @param targetType the type descriptor of the field we are converting to
* @return the converted object
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
GenericConverter 接口是所有的 Converter 接口中最灵活也是最复杂的一个类型转换接口。像我们之前介绍的 Converter 接口只支持从一个原类型转换为一个目标类型;ConverterFactory 接口只支持从一个原类型转换为一个目标类型对应的子类型;而 GenericConverter 接口支持在多个不同的原类型和目标类型之间进行转换,这也就是 GenericConverter 接口灵活和复杂的地方。GenericConverter 接口中一共定义了两个方法,getConvertibleTypes () 和 convert (Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes 方法用于返回这个 GenericConverter 能够转换的原类型和目标类型的这么一个组合;convert 方法则是用于进行类型转换的,我们可以在这个方法里面实现我们自己的转换逻辑。
5.2 实例
5.2.1 实体类
为了方便我就直接用 User 和 Person
@Data
public class Person {
private String name;
private Telephone telephone;
private Sex sex;
private Race race;
private User u;
}
5.2.1 转化器
public class MyGenericConverter implements GenericConverter {
@Autowired
private UserService userService;
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
pairs.add(new ConvertiblePair(String.class, User.class));
//受web层的request.getParamater()的影响,在web层Long作用不了
pairs.add(new ConvertiblePair(Long.class, User.class));
return pairs;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if(sourceType.getType() == Long.class){
return userService.selectByPrimaryKey(source);
} else if(sourceType.getType() == String.class){
User u4q = new User();
u4q.setName(source+"");
return userService.selectOne(u4q);
}
return null;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
5.2.3 控制器
@RequestMapping (value="/converter/2",method= RequestMethod.GET)
@ResponseBody
public Person demo2(Person p) {
return p;
}
直接访问:http://localhost:8080/kingstemplate/converter/2?u=ws
得到 Json 结果如下:
{"name":null,"telephone":null,"sex":null,"race":null,"u":{"id":1,"name":"ws","age":26}}
5.2.4 非 web 项目
在 5.2.3 里面我们用不了 Long 转 User,因为受 web 项目的影响,这次我们来个非 web 项目的
首先注册一个 DefaultConversionService,因为它里面有个 addConverter 方便我么自己加入容器中的 converter
<bean id="defaultConversionService" class="org.springframework.core.convert.support.DefaultConversionService"></bean>
再上测试 demo
public class ConverterDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config/spring.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
MyGenericConverter myGenericConverter = new MyGenericConverter();
//注入接口
myGenericConverter.setUserService(userService);
DefaultConversionService defaultConversionService = (DefaultConversionService) context.getBean("defaultConversionService");
defaultConversionService.addConverter(myGenericConverter);
User u = defaultConversionService.convert(1L, User.class);//Long->User
System.out.println(u.getAge());//age输出不告诉你
User u1 = defaultConversionService.convert("ws", User.class);//String->User
System.out.println(u1.getAge());//age输出不告诉你
}
}
6.ConversionService
一般的 ConversionService 最底层都会继承 ConverterRegistry 和 ConversionService
6.1 ConverterRegistry
6.1.1 接口说明
类型转换器注册支持,可以注册 / 删除相应的类型转换器
注册的时候添加了:Converter、GenericConverter、ConverterFactory 以及移除某些类型的转换
void addConverter(Converter<?, ?> converter);
/**
* Add a plain converter to this registry.
* The convertible source/target type pair is specified explicitly.
* <p>Allows for a Converter to be reused for multiple distinct pairs without
* having to create a Converter class for each pair.
* @since 3.1
*/
<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
/**
* Add a generic converter to this registry.
*/
void addConverter(GenericConverter converter);
/**
* Add a ranged converter factory to this registry.
* The convertible source/target type pair is derived from the ConverterFactory''s parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved.
*/
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
/**
* Remove any converters from sourceType to targetType.
* @param sourceType the source type
* @param targetType the target type
*/
void removeConvertible(Class<?> sourceType, Class<?> targetType);
6.2 ConversionService
6.2.1 接口说明
提供运行期类型转换的支持
/**
* Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
* <p>If this method returns {@code true}, it means {@link #convert(Object, Class)} is capable
* of converting an instance of {@code sourceType} to {@code targetType}.
* <p>Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return {@code true}
* even though a convert invocation may still generate a {@link ConversionException} if the
* underlying elements are not convertible. Callers are expected to handle this exceptional case
* when working with collections and maps.
* @param sourceType the source type to convert from (may be {@code null} if source is {@code null})
* @param targetType the target type to convert to (required)
* @return {@code true} if a conversion can be performed, {@code false} if not
* @throws IllegalArgumentException if {@code targetType} is {@code null}
*/
boolean canConvert(Class<?> sourceType, Class<?> targetType);
/**
* Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
* The TypeDescriptors provide additional context about the source and target locations
* where conversion would occur, often object fields or property locations.
* <p>If this method returns {@code true}, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)}
* is capable of converting an instance of {@code sourceType} to {@code targetType}.
* <p>Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return {@code true}
* even though a convert invocation may still generate a {@link ConversionException} if the
* underlying elements are not convertible. Callers are expected to handle this exceptional case
* when working with collections and maps.
* @param sourceType context about the source type to convert from
* (may be {@code null} if source is {@code null})
* @param targetType context about the target type to convert to (required)
* @return {@code true} if a conversion can be performed between the source and target types,
* {@code false} if not
* @throws IllegalArgumentException if {@code targetType} is {@code null}
*/
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Convert the given {@code source} to the specified {@code targetType}.
* @param source the source object to convert (may be {@code null})
* @param targetType the target type to convert to (required)
* @return the converted object, an instance of targetType
* @throws ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is {@code null}
*/
<T> T convert(Object source, Class<T> targetType);
/**
* Convert the given {@code source} to the specified {@code targetType}.
* The TypeDescriptors provide additional context about the source and target locations
* where conversion will occur, often object fields or property locations.
* @param source the source object to convert (may be {@code null})
* @param sourceType context about the source type to convert from
* (may be {@code null} if source is {@code null})
* @param targetType context about the target type to convert to (required)
* @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}
* @throws ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is {@code null},
* or {@code sourceType} is {@code null} but source is not {@code null}
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
Spring 提供了两个默认实现(其都实现了 ConverterRegistry、ConversionService 接口):
DefaultConversionService: 默认的类型转换服务实现;
DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。
发现一个机智的导航
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 的常用接口目录
5.盘点springmvc的常用接口之Converter(上篇)
5.盘点springmvc的常用接口之Converter(上篇)###
前言
上一章讲完了已淘汰的PropertyEditor
转换器,那么这一章隆重介绍新的转换器org.springframework.core.convert.converter.Converter
。(别看这接口也叫Converter,和前几章讲到的那个HttpMessageConverter
不是同一个东西,千万不要搞混了-_-)。
PropertyEditor
的缺点其实很明显,它只能实现从String转化到目标类型。
而Converter
可以实现从任意的原类型转化到目标类型。
接口说明
public interface Converter<S, T> {
T convert(S source);
}
泛型S,T 分别为source原类型,target目标类型。
方法convert
即为转换过程。
示例1
使用Converter
接口重新实现上一章的转化Telephone
package com.demo.mvc.component;
import org.springframework.core.convert.converter.Converter;
import com.demo.domain.Telephone;
public class TelephoneConverter implements Converter<String, Telephone> {
@Override
public Telephone convert(String source) {
if (StringUtils.hasText(source) && source.matches("^\\d+\\-\\d+$")) {
String[] strs = source.split("\\-");
return new Telephone(strs[0], strs[1]);
}
return null;
}
}
package com.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.domain.Person;
import com.demo.domain.Telephone;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
@RequestMapping("demo5")
public class ConverterDemoController {
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public String postTelephone(@RequestParam Telephone telephone) {
log.info(telephone.toString());
return telephone.toString();
}
}
spring-boot注册:
package com.demo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import com.demo.mvc.component.TelephoneConverter;
@SpringBootApplication
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
public FormattingConversionService mvcConversionService() {
TelephoneConverter telephoneConverter = new TelephoneConverter();
FormattingConversionService formattingConversionService = super.mvcConversionService();
formattingConversionService.addConverter(telephoneConverter);
return formattingConversionService;
}
}
xml注册:
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService">
<property name="converters">
<set>
<bean/>
</set>
</property>
</bean>
示例2
很多朋友都问过我如何把类似“2016-01-01 01:00:00”这样的字符串时间传到控制器中的Date类型。
如果不知道Converter
的话,只能传字符串过去,在Controller里写硬代码转换,那就又成了垃圾代码。
Converter
也可以转化表单数据到实体中各个属性。下面是的示例展示表单数据赋值进Person对象中,其中java.util.Date和Telephone默认是不能直接从字符串到对象的。
实体追加属性:
package com.demo.domain;
import java.util.Date;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class Person {
private String firstName;
private String lastName;
private Telephone telephone;
private Date registerTime;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
}
String到java.util.Date
的转换器:
package com.demo.mvc.component;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
public class StringToUtilDateConverter implements Converter<String, java.util.Date> {
private DateFormat df;
public StringToUtilDateConverter(String pattern) {
df = new SimpleDateFormat(pattern);
}
@Override
public Date convert(String source) {
if (StringUtils.hasText(source)) {
try {
return df.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
controller:
@ResponseBody
@RequestMapping(value = "person", method = RequestMethod.POST)
public String postPerson(Person person) {
log.info(person.toString());
log.info(person.getTelephone().toString());
log.info(person.getRegisterTime().toString());
return person.toString();
}
转换器注册略,和示例1相同。
测试访问POST http://localhost:8080/demo5
表单数据:
firstName = Bill
lastName = Gates
telephone = 010-12345
registerTime = 2016-01-01 01:00:00
Person类中的Telephone和Date都能被正确赋值。
那么问题来了,假如PropertyEditor
和Converter
同时使用会怎么样呢?
Spring的策略是先查找PropertyEditor
进行类型转换,如果没有找到相应的PropertyEditor
再通过Converter
进行转换。
附录:Spring内置的各种转换器
标量转换器
转换器类 | 功能 |
---|---|
StringToBooleanConverter | String----->Boolean true: true/on/yes/1;false: false/off/no/0 |
ObjectToStringConverter | Object----->String 调用toString方法转换 |
StringToNumberConverterFactory | String----->Number(如Integer、Long等) |
NumberToNumberConverterFactory | Number子类型(Integer、Long、Double等)-----> Number子类型(Integer、Long、Double等) |
StringToCharacterConverter | String----->java.lang.Character 取字符串第一个字符 |
NumberToCharacterConverter | Number子类型(Integer、Long、Double等)-----> java.lang.Character |
CharacterToNumberFactory | java.lang.Character ----->Number子类型(Integer、Long、Double等) |
StringToEnumConverterFactory | String----->enum类型 |
EnumToStringConverter | enum类型----->String 返回enum对象的name()值 |
StringToLocaleConverter | String----->java.util.Local |
PropertiesToStringConverter | java.util.Properties----->String |
StringToPropertiesConverter | String----->java.util.Properties |
集合、数组相关转换器
转换器类 | 功能 |
---|---|
ArrayToCollectionConverter | 任意S数组---->任意T集合(List、Set) |
CollectionToArrayConverter | 任意T集合(List、Set)---->任意S数组 |
ArrayToArrayConverter | 任意S数组<---->任意T数组 |
CollectionToCollectionConverter | 任意T集合(List、Set)<---->任意T集合(List、Set) |
MapToMapConverter | Map<---->Map之间的转换 |
ArrayToStringConverter | 任意S数组---->String类型 |
StringToArrayConverter | String----->数组 默认通过“,”分割,且去除字符串的两边空格(trim) |
ArrayToObjectConverter | 任意S数组---->任意Object的转换 |
ObjectToArrayConverter | Object----->单元素数组 |
CollectionToStringConverter | 任意T集合(List、Set)---->String类型 |
StringToCollectionConverter | String----->集合(List、Set) |
CollectionToObjectConverter | 任意T集合---->任意Object的转换 |
ObjectToCollectionConverter | Object----->单元素集合 |
默认(fallback)转换器之前的转换器不能转换时调用
转换器类 | 功能 |
---|---|
ObjectToObjectConverter | Object(S)----->Object(T)首先尝试valueOf进行转换、没有则尝试new 构造器(S) |
IdToEntityConverter | Id(S)----->Entity(T) |
FallbackObjectToStringConverter | Object----->String 最终转换方法,调用toString() |
p.s.:Spring自带了这么多的转换器,从而可以自动化地转换类型。开发者无需再实现低级的普通类型间的转换,不然像String->Integer,String->Boolean这种,每个都要开发者自己实现,那不是要吐了。关于Converter
的介绍还没有完,在下一章将介绍更加深入的使用Converter
。
友情链接:
盘点springmvc的常用接口目录
关于【原创】遨游 springmvc 之 Converter的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于11.SpringMVC 之 HttpMessageConverter、12.Spring MVC类型转换器(Converter)、3. 盘点 springmvc 的常用接口之 HttpMessageConverter、5.盘点springmvc的常用接口之Converter(上篇)的相关信息,请在本站寻找。
本文标签: