SpringBoot(十七):SpringBoot2.1.1数据类型转化器Converter
什么场景下需要使用类型化器Converter?
springboot2.1.1在做Restful Api开发过程中往往希望接口直接接收date类型参数,但是默认不加设置是不支持的,会抛出异常:系统是希望接收date类型,string无法转化为date错误。
{
"timestamp": "2019-10-29 11:52:05",
"status": 400,
"error": "Bad Request",
"message": "Failed to convert value of type 'java.lang.String' to required type 'java.util.Date';
nested exception is org.springframework.core.convert.ConversionFailedException:
Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.util.Date] for value '2019-10-09';
nested exception is java.lang.IllegalArgumentException",
"path": "/api/v1/articles"
}
此时就需要配置自定义类型转化器。
实际上在SpringMvc框架中已经内置了很多类型转化器,当发送一个post,get等请求后,调用请求方法之前会对方法参数进行类型转化,默认在SpringMvc系统中由‘org.springframework.core.convert.support.DefaultConversionService’装配了一套默认Converters。
public class DefaultConversionService extends GenericConversionService { @Nullable
private static volatile DefaultConversionService sharedInstance; /**
* Create a new {@code DefaultConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}.
*/
public DefaultConversionService() {
addDefaultConverters(this);
} /**
* Return a shared default {@code ConversionService} instance,
* lazily building it once needed.
* <p><b>NOTE:</b> We highly recommend constructing individual
* {@code ConversionService} instances for customization purposes.
* This accessor is only meant as a fallback for code paths which
* need simple type coercion but cannot access a longer-lived
* {@code ConversionService} instance any other way.
* @return the shared {@code ConversionService} instance (never {@code null})
* @since 4.3.5
*/
public static ConversionService getSharedInstance() {
DefaultConversionService cs = sharedInstance;
if (cs == null) {
synchronized (DefaultConversionService.class) {
cs = sharedInstance;
if (cs == null) {
cs = new DefaultConversionService();
sharedInstance = cs;
}
}
}
return cs;
} /**
* Add converters appropriate for most environments.
* @param converterRegistry the registry of converters to add to
* (must also be castable to ConversionService, e.g. being a {@link ConfigurableConversionService})
* @throws ClassCastException if the given ConverterRegistry could not be cast to a ConversionService
*/
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry); converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter()); converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
} /**
* Add common collection converters.
* @param converterRegistry the registry of converters to add to
* (must also be castable to ConversionService, e.g. being a {@link ConfigurableConversionService})
* @throws ClassCastException if the given ConverterRegistry could not be cast to a ConversionService
* @since 4.2.3
*/
public static void addCollectionConverters(ConverterRegistry converterRegistry) {
ConversionService conversionService = (ConversionService) converterRegistry; converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
converterRegistry.addConverter(new CollectionToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
converterRegistry.addConverter(new MapToMapConverter(conversionService)); converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
converterRegistry.addConverter(new StringToArrayConverter(conversionService)); converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
converterRegistry.addConverter(new ObjectToArrayConverter(conversionService)); converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
converterRegistry.addConverter(new StringToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); converterRegistry.addConverter(new StreamConverter(conversionService));
} private static void addScalarConverters(ConverterRegistry converterRegistry) {
converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharacterConverter());
converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new NumberToCharacterConverter());
converterRegistry.addConverterFactory(new CharacterToNumberFactory()); converterRegistry.addConverter(new StringToBooleanConverter());
converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry)); converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new StringToLocaleConverter());
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCharsetConverter());
converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToCurrencyConverter());
converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new StringToPropertiesConverter());
converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToUUIDConverter());
converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
} }
DefaultConversionService中在给converterRegistry添加转化器分为了三类去添加:addScalarConverters-参数到其他类型参数;addCollectionConverters-集合转化器;addDefaultConverters-默认转化器。
查找相应类型转化器的方式,通过sourceType,targetType去配置。在注册转化器时,会记录该converter是将什么类型的数据处理为什么类型的数据,其实就是记录了sourceType,targetType。
SpringMvc中Converter的用法
Converter是SpringMvc框架中的一个功能点,通过转化器可以实现对UI端传递的数据进行类型转化,实现类型转化可以实现接口Converter<S,T>接口、ConverterFactory接口、GenericConverter接口。ConverterRegistry接口就是对这三种类型提供了对应的注册方法。
Converter接口用法:
Converter接口的定义:
public interface Converter<S, T> {
T convert(S source);
}
接口是使用了泛型的,第一个类型表示原类型,第二个类型表示目标类型,然后里面定义了一个convert方法,将原类型对象作为参数传入进行转换之后返回目标类型对象。
用法:
自定义实现字符串日期转化为日期类型供接口接收:
import org.springframework.core.convert.converter.Converter; import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date; public class StringToDateConverter implements Converter<String, Date> {
private static ThreadLocal<SimpleDateFormat[]> formats = new ThreadLocal<SimpleDateFormat[]>() {
protected SimpleDateFormat[] initialValue() {
return new SimpleDateFormat[]{
new SimpleDateFormat("yyyy-MM"),
new SimpleDateFormat("yyyy-MM-dd"),
new SimpleDateFormat("yyyy-MM-dd HH"),
new SimpleDateFormat("yyyy-MM-dd HH:mm"),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
};
}
}; @Override
public Date convert(String source) {
if (source == null || source.trim().equals("")) {
return null;
} Date result = null;
String originalValue = source.trim();
if (source.matches("^\\d{4}-\\d{1,2}$")) {
return parseDate(source, formats.get()[0]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
return parseDate(source, formats.get()[1]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}$")) {
return parseDate(source, formats.get()[2]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formats.get()[3]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formats.get()[4]);
} else if (originalValue.matches("^\\d{1,13}$")) {
try {
long timeStamp = Long.parseLong(originalValue);
if (originalValue.length() > 10) {
result = new Date(timeStamp);
} else {
result = new Date(1000L * timeStamp);
}
} catch (Exception e) {
result = null;
e.printStackTrace();
}
} else {
result = null;
} return result;
} /**
* 格式化日期
*
* @param dateStr String 字符型日期
* @param dateFormat 日期格式化器
* @return Date 日期
*/
public Date parseDate(String dateStr, DateFormat dateFormat) {
Date date = null;
try {
date = dateFormat.parse(dateStr);
} catch (Exception e) { }
return date;
}
}
在WebMvcConfiguration中注入该Converter.
/**
* WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现 WebMvcConfigurer 这个接口
*/
@Configuration
@Import({WebMvcAutoConfiguration.class})
@ComponentScan(
value = "com.dx.test.web",
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
})
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public StringToDateConverter stringToDateConverter() {
return new StringToDateConverter();
}
...
}
这样前端访问Restful api时,当api接口,接口需要接收date类型的参数时,前端传入日期字符串后,后端会使用该类型转化器将参数转化为date后传递给api接口函数。
考虑这样一种情况,我们有一个表示用户状态的枚举类型UserStatus,如果要定义一个从String转为UserStatus的Converter,根据之前Converter接口的说明,我们的StringToUserStatus大概是这个样子:
public class StringToUserStatus implements Converter<String, UserStatus> {
@Override
public UserStatus convert(String source) {
if (source == null) {
return null;
}
return UserStatus.valueOf(source);
}
}
如果这个时候有另外一个枚举类型UserType,那么我们就需要定义另外一个从String转为UserType的Converter——StringToUserType,那么我们的StringToUserType大概是这个样子:
public class StringToUserType implements Converter<String, UserType> {
@Override
public UserType convert(String source) {
if (source == null) {
return null;
}
return UserType.valueOf(source);
}
}
如果还有其他枚举类型需要定义原类型为String的Converter的时候,我们还得像上面那样定义对应的Converter。有了ConverterFactory之后,这一切都变得非常简单,因为UserStatus、UserType等其他枚举类型同属于枚举,所以这个时候我们就可以统一定义一个从String到Enum的ConverterFactory,然后从中获取对应的Converter进行convert操作。
ConverterFactory接口的用法:
ConverterFactory接口的定义:
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
用法:
Spring官方已经为我们实现了这么一个StringToEnumConverterFactory:
Spring官方已经为我们实现了这么一个StringToEnumConverterFactory:
package org.springframework.core.convert.support import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory; @SuppressWarnings({"unchecked", "rawtypes"})
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> { public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(targetType);
} private class StringToEnum<T extends Enum> implements Converter<String, T> { private final Class<T> enumType; public StringToEnum(Class<T> enumType) {
this.enumType = enumType;
} public T convert(String source) {
if (source.length() == 0) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
return (T) Enum.valueOf(this.enumType, source.trim());
}
} }
GenericConverter接口的用法:
GenericConverter接口是所有的Converter接口中最灵活也是最复杂的一个类型转换接口。
Converter接口只支持从一个原类型转换为一个目标类型;ConverterFactory接口只支持从一个原类型转换为一个目标类型对应的子类型;而GenericConverter接口支持在多个不同的原类型和目标类型之间进行转换,这也就是GenericConverter接口灵活和复杂的地方。
GenericConverter接口的定义:
public interface GenericConverter { Set<ConvertiblePair> getConvertibleTypes(); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); public static final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(sourceType, "Source type must not be null");
Assert.notNull(targetType, "Target type must not be null");
this.sourceType = sourceType;
this.targetType = targetType;
} public Class<?> getSourceType() {
return this.sourceType;
} public Class<?> getTargetType() {
return this.targetType;
}
}
}
关于GenericConverter的使用,这里也举一个例子。假设我们有一项需求是希望能通过user的id或者username直接转换为对应的user对象,那么我们就可以针对于id和username来建立一个GenericConverter。这里假设id是int型,而username是String型的,所以我们的GenericConverter可以这样来写:
public class UserGenericConverter implements GenericConverter { @Autowired
private UserService userService; @Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null || sourceType == TypeDescriptor.NULL || targetType == TypeDescriptor.NULL) {
return null;
}
User user = null;
if (sourceType.getType() == Integer.class) {
user = userService.findById((Integer) source);//根据id来查找user
} else if (sourceType.getType() == String.class) {
user = userService.find((String)source);//根据用户名来查找user
}
return user;
} @Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
pairs.add(new ConvertiblePair(Integer.class, User.class));
pairs.add(new ConvertiblePair(String.class, User.class));
return pairs;
}
}
使用GenericConverter实现对@RequestHeader中文参数值进行解码
默认从UI端传入到服务器端的header中文参数都会被encoder,为了实现对header中文解码,可以通过GenericConverter实现解码。
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestHeader; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Set; public class RequestHeaderDecodeConverter implements GenericConverter {
private static final String ENCODE = "utf-8";
private String encoder = null; public RequestHeaderDecodeConverter(@Nullable String encoder) {
if (encoder == null) {
this.encoder = ENCODE;
} else {
this.encoder = encoder;
}
} @Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
pairs.add(new ConvertiblePair(String.class, String.class));
return pairs;
} @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null || sourceType == null || targetType == null) {
return null;
} Object userName = source;
if (targetType.hasAnnotation(RequestHeader.class) && targetType.getType().equals(String.class)) {
try {
System.out.println(source.toString());
userName = (source != null ? URLDecoder.decode(source.toString(), ENCODE) : null);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} return userName;
}
}
在SpringBoot中配置中引入
/**
* WebMvcConfigurerAdapter 这个类在SpringBoot2.0已过时,官方推荐直接实现 WebMvcConfigurer 这个接口
*/
@Configuration
@Import({WebMvcAutoConfiguration.class})
@ComponentScan(
value = "com.dx.test.web",
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
})
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public RequestHeaderDecodeConverter requestHeaderDecodeConverter() {
return new RequestHeaderDecodeConverter(null);
}
...
}
测试Controller接口
@ApiOperation(value = "查询文章列表", code = 200, httpMethod = "GET", produces = "application/json", notes = "queryById方法定义说明:根据title检索文章,返回文章列表。")
@ApiImplicitParams(value = {
@ApiImplicitParam(name = "userId", paramType = "header", value = "操作用户id", required = false, dataType = "String"),
@ApiImplicitParam(name = "userName", paramType = "header", value = "操作用户", required = false, dataType = "String"),
@ApiImplicitParam(name = "title", paramType = "query", value = "文章标题检索值", required = false, dataType = "String"),
@ApiImplicitParam(name = "articleType", paramType = "query", value = "文章类型", required = false, dataType = "ArticleType"),
@ApiImplicitParam(name = "createTime", paramType = "query", value = "文章发布时间", required = false, dataType = "Date")
})
@RequestMapping(value = {"/articles"}, method = {RequestMethod.GET}, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
public List<Article> queryList(
@RequestHeader(value = "userId", required = false) String userId,
@RequestHeader(value = "userName", required = false) String userName,
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "articleType",required = false) ArticleType articleType,
@RequestParam(value = "createTime", required = false) Date createTime) {
System.out.println(createTime);
List<Article> articles = new ArrayList<>();
articles.add(new Article(1L, "文章1", "", "", new Date()));
articles.add(new Article(2L, "文章2", "", "", new Date()));
articles.add(new Article(3L, "文章3", "", "", new Date()));
articles.add(new Article(4L, "文章4", "", "", new Date())); return articles.stream().filter(s -> s.getTitle().contains(title)).collect(Collectors.toList());
}
断点在Resetful api内部,可以发现当WebMvcConfiguration中注入 RequestHeaderDecodeConverter 对userName是否encoder变化情况。
《SpringMVC数据类型转换——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC》
SpringBoot(十七):SpringBoot2.1.1数据类型转化器Converter的更多相关文章
- 自定义Retrofit转化器Converter
我们来看一下Retrofit的使用 interface TestConn { //这里的Bitmap就是要处理的类型 @GET("https://ss0.baidu.com/73F1bjeh ...
- springboot(四).配置FastJson自定义消息转化器
配置FastJson自定义消息转化器 一.fastJson简介 fastJson是阿里巴巴旗下的一个开源项目之一,顾名思义它专门用来做快速操作Json的序列化与反序列化的组件.它是目前json解析最快 ...
- 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-10.Springboot2.x用户登录拦截器开发实战
笔记 10.Springboot2.x用户登录拦截器开发实战 简介:实战开发用户登录拦截器拦截器 LoginInterceptor 1.实现接口 LoginI ...
- jQuery源码分析系列(36) : Ajax - 类型转化器
什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的 ...
- 05.配置为开发模式、配置静态资源locations、自定义消息转化器、FastJson
配置为开发模式,代码做了修改,不用重新运行 idea需要该配置,mac测试无效 <dependency> <groupId>org.springframework</gr ...
- mysql-数据类型与java数据类型转化工具类
mysql和java对照表 类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int) 描述 VARCHAR L+N VARCHAR java.lang.Stri ...
- SpringMVC09异常处理和类型转化器
public class User { private String name; private Integer age; public String getName() { return name; ...
- springBoot(6)---过滤器,监听器,拦截器
过滤器,监听器,拦截器 一.理解它们 看里十几篇博客,总算有点小明白,总的来讲,两张图可以让我看明白点. 通过两幅图我们可以理解拦截器和过滤器的特点 1.过滤器 过滤器是在请求进入tomcat容器后, ...
- SpringBoot开发使用@ImportResource注解影响拦截器
问题描述 今天在给SpringBoot项目配置拦截器的时候发现怎么都进不到拦截器的方法里面,在搜索引擎上看了无数篇关于配置拦截器的文章都没有找到解决方案. 就在我准备放弃的时候,在 CSDN 上发现了 ...
随机推荐
- consul:kv
consul除了提供了服务发现的功能,还是提供了kv store的功能,kv store可用于动态配置服务.协调服务.leader选举等场景. consul的kv提供了cli和http的两种接口: h ...
- kubernetes网络之Flannel
简介 Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址. 在默认的Dock ...
- Kubernetes学习之原理
Kubernetes基本概念 一.Label selector在kubernetes中的应用场景 1.kube-controller-manager的replicaSet通过定义的label 来筛选要 ...
- DMA初识
功能 DMA可以在CPU不干涉的情况下,进行数据的搬移.例如:通过DMA来获取摄像头输出的像素数据,而占用少量CPU资源. DMAMUX DMAMUX负责数据的路由:将触发源绑定到特定的DMA通道,当 ...
- Python时间模块。
python中时间的表示方式 unix时间戳,字符串时间,格式化时间 时间模块有,time,datetime,calendar #time模块 import time #获取本地时间戳,返回浮点数 p ...
- 微信开发:"errcode": -1000,"errmsg": "system error"错误的解决办法
最近在微信开发使用微信公众平台接口调试工具时遇到错误. 错误再现:使用appid及appsecret在该测试工具中获取access_token,检查问题时,校验全部通过,但是无法获取access_to ...
- 安全组与网络ACL
通过配置网络ACL和安全组策略,保障VPC内的弹性云服务器安全使用. 安全组对弹性云服务器进行防护:设置不同安全组访问规则实现系统访问控制 网络ACL对子网进行防护:可实现网络区域访问控制
- vector Construct
#include<vector> #include<iostream> using namespace std; void Test(); void main() { ,,,, ...
- vo bo po dao pojo dto
Recommended for you: Get network issues from WhatsUp Gold. Not end users. DAO: Data access object d ...
- Struct Socket详细分析(转)
原文地址:http://anders0913.iteye.com/blog/411986 用户使用socket系统调用编写应用程序时,通过一个数字来表示一个socket,所有的操作都在该数字上进行,这 ...