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 onvert.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系统中
由‘onvert.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));
spring mvc和boot区别}
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添加转化器分为了三类去添加:addScalarConvert
ers-参数到其他类型参数;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 verter.Converter;
DateFormat;
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 || im().equals("")) {
return null;
}
Date result = null;
String originalValue = im();
if (source.matches("^\\d{4}-\\d{1,2}$")) {
return parseDate(source, ()[0]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
return parseDate(source, ()[1]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}$")) {
return parseDate(source, ()[2]);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, ()[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, ()[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 = "st.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 onvert.support
import verter.Converter;
import verter.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) {
}
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.umType, im());
}
}
}
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) {
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return this.sourceType;
}
public Class<?> getTargetType() {
return this.targetType;
}
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论