ConversionService 类型转换体系
概述
ConversionService 是 Spring 的类型转换核心接口,负责在字符串到目标类型、数值到数值、枚举等类型之间进行转换。Spring Boot 在此基础上进一步扩展,支持了 Duration、DataSize、Period 等特殊类型的便捷写法。
Spring Boot 的外部化配置中,application.yml 中的字符串值要绑定到 @ConfigurationProperties 的 Java 属性字段,中间的核心桥梁就是 ConversionService。
本文基于 Spring Framework 6.x / Spring Boot 3.x 源码分析。
1. 整体架构
应用代码
│
├─ @Value("${server.port}") int port ← 字符串 → int
├─ @ConfigurationProperties 绑定 ← YAML 值 → 目标字段
│
└─ ConversionService.convert(value, targetType)
│
└─ GenericConversionService
│
├─ 查找匹配的 Converter
│ ├─ String → Integer
│ ├─ String → Duration
│ ├─ String → DataSize
│ └─ ...
│
├─ 缓存匹配结果
│ └─ convertersCache: ConcurrentHashMap<ConvertiblePair, Converter>
│
└─ 执行转换
└─ converter.convert(source)2. ConversionService.canConvert() 的实现
2.1 接口定义
java
// ConversionService.java
public interface ConversionService {
/** 判断是否可以从 sourceType 转换为 targetType */
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
/** 判断是否可以从 sourceType 转换为 targetType(带泛型信息) */
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
/** 执行类型转换 */
@SuppressWarnings("unchecked")
@Nullable
<T> T convert(Object source, Class<T> targetType);
/** 执行类型转换(带泛型信息) */
@Nullable
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}2.2 GenericConversionService.canConvert() 源码
java
// GenericConversionService.java
public class GenericConversionService implements ConfigurableConversionService {
// 转换器缓存 —— key = ConvertiblePair, value = 匹配的 Converter
private final ConcurrentHashMap<ConvertiblePair, GenericConverter> convertersCache =
new ConcurrentHashMap<>(256);
@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
// 委托给 getConverter() 查找匹配的转换器
GenericConverter converter = getConverter(sourceType, targetType);
// 如果找到转换器 → 可以转换
return converter != null;
}
@Override
public boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType) {
// 包装为 TypeDescriptor 再委托
return canConvert(
(sourceType != null ? TypeDescriptor.valueOf(sourceType) : null),
TypeDescriptor.valueOf(targetType));
}
@Nullable
protected GenericConverter getConverter(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
// 1. 从 ConvertiblePair 查缓存
ConvertiblePair pair = new ConvertiblePair(
(sourceType != null ? sourceType.getType() : null),
targetType.getType());
GenericConverter converter = this.convertersCache.get(pair);
if (converter != null) {
return converter; // 缓存命中
}
// 2. 在注册的转换器中查找匹配的
converter = findConverter(sourceType, targetType);
if (converter != null) {
// 3. 写入缓存
this.convertersCache.put(pair, converter);
}
return converter;
}
}2.3 查找逻辑
findConverter(sourceType=String.class, targetType=Duration.class)
│
├─ 1. 精确匹配
│ ConvertiblePair(String, Duration) → 直接命中
│ → StringToDurationConverter
│
├─ 2. 父类/接口匹配(精确匹配未命中时)
│ sourceType = String.class(无父类匹配)
│ targetType = Duration.class → TemporalAmount(接口)
│ → 检查是否有 String → TemporalAmount 的转换器
│
└─ 3. 条件匹配(ConditionalGenericConverter)
→ ConditionalGenericConverter.matches(sourceType, targetType)
→ 动态判断是否可用3. DefaultConversionService 注册的 80+ 个默认转换器
3.1 源码
java
// DefaultConversionService.java
public class DefaultConversionService extends GenericConversionService {
public DefaultConversionService() {
// 调用静态方法注册所有默认转换器
addDefaultConverters(this);
}
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
// 字符串相关转换器
converterRegistry.addConverter(new StringToBooleanConverter()); // "true" → true
converterRegistry.addConverter(new StringToCharacterConverter()); // "A" → 'A'
converterRegistry.addConverter(new StringToLocaleConverter()); // "zh_CN" → Locale
converterRegistry.addConverter(new StringToPropertiesConverter()); // "key=value" → Properties
converterRegistry.addConverter(new StringToUUIDConverter()); // "xxx" → UUID
// 数值转换器
converterRegistry.addConverter(new StringToIntegerConverter()); // "123" → 123
converterRegistry.addConverter(new StringToLongConverter()); // "123" → 123L
converterRegistry.addConverter(new StringToFloatConverter()); // "1.5" → 1.5f
converterRegistry.addConverter(new StringToDoubleConverter()); // "1.5" → 1.5
converterRegistry.addConverter(new StringToBigDecimalConverter()); // "1.5" → BigDecimal
converterRegistry.addConverter(new StringToBigIntegerConverter()); // "1" → BigInteger
converterRegistry.addConverter(new NumberToNumberConverterFactory());// int → long, float → double 等
// 枚举转换器
converterRegistry.addConverter(new StringToEnumConverterFactory()); // "RED" → Color.RED
converterRegistry.addConverter(new EnumToStringConverterFactory()); // Color.RED → "RED"
// 集合/数组转换器
converterRegistry.addConverter(new ArrayToCollectionConverter()); // String[] → List<String>
converterRegistry.addConverter(new CollectionToArrayConverter()); // List<String> → String[]
converterRegistry.addConverter(new ArrayToStringConverter()); // String[] → "a,b,c"
converterRegistry.addConverter(new StringToArrayConverter()); // "a,b,c" → String[]
converterRegistry.addConverter(new CollectionToStringConverter()); // [a,b,c] → "a,b,c"
converterRegistry.addConverter(new StringToCollectionConverter()); // "a,b,c" → List.of("a","b","c")
// Map 转换器
converterRegistry.addConverter(new MapToMapConverter());
converterRegistry.addConverter(new ObjectToObjectConverter());
// 字符编码转换器
converterRegistry.addConverter(new StringToCharsetConverter()); // "UTF-8" → Charset
converterRegistry.addConverter(new StringToCurrencyConverter()); // "CNY" → Currency
// 时间相关转换器
converterRegistry.addConverter(new NumberToDurationConverter()); // 3600 → Duration.ofSeconds(3600)
converterRegistry.addConverter(new StringToDurationConverter()); // "10s" → Duration.ofSeconds(10)
converterRegistry.addConverter(new StringToPeriodConverter()); // "2d" → Period.ofDays(2)
converterRegistry.addConverter(new NumberToPeriodConverter()); // 2 → Period.ofDays(2)
// 对象转换
converterRegistry.addConverter(new ObjectToStringConverter()); // 任意对象 → toString()
converterRegistry.addConverter(new ObjectToOptionalConverter()); // 任意对象 → Optional
// 另外还有 InputStream、ByteArray、Resource 等 40+ 个转换器
// 总计约 80+ 个默认转换器
}
}3.2 转换器分类统计
| 类别 | 转换器数量 | 典型转换器 |
|---|---|---|
| String → 基本类型 | ~15 | StringToIntegerConverter、StringToBooleanConverter |
| String → 时间/日期 | ~10 | StringToDurationConverter、StringToPeriodConverter |
| String → 枚举 | ~2 | StringToEnumConverterFactory、EnumToStringConverterFactory |
| 数值 → 数值 | ~10 | NumberToNumberConverterFactory、NumberToDurationConverter |
| 集合/数组 | ~15 | ArrayToCollectionConverter、StringToCollectionConverter |
| 对象 → 对象 | ~8 | ObjectToObjectConverter、ObjectToStringConverter |
| 其他 | ~20 | StringToLocaleConverter、StringToCharsetConverter |
| 合计 | ~80 |
3.3 常用转换器示例
java
ConversionService cs = new DefaultConversionService();
// 字符串 → 整数
cs.canConvert(String.class, Integer.class); // true
cs.convert("123", Integer.class); // 123
// 字符串 → 布尔值
cs.convert("true", Boolean.class); // true
cs.convert("yes", Boolean.class); // true
cs.convert("1", Boolean.class); // true
cs.convert("false", Boolean.class); // false
// 字符串 → 枚举
cs.convert("RED", Color.class); // Color.RED
// 字符串 → UUID
cs.convert("550e8400-e29b-41d4-a716-446655440000", UUID.class);
// 整数 → Duration
cs.convert(3600, Duration.class); // Duration.ofSeconds(3600)4. ApplicationConversionService 的 Spring Boot 扩展
4.1 源码
java
// ApplicationConversionService.java
public class ApplicationConversionService extends DefaultConversionService {
private static volatile ApplicationConversionService sharedInstance;
public ApplicationConversionService() {
super();
// 追加 Spring Boot 特有的转换器
addBeansSupport(); // 支持 Bean 到 Bean 的赋值转换
}
// Spring Boot 扩展的额外转换器注册
public static void addApplicationConverters(ConverterRegistry converterRegistry) {
// Duration 相关——支持 "10s"、"5m"、"2h" 等字符串
addDurationConverters(converterRegistry);
// DataSize 相关——支持 "10MB"、"1GB"、"500KB" 等字符串
addDataSizeConverters(converterRegistry);
// Period 相关——支持 "2d"、"3w"、"1m" 等字符串
addPeriodConverters(converterRegistry);
// 数字格式化相关
addNumberFormattingConverters(converterRegistry);
// ConfigurationProperties 中的集合转换优化
addCollectionConversionSupport(converterRegistry);
}
private static void addDurationConverters(ConverterRegistry registry) {
registry.addConverter(new StringToDurationConverter());
registry.addConverter(new NumberToDurationConverter());
registry.addConverter(new DurationToStringConverter());
}
private static void addDataSizeConverters(ConverterRegistry registry) {
registry.addConverter(new StringToDataSizeConverter()); // "10MB" → DataSize.ofMegabytes(10)
registry.addConverter(new NumberToDataSizeConverter()); // 10 → DataSize.ofBytes(10)
registry.addConverter(new DataSizeToStringConverter()); // DataSize.ofMegabytes(10) → "10MB"
}
}4.2 Spring Boot 追加的转换器
| 转换器 | 输入 | 输出 | 示例 |
|---|---|---|---|
StringToDurationConverter | "10s" / "5m" / "2h" / "3d" | Duration | "10s" → Duration.ofSeconds(10) |
NumberToDurationConverter | 3600 (int) | Duration | 3600 → Duration.ofSeconds(3600) |
StringToDataSizeConverter | "10MB" / "1GB" / "500KB" | DataSize | "10MB" → DataSize.ofMegabytes(10) |
NumberToDataSizeConverter | 1024 (int) | DataSize | 1024 → DataSize.ofBytes(1024) |
StringToPeriodConverter | "2d" / "3w" / "1m" | Period | "2d" → Period.ofDays(2) |
NumberToPeriodConverter | 3 (int) | Period | 3 → Period.ofDays(3) |
4.3 在 Spring Boot 启动时的安装
java
// SpringApplication.java
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
// 设置 ConversionService 为 ApplicationConversionService
// 这意味着所有 Environment 中的类型转换(如 @Value)都会使用 Boot 扩展的转换器
ConfigurableConversionService conversionService =
environment.getConversionService();
// 如果当前 conversionService 是 GenericConversionService 的实例
if (conversionService instanceof GenericConversionService) {
// 追加 ApplicationConversionService 的扩展转换器
ApplicationConversionService.addApplicationConverters(
(GenericConversionService) conversionService);
}
}5. ConvertiblePair 的匹配机制
5.1 ConvertiblePair 定义
java
// ConvertiblePair.java
public final class ConvertiblePair {
private final Class<?> sourceType; // 源类型
private final Class<?> targetType; // 目标类型
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
this.sourceType = sourceType;
this.targetType = targetType;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof ConvertiblePair)) return false;
ConvertiblePair otherPair = (ConvertiblePair) other;
return this.sourceType == otherPair.sourceType
&& this.targetType == otherPair.targetType;
}
@Override
public int hashCode() {
return this.sourceType.hashCode() * 31
+ this.targetType.hashCode();
}
// getter
public Class<?> getSourceType() { return this.sourceType; }
public Class<?> getTargetType() { return this.targetType; }
}5.2 精确匹配 + 父类/接口匹配
java
// GenericConversionService.java
@Nullable
protected GenericConverter findConverter(
@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
// 1. 获取所有注册的 GenericConverter
List<GenericConverter> converters = getRegisteredConverters();
// 2. 精确匹配:sourceType 和 targetType 完全一致
for (GenericConverter converter : converters) {
Set<ConvertiblePair> pairs = converter.getConvertibleTypes();
if (pairs != null) {
for (ConvertiblePair pair : pairs) {
if (pair.getSourceType() == sourceType.getType()
&& pair.getTargetType() == targetType.getType()) {
return converter; // 精确匹配
}
}
}
}
// 3. 父类/接口匹配:sourceType 是 pair.sourceType 的子类
// targetType 是 pair.targetType 的子类
for (GenericConverter converter : converters) {
Set<ConvertiblePair> pairs = converter.getConvertibleTypes();
if (pairs != null) {
for (ConvertiblePair pair : pairs) {
// 检查是否可以赋值
if (pair.getSourceType().isAssignableFrom(sourceType.getType())
&& pair.getTargetType().isAssignableFrom(targetType.getType())) {
return converter;
}
}
}
}
// 4. 条件匹配(ConditionalGenericConverter)
for (GenericConverter converter : converters) {
if (converter instanceof ConditionalGenericConverter) {
if (((ConditionalGenericConverter) converter)
.matches(sourceType, targetType)) {
return converter;
}
}
}
return null; // 找不到可用的转换器
}5.3 匹配优先级
优先级
│
├─ 1. 精确匹配 (exact match)
│ ConvertiblePair(String.class, Integer.class)
│ ↓ source = String.class, target = Integer.class
│
├─ 2. 父类/接口匹配 (assignable match)
│ ConvertiblePair(Number.class, String.class)
│ ↓ source = Integer.class (是 Number 的子类), target = String.class
│
└─ 3. 条件匹配 (conditional match)
ConditionalGenericConverter.matches(sourceType, targetType)
↓ 动态判断6. Converter vs ConverterFactory vs GenericConverter
6.1 三种转换器接口
java
// Converter —— 单类型转换(S → T)
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
// ConverterFactory —— 同一源类型到一族目标类型
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
// GenericConverter —— 通用类型转换(源 + 目标都灵活)
public interface GenericConverter {
@Nullable
Set<ConvertiblePair> getConvertibleTypes();
@Nullable
Object convert(@Nullable Object source,
TypeDescriptor sourceType,
TypeDescriptor targetType);
}6.2 三种转换器对比
| 特性 | Converter<S, T> | ConverterFactory<S, R> | GenericConverter |
|---|---|---|---|
| 源类型 | 固定 S | 固定 S | 灵活(ConvertiblePair 指定) |
| 目标类型 | 固定 T | 家族类型 R(多个子类型) | 灵活 |
| 使用场景 | String → Integer | String → Enum(任意枚举) | 集合/数组转换 |
| 注册数量 | 20+ | ~5 | ~15 |
6.3 示例对比
java
// Converter:String → Boolean(单一目标)
public class StringToBooleanConverter implements Converter<String, Boolean> {
@Override
public Boolean convert(String source) {
return "true".equalsIgnoreCase(source)
|| "yes".equalsIgnoreCase(source)
|| "1".equals(source);
}
}
// ConverterFactory:String → Enum(任意枚举)
public class StringToEnumConverterFactory
implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private static class StringToEnumConverter<T extends Enum>
implements Converter<String, T> {
private final Class<T> enumType;
StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
@Override
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
// GenericConverter:Array → Collection(任意对象数组 → 任意集合类型)
public class ArrayToCollectionConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
// 支持任意类型 → Collection 子类型
return Collections.singleton(
new ConvertiblePair(Object[].class, Collection.class));
}
@Override
public Object convert(@Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) return null;
// 获取目标集合类型
Class<?> collectionType = targetType.getType();
// 创建集合实例
Collection<Object> result = CollectionFactory
.createCollection(collectionType, sourceType.getType(), ((Object[]) source).length);
// 逐个元素转换并添加
for (Object element : (Object[]) source) {
result.add(element);
}
return result;
}
}7. ConditionalGenericConverter 的条件判断
7.1 接口定义
java
// ConditionalGenericConverter.java
public interface ConditionalGenericConverter
extends GenericConverter, ConditionalConverter {
/**
* 判断此转换器是否可以处理 sourceType → targetType 的转换。
* 在 getConvertibleTypes() 匹配后触发,用于动态条件筛选。
*/
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}7.2 典型实现:IdToEntityConverter
java
// IdToEntityConverter.java —— Spring Data 中的经典示例
public class IdToEntityConverter implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
// 匹配所有类型 → 实体类型的转换
return Collections.singleton(
new ConvertiblePair(Object.class, Object.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// 动态判断条件:
// 1. 源类型是 ID 类型(String、Long 等)
// 2. 目标类型是 JPA 实体(有 @Entity 注解)
return sourceType.getType() != null
&& targetType.getType() != null
// 检查目标类型是否有关联的 Repository
&& isEntityType(targetType.getType());
}
@Override
public Object convert(@Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
// 通过 Repository 根据 ID 加载实体
if (source == null) return null;
return entityManager.find(targetType.getType(), source);
}
}7.3 在转换器注册中的使用
java
// GenericConversionService.addConverter()
@Override
public void addConverter(GenericConverter converter) {
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
if (convertibleTypes != null) {
for (ConvertiblePair pair : convertibleTypes) {
// 将转换器注册到对应的类型对
getConverterMap(pair.getSourceType(), pair.getTargetType())
.add(converter);
}
}
}
// 查找时:先匹配 ConvertiblePair,再执行 matches()
@Nullable
private GenericConverter getConditionalConverter(
TypeDescriptor sourceType, TypeDescriptor targetType) {
// 遍历所有条件转换器
for (GenericConverter converter : this.conditionalConverters) {
ConditionalGenericConverter conditional =
(ConditionalGenericConverter) converter;
// 先检查是否能处理该类型对
Set<ConvertiblePair> pairs = converter.getConvertibleTypes();
if (pairs != null) {
for (ConvertiblePair pair : pairs) {
if (pair.getSourceType().isAssignableFrom(sourceType.getType())
&& pair.getTargetType().isAssignableFrom(targetType.getType())) {
// 再执行动态条件判断
if (conditional.matches(sourceType, targetType)) {
return converter; // 满足条件
}
}
}
}
}
return null;
}8. TypeConverterDelegate 在 Bean 属性赋值中的角色
8.1 BeanWrapperImpl 中的使用
java
// BeanPropertyValue.applyValue() 中的转换链
// AbstractNestablePropertyAccessor(BeanWrapperImpl 的父类)
protected Object convertIfNecessary(String propertyName, Object oldValue,
Object newValue, Class<?> requiredType, TypeDescriptor td)
throws TypeMismatchException {
// 委托给 TypeConverterDelegate 执行转换
return this.typeConverterDelegate.convertIfNecessary(
propertyName, oldValue, newValue, requiredType, td);
}8.2 TypeConverterDelegate 源码
java
// TypeConverterDelegate.java
public class TypeConverterDelegate {
private final PropertyEditorRegistrySupport propertyEditorRegistry;
private final ConversionService conversionService;
public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) {
this.propertyEditorRegistry = propertyEditorRegistry;
this.conversionService = propertyEditorRegistry.getConversionService();
}
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName,
@Nullable Object oldValue, @Nullable Object newValue,
Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor)
throws TypeMismatchException {
// 类型为空或类型兼容 → 直接返回
if (requiredType == null
|| (newValue != null && requiredType.isInstance(newValue))) {
return (T) newValue;
}
// 1. 先查找自定义 PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry
.findCustomEditor(requiredType, propertyName);
if (editor != null) {
editor.setValue(newValue);
Object convertedValue = editor.getValue();
if (convertedValue != null) {
return (T) convertedValue;
}
}
// 2. 使用 ConversionService 进行类型转换
if (this.conversionService != null) {
try {
// 转换
Object convertedValue = this.conversionService.convert(
newValue,
typeDescriptor != null ? typeDescriptor
: TypeDescriptor.forObject(newValue),
TypeDescriptor.valueOf(requiredType));
if (convertedValue != null) {
return (T) convertedValue;
}
} catch (ConversionException ex) {
throw new TypeMismatchException(...);
}
}
// 3. 兜底:使用 Java Beans 的 PropertyEditor
if (newValue instanceof String) {
PropertyEditor editor = this.propertyEditorRegistry
.getDefaultEditor(requiredType);
if (editor == null) {
editor = PropertyEditorManager.findEditor(requiredType);
}
if (editor != null) {
editor.setAsText((String) newValue);
return (T) editor.getValue();
}
}
// 无法转换 → 抛出异常
throw new TypeMismatchException(...);
}
}8.3 转换优先级
Bean 属性绑定 (如 @Value、populateBean)
│
├─ 1. 类型兼容 → 直接返回
│
├─ 2. 自定义 PropertyEditor(@InitBinder 注册)
│
├─ 3. ConversionService(Spring 类型转换体系)
│ ├─ DefaultConversionService(80+ 个内置转换器)
│ └─ ApplicationConversionService(追加 Duration/DataSize 等)
│
└─ 4. 标准 Java Beans PropertyEditor(兜底)
└─ PropertyEditorManager.findEditor()9. PropertyEditorRegistry 与 PropertyEditor 的兼容
9.1 两套体系的兼容
Spring ConversionService(推荐)
├─ Converter<S, T> ← 类型安全、函数式
├─ ConverterFactory<S, R> ← 批量类型转换
├─ GenericConverter ← 灵活的类型转换
└─ ConditionalGenericConverter ← 条件判断
Java Beans PropertyEditor(遗留兼容)
└─ PropertyEditor
├─ setAsText(String) ← 字符串 → 对象
├─ getAsText() ← 对象 → 字符串
└─ getValue() / setValue()9.2 PropertyEditorRegistrySupport 的兜底逻辑
java
// PropertyEditorRegistrySupport.java
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
// 默认 PropertyEditor 缓存
private Map<Class<?>, PropertyEditor> defaultEditors;
// 获取默认的 PropertyEditor
@Nullable
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
// 懒加载默认 PropertyEditor
if (this.defaultEditors == null) {
createDefaultEditors();
}
return this.defaultEditors.get(requiredType);
}
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// 注册 Java Beans 标准 PropertyEditor
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(URL.class, new URLEditor());
// ... 更多默认 PropertyEditor
}
}9.3 兼容流程
java
// TypeConverterDelegate 中的兼容处理
public <T> T convertIfNecessary(...) {
// 第 1 步:检查 ConversionService
if (this.conversionService.canConvert(
TypeDescriptor.forObject(newValue),
TypeDescriptor.valueOf(requiredType))) {
// 使用 ConversionService 转换
return (T) this.conversionService.convert(newValue, ...);
}
// 第 2 步:ConversionService 不可用 → 回退到 PropertyEditor
PropertyEditor editor = getPropertyEditor(requiredType);
if (editor != null) {
if (newValue instanceof String) {
editor.setAsText((String) newValue);
} else {
editor.setValue(newValue);
}
return (T) editor.getValue();
}
// 第 3 步:两者都不可用 → 抛出异常
throw new TypeMismatchException(...);
}10. Duration / DataSize / Period 特殊类型转换
10.1 三种特殊类型的使用
yaml
# application.yml
spring:
cache:
ttl: 10s # Duration
max-size: 10MB # DataSize
task:
scheduling:
pool:
size: 4
shutdown:
await-termination: 30s # Duration
servlet:
multipart:
max-file-size: 10MB # DataSize
max-request-size: 100MB # DataSize
datasource:
hikari:
connection-timeout: 30000 # Duration(数值 = 毫秒)
maximum-pool-size: 2010.2 StringToDurationConverter 源码
java
// StringToDurationConverter.java
public class StringToDurationConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(String.class, Duration.class));
}
@Override
public Object convert(@Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) return null;
String value = (String) source;
// 使用 DurationStyle 解析字符串
return DurationStyle.detectAndParse(value);
}
}10.3 DurationStyle 的解析逻辑
java
// DurationStyle.java (Spring Boot 内部类)
public enum DurationStyle {
// ISO-8601 格式: PT10S (10 秒)
ISO {
@Override
public Duration parse(String value) {
return Duration.parse(value);
}
},
// 简易格式: 10s (10 秒), 5m (5 分钟), 2h (2 小时), 3d (3 天)
SIMPLE {
@Override
public Duration parse(String value) {
// 提取数值部分
String numberPart = value.replaceAll("[^\\d.-]", "");
// 提取单位部分
String unitPart = value.replaceAll("[\\d.-]", "").trim();
long amount = Long.parseLong(numberPart);
switch (unitPart) {
case "ns": return Duration.ofNanos(amount);
case "us": return Duration.ofNanos(amount * 1000);
case "ms": return Duration.ofMillis(amount);
case "s": return Duration.ofSeconds(amount);
case "m": return Duration.ofMinutes(amount);
case "h": return Duration.ofHours(amount);
case "d": return Duration.ofDays(amount);
default:
throw new IllegalArgumentException(...);
}
}
};
// 自动检测格式
public static Duration detectAndParse(String value) {
if (value.startsWith("PT") || value.startsWith("P")) {
return ISO.parse(value);
}
return SIMPLE.parse(value);
}
}10.4 Duration 支持的格式
| 字符串 | 含义 | 转换结果 |
|---|---|---|
"10s" | 10 秒 | Duration.ofSeconds(10) |
"5m" | 5 分钟 | Duration.ofMinutes(5) |
"2h" | 2 小时 | Duration.ofHours(2) |
"3d" | 3 天 | Duration.ofDays(3) |
"500ms" | 500 毫秒 | Duration.ofMillis(500) |
"PT10S" | ISO-8601 10 秒 | Duration.parse("PT10S") |
10.5 DataSize 支持的格式
java
// StringToDataSizeConverter.java
public class StringToDataSizeConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(String.class, DataSize.class));
}
@Override
public Object convert(@Nullable Object source,
TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) return null;
String value = (String) source;
// 使用 DataSize.parse() 解析
return DataSize.parse(value);
}
}| 字符串 | 含义 | 转换结果 |
|---|---|---|
"10B" | 10 字节 | DataSize.ofBytes(10) |
"500KB" | 500 千字节 | DataSize.ofKilobytes(500) |
"10MB" | 10 兆字节 | DataSize.ofMegabytes(10) |
"1GB" | 1 吉字节 | DataSize.ofGigabytes(1) |
"2TB" | 2 太字节 | DataSize.ofTerabytes(2) |
10.6 数值到 Duration/DataSize
java
// NumberToDurationConverter
cs.convert(3600, Duration.class); // Duration.ofSeconds(3600)
cs.convert(30000, Duration.class); // Duration.ofMillis(30000)
// NumberToDataSizeConverter
cs.convert(1024, DataSize.class); // DataSize.ofBytes(1024)10.7 在 @ConfigurationProperties 中的便捷使用
java
@ConfigurationProperties(prefix = "app.cache")
public class CacheProperties {
// 在 application.yml 中直接写:
// app.cache.ttl=10s
// app.cache.max-size=10MB
private Duration ttl = Duration.ofMinutes(5); // 默认 5 分钟
private DataSize maxSize = DataSize.ofMegabytes(100); // 默认 100MB
// getter / setter
}总结
| # | 细节点 | 核心要点 |
|---|---|---|
| ① | canConvert() 实现 | GenericConversionService.getConverter() → 查找匹配的 Converter,ConcurrentHashMap 缓存 |
| ② | DefaultConversionService 80+ 转换器 | String→基本类型、数值→数值、String→Enum、集合/数组、对象→Optional 等 |
| ③ | ApplicationConversionService Boot 扩展 | 追加 StringToDurationConverter、StringToDataSizeConverter、StringToPeriodConverter |
| ④ | ConvertiblePair 匹配 | 精确匹配 → 父类/接口匹配 → 条件匹配 三级优先 |
| ⑤ | Converter/ConverterFactory/GenericConverter | 单类型 / 源到家族 / 通用转换 三层体系 |
| ⑥ | ConditionalGenericConverter | matches(sourceType, targetType) 动态条件判断 |
| ⑦ | TypeConverterDelegate 角色 | Bean 属性赋值时按 PropertyEditor → ConversionService → 标准 PropertyEditor 三级兜底 |
| ⑧ | PropertyEditor 兼容 | PropertyEditorRegistrySupport 作为 ConversionService 不可用时的兜底方案 |
| ⑨ | Duration/DataSize 特殊类型 | "10s"、"10MB" 等便捷写法,DurationStyle / DataSize.parse() 解析 |