内建 80+ 类型转换器族解析
概述
Spring 的 DefaultConversionService 注册了 80+ 个内建类型转换器(Converter、ConverterFactory、GenericConverter),覆盖了从字符串到基本类型、枚举、集合、特殊类型(Duration、DataSize、Period、UUID 等)的所有常见转换场景。
本文以转换器族为线索,深入拆解 10 个最具代表性的转换器的实现细节。
本文基于 Spring Framework 6.x + Spring Boot 3.x 源码分析。
1. StringToIntegerConverter.matches() 的条件
1.1 源码
java
// StringToIntegerConverter.java
final class StringToIntegerConverter implements Converter<String, Integer> {
// 使用 NumberUtils 进行转换
// 支持十进制、八进制(0 开头)、十六进制(0x 开头)
@Override
public Integer convert(String source) {
// 去除首尾空白
String value = source.trim();
// 检查是否为空
if (value.isEmpty()) {
return null;
}
// 使用 NumberUtils 解析
// 支持: "123"、"-456"、"+789"、""0xFF"、""071"
return NumberUtils.parseNumber(value, Integer.class);
}
}1.2 matches() 在 ConditionalGenericConverter 中的作用
java
// 注意: StringToIntegerConverter 实现的是 Converter<S, T> 接口
// 不是 ConditionalGenericConverter
// 所以它的 matches() 不是显式实现的,而是通过 GenericConversionService.getConverter() 查找
// 查找逻辑:
// GenericConversionService.getConverter(sourceType, targetType)
// → 从 converterCache 查找 ConvertiblePair(Integer.class, String.class)
// → 如果缓存命中 → 使用缓存的转换器
// → 如果缓存未命中 → 遍历所有注册的 Converter
// → 检查 ConvertiblePair(Integer.class, String.class)
// → 精确匹配 → 返回 StringToIntegerConverter
// 如果使用的是 ConditionalGenericConverter,则:
public class MyConditionalConverter implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// 动态条件判断
// 如果条件不满足 → 跳过此转换器
// 例如: 只在特定 targetType 下生效
return String.class == sourceType.getObjectType()
&& Number.class.isAssignableFrom(targetType.getObjectType());
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(String.class, Number.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
// 实际的转换逻辑
}
}
// DefaultConversionService 中不包含条件式的 String → Integer 转换
// 而是直接通过精确类型匹配实现1.3 支持的整数转换器对照表
| 转换器 | 源类型 | 目标类型 | 格式支持 |
|---|---|---|---|
StringToIntegerConverter | String | Integer | 十进制、八进制(0)、十六进制(0x) |
StringToLongConverter | String | Long | 同上 |
StringToShortConverter | String | Short | 同上 |
StringToByteConverter | String | Byte | 同上 |
StringToAtomicIntegerConverter | String | AtomicInteger | 仅十进制 |
NumberToNumberConverterFactory | Number | Number | 兼容所有 Number 子类型 |
2. StringToBooleanConverter 的 10+ 个 true/false 字符串
2.1 源码
java
// StringToBooleanConverter.java
final class StringToBooleanConverter implements Converter<String, Boolean> {
// 被认为是 true 的字符串(不区分大小写)
private static final Set<String> TRUE_VALUES = Set.of(
"true", // 标准
"yes", // 配置文件中常用
"1", // Unix 约定
"on" // HTML 表单提交值
);
// 被认为是 false 的字符串(不区分大小写)
private static final Set<String> FALSE_VALUES = Set.of(
"false", // 标准
"no", // 配置文件
"0", // Unix 约定
"off" // HTML 表单
);
@Override
@Nullable
public Boolean convert(String source) {
String value = source.trim();
if (value.isEmpty()) {
return null;
}
// 不区分大小写匹配
value = value.toLowerCase();
if (TRUE_VALUES.contains(value)) {
return Boolean.TRUE;
}
if (FALSE_VALUES.contains(value)) {
return Boolean.FALSE;
}
// 无法识别 → 抛出异常
throw new IllegalArgumentException(
"Invalid boolean value '" + source + "'");
}
}2.2 支持的字符串对照表
java
// TRUE 值(大小写不敏感)
// "true" → Boolean.TRUE
// "TRUE" → Boolean.TRUE
// "True" → Boolean.TRUE
// "yes" → Boolean.TRUE
// "YES" → Boolean.TRUE
// "Y" → Boolean.TRUE ※ 注意: StringToBooleanConverter 不支持 "Y"
// "1" → Boolean.TRUE
// "on" → Boolean.TRUE
// "ON" → Boolean.TRUE
// FALSE 值(大小写不敏感)
// "false" → Boolean.FALSE
// "FALSE" → Boolean.FALSE
// "no" → Boolean.FALSE
// "NO" → Boolean.FALSE
// "0" → Boolean.FALSE
// "off" → Boolean.FALSE
// "OFF" → Boolean.FALSE2.3 在其他转换场景中
java
// 在 yaml 配置中:
// 这些字符串都可以被正确转换为 Boolean
app:
enabled: true
audit-log: yes
use-cache: 1
debug-mode: on
maintenance: false
dry-run: no
read-only: 0
ssl-enabled: off3. StringToEnumConverterFactory 的泛型工厂
3.1 源码
java
// StringToEnumConverterFactory.java
final class StringToEnumConverterFactory
implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(
Class<T> targetType) {
// 为每个枚举类型创建专属的 Converter
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;
}
@Override
@Nullable
public T convert(String source) {
String value = source.trim();
if (value.isEmpty()) {
return null;
}
// 使用 EnumUtils 将字符串转换为枚举常量
// 转换策略:
// 1. 精确匹配: "ACTIVE" → Enum.valueOf(Status.ACTIVE)
// 2. 不区分大小写: "active" → Status.ACTIVE
// 3. 宽松匹配: "active_mode" → Status.ACTIVE_MODE(如果存在)
return EnumUtils.valueOf(this.enumType, value.trim());
}
}
}3.2 EnumUtils.valueOf() 的实现
java
// EnumUtils.java
public static <T extends Enum<T>> T valueOf(
Class<T> enumType, String name) {
// 1. 精确匹配(大小写敏感)
try {
return Enum.valueOf(enumType, name);
} catch (IllegalArgumentException ex) {
// 继续尝试其他匹配方式
}
// 2. 不区分大小写匹配
for (T constant : enumType.getEnumConstants()) {
if (constant.name().equalsIgnoreCase(name)) {
return constant;
}
}
// 3. 尝试去除下划线/连接符后匹配
// 例如: "ACTIVE_MODE" 匹配 "activemode"、"active-mode"
String normalizedName = name.replace("-", "_");
for (T constant : enumType.getEnumConstants()) {
if (constant.name().equalsIgnoreCase(normalizedName)) {
return constant;
}
}
// 4. 所有匹配都失败
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}3.3 使用示例
java
// 定义枚举
public enum Status {
ACTIVE,
INACTIVE,
PENDING
}
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
DELIVERED,
CANCELLED
}
// 自动转换示例:
// yml 配置:
// app.status: active
// app.order-status: paid
// Spring Boot 自动通过 StringToEnumConverterFactory 转换
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Status status; // "active" → Status.ACTIVE
private OrderStatus orderStatus; // "paid" → OrderStatus.PAID
}
// `@Value` 中的转换:
@Value("${app.status}")
private Status status; // yml 中写 "active" 自动转换为 Status.ACTIVE4. NumberToNumberConverterFactory 的数值范围处理
4.1 源码
java
// NumberToNumberConverterFactory.java
final class NumberToNumberConverterFactory
implements ConverterFactory<Number, Number> {
@Override
public <T extends Number> Converter<Number, T> getConverter(
Class<T> targetType) {
return new NumberToNumber<>(targetType);
}
private static final class NumberToNumber<T extends Number>
implements Converter<Number, T> {
private final Class<T> targetType;
NumberToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
@Nullable
public T convert(Number source) {
// 使用 NumberUtils 进行安全的数值类型转换
// 包括溢出检查和精度处理
// Integer → Long: 不会丢失精度
// Long → Integer: 检查是否在 Integer 范围内,超出则抛出异常
// Float → Double: 完全兼容
// Double → Float: 可能丢失精度,使用 floatValue() 截断
return NumberUtils.convertNumberToTargetClass(
source, this.targetType);
}
}
}4.2 NumberUtils.convertNumberToTargetClass() 的转换规则
java
// NumberUtils.java
public static <T extends Number> T convertNumberToTargetClass(
Number number, Class<T> targetClass) throws IllegalArgumentException {
// 空值处理
if (number == null) {
return null;
}
// 如果已经是目标类型,直接返回
if (targetClass.isInstance(number)) {
return (T) number;
}
// Byte
if (Byte.class == targetClass) {
long value = checkedLongValue(number, targetClass);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Byte.valueOf(number.byteValue());
}
// Short
if (Short.class == targetClass) {
long value = checkedLongValue(number, targetClass);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Short.valueOf(number.shortValue());
}
// Integer
if (Integer.class == targetClass) {
long value = checkedLongValue(number, targetClass);
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Integer.valueOf(number.intValue());
}
// Long(安全,不会溢出)
if (Long.class == targetClass) {
return (T) Long.valueOf(number.longValue());
}
// BigInteger
if (BigInteger.class == targetClass) {
if (number instanceof BigDecimal) {
return (T) ((BigDecimal) number).toBigInteger();
}
return (T) BigInteger.valueOf(number.longValue());
}
// Float(可能有精度损失)
if (Float.class == targetClass) {
return (T) Float.valueOf(number.floatValue());
}
// BigDecimal(安全)
if (BigDecimal.class == targetClass) {
return (T) BigDecimal.valueOf(number.doubleValue());
}
// Double(安全)
if (Double.class == targetClass) {
return (T) Double.valueOf(number.doubleValue());
}
throw new IllegalArgumentException(
"Could not convert number [" + number + "] to target class ["
+ targetClass.getName() + "]");
}4.3 转换对照表
| 源类型 | 目标类型 | 是否安全 | 说明 |
|---|---|---|---|
| Integer | Long | ✅ 安全 | 100 → 100L,无精度损失 |
| Long | Integer | ⚠️ 溢出检查 | 9999999999L → 抛出异常 |
| Integer | Float | ⚠️ 精度损失 | 123456789 → 1.23456792E8 |
| Float | Integer | ⚠️ 精度+溢出 | 3.14f → 3(截断小数) |
| Integer | BigDecimal | ✅ 安全 | 100 → 100.0 |
| BigDecimal | Integer | ⚠️ 精度损失 | 100.99 → 100(截断小数) |
| AtomicInteger | Integer | ✅ 安全 | new AtomicInteger(5) → 5 |
| Double | Float | ⚠️ 精度损失 | 3.1415926535 → 3.1415927 |
5. StringToDurationConverter 解析 10s / 5m / 2h
5.1 源码
java
// StringToDurationConverter.java
final class StringToDurationConverter
implements Converter<String, Duration> {
@Override
@Nullable
public Duration convert(String source) {
String value = source.trim();
if (value.isEmpty()) {
return null;
}
// 使用 DurationStyle 检测并解析
// 支持两种格式:
// ISO-8601: "PT10S"、"P2D"、"PT5M"
// 简洁格式: "10s"、"5m"、"2h"、"3d"
return DurationStyle.detectAndParse(value);
}
}5.2 DurationStyle.detectAndParse() 的实现
java
// DurationStyle.java
public static Duration detectAndParse(String value) {
// 1. 检测格式类型
// ISO-8601 格式以 "P" 或 "PT" 开头
if (value.startsWith("P") || value.startsWith("p")) {
// ISO-8601 格式: PT10S, P2D, PT5M
return parseIso8601(value);
}
// 简洁格式: 10s, 5m, 2h, 3d
return parseSimple(value);
}
// ISO-8601 格式解析
private static Duration parseIso8601(String value) {
// 标准格式: PT10S (10秒), P2D (2天), PT5M30S (5分30秒)
// java.time.Duration.parse() 原生支持此格式
//
// 兼容格式(不区分大小写):
return Duration.parse(value.toUpperCase());
}
// 简洁格式解析
private static Duration parseSimple(String value) {
// 正则匹配: (\d+)([smhd])
// 支持后缀:
// s / sec / second / seconds → 秒
// m / min / minute / minutes → 分
// h / hr / hour / hours → 时
// d / day / days → 天
// ns / nano / nanos → 纳秒(Spring Boot 3.x)
// ms / milli / millis → 毫秒
// 示例:
// "10s" → Duration.ofSeconds(10)
// "5m" → Duration.ofMinutes(5)
// "2h" → Duration.ofHours(2)
// "3d" → Duration.ofDays(3)
// "500ms" → Duration.ofMillis(500)
// "100ns" → Duration.ofNanos(100)
// "30s" → Duration.ofSeconds(30)
// 获取数值和单位
char suffix = value.charAt(value.length() - 1);
long amount = Long.parseLong(
value.substring(0, value.length() - 1));
switch (Character.toLowerCase(suffix)) {
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(
"Unsupported duration suffix: " + suffix);
}
}5.3 支持的格式总览
yaml
# 在配置中的使用:
spring:
task:
execution:
pool:
keep-alive: 60s # 60 秒
servlet:
multipart:
max-file-size: 10MB # DataSize
session:
timeout: 30m # 30 分钟
# 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))
# 100ns → 100纳秒 (Duration.ofNanos(100))
#
# ISO-8601 格式:
# PT10S → 10秒
# PT5M → 5分钟
# PT2H → 2小时
# P3D → 3天
# PT30M30S → 30分钟30秒6. StringToDataSizeConverter 解析 10MB / 1GB
6.1 源码
java
// StringToDataSizeConverter.java
final class StringToDataSizeConverter
implements Converter<String, DataSize> {
@Override
@Nullable
public DataSize convert(String source) {
String value = source.trim();
if (value.isEmpty()) {
return null;
}
// 使用 DataSize.parse() 解析
// 支持多种单位格式
return DataSize.parse(value);
}
}6.2 DataSize.parse() 的实现
java
// DataSize.java
public static DataSize parse(CharSequence text) {
// 1. 去掉首尾空白
String value = text.toString().trim();
// 2. 分离数值和单位
// 正则: (\d+)([BKMGTPE]?B?)
// "10MB" → amount=10, unit=MB
// "1GB" → amount=1, unit=GB
// "1024KB" → amount=1024, unit=KB
// 3. 单位转换(二进制标准 1024 进制)
// B → Bytes
// KB → Kilobytes (1024^1)
// MB → Megabytes (1024^2)
// GB → Gigabytes (1024^3)
// TB → Terabytes (1024^4)
// PB → Petabytes (1024^5)
// EB → Exabytes (1024^6)
// 4. 也支持十进制标准:
// kB → Kilobytes (1000^1)
// MB → Megabytes (1000^2) 注意: MB 默认是二进制
// MiB → Mebibytes (1024^2) 显式二进制
}6.3 单位对照表
yaml
# DataSize 支持的写法(二进制标准 1024):
# 1B → 1 byte
# 1KB → 1024 bytes
# 1MB → 1,048,576 bytes
# 1GB → 1,073,741,824 bytes
# 1TB → 1,099,511,627,776 bytes
# 在配置中的使用:
spring:
servlet:
multipart:
max-file-size: 10MB # 10 * 1024 * 1024 = 10,485,760 bytes
max-request-size: 100MB # 100 * 1024 * 1024 = 104,857,600 bytes
file-size-threshold: 256KB # 256 * 1024 = 262,144 bytes
codec:
max-in-memory-size: 512KB # 512 * 1024 = 524,288 bytes
jackson:
max-nesting-depth: 10 # 不是 DataSize7. ObjectToObjectConverter 的构造器/工厂方法
7.1 源码
java
// ObjectToObjectConverter.java
final class ObjectToObjectConverter
implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
// 不限定具体类型对
// 它适用于任何可以通过构造器或工厂方法进行转换的类型
return null; // null 表示任意源/目标类型
}
@Override
public boolean matches(TypeDescriptor sourceType,
TypeDescriptor targetType) {
Class<?> sourceClass = sourceType.getObjectType();
Class<?> targetClass = targetType.getObjectType();
// 1. 检查目标类是否有构造器接受源类型: new Target(source)
if (hasConstructor(sourceClass, targetClass)) {
return true;
}
// 2. 检查源类是否有工厂方法: source.toTarget()
if (hasValueMethod(sourceClass, targetClass)) {
return true;
}
// 3. 检查目标类是否有静态工厂方法:
// Target.valueOf(source) 或 Target.from(source)
if (hasStaticFactoryMethod(sourceClass, targetClass)) {
return true;
}
return false;
}
@Override
@Nullable
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
Class<?> sourceClass = sourceType.getObjectType();
Class<?> targetClass = targetType.getObjectType();
// 按优先级尝试:
// 1. 构造器: new TargetClass(source)
try {
Constructor<?> constructor = targetClass.getConstructor(sourceClass);
return constructor.newInstance(source);
} catch (NoSuchMethodException ex) {
// 继续尝试其他方式
}
// 2. 工厂方法: source.toTargetClass()
try {
Method valueOf = sourceClass.getMethod("to" + targetClass.getSimpleName());
return valueOf.invoke(source);
} catch (NoSuchMethodException ex) {
// 继续尝试其他方式
}
// 3. 静态工厂: TargetClass.valueOf(source) 或 TargetClass.from(source)
try {
Method valueOf = targetClass.getMethod("valueOf", sourceClass);
return valueOf.invoke(null, source);
} catch (NoSuchMethodException ex) {
// 继续
}
try {
Method from = targetClass.getMethod("from", sourceClass);
return from.invoke(null, source);
} catch (NoSuchMethodException ex) {
// 所有方式都失败
}
throw new ConverterNotFoundException(...);
}
}7.2 支持的转换示例
java
// 1. 构造器方式: new Target(source)
// String → File
File file = new File("/path/to/file.txt"); // File(String)
// String → URL
URL url = new URL("https://example.com"); // URL(String)
// Integer → String(String 的构造器)
String str = new String("123"); // 虽然不常用
// String → BigDecimal
BigDecimal bd = new BigDecimal("3.14"); // BigDecimal(String)
// 2. 工厂方法方式: source.toTarget()
// String → char[]
// 没有直接的方法,所以实际上 String → char[] 通过:
new String(new char[]{'a', 'b'}).toCharArray(); // ❌ 不是 ObjectToObjectConverter
// 3. 静态工厂方法方式: Target.valueOf(source)
// String → Integer
Integer num = Integer.valueOf("123"); // Integer.valueOf(String)
// String → Long
Long val = Long.valueOf("123"); // Long.valueOf(String)
// String → Boolean
Boolean bool = Boolean.valueOf("true"); // Boolean.valueOf(String)
// 4. 静态工厂方法方式: Target.from(source)
// String → LocalDate
LocalDate date = LocalDate.from(
DateTimeFormatter.ISO_DATE.parse("2026-07-25")); // TemporalAccessor → LocalDate
// String → Duration
Duration duration = Duration.parse("PT10S"); // CharSequence → Duration8. ArrayToCollectionConverter 的自动包装
8.1 源码
java
// ArrayToCollectionConverter.java
final class ArrayToCollectionConverter
implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(Object[].class, Collection.class));
}
@Override
public boolean matches(TypeDescriptor sourceType,
TypeDescriptor targetType) {
// 源类型必须是数组
if (!sourceType.isArray()) {
return false;
}
// 目标类型必须是 Collection(List、Set 等)
return Collection.class.isAssignableFrom(
targetType.getObjectType());
}
@Override
@Nullable
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
// 1. 将数组转换为数组
Object[] array = (Object[]) source;
// 2. 确定 Collection 的具体类型
// 默认: ArrayList、LinkedHashSet(根据目标类型)
Collection<Object> target = CollectionFactory.createCollection(
targetType.getType(), array.length);
// 3. 获取目标 Collection 的元素类型
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
// 4. 逐个元素转换
for (Object element : array) {
// 如果目标元素类型与源元素类型不同 → 逐个转换
// 例如: String[] → List<Integer>
// 每个 String 通过 StringToIntegerConverter 转换
Object converted = element;
if (elementDesc != null && element != null
&& !elementDesc.getObjectType().isInstance(element)) {
// 使用 ConversionService 进行元素级转换
converted = this.conversionService.convert(
element, sourceType.getElementTypeDescriptor(),
elementDesc);
}
target.add(converted);
}
return target;
}
}8.2 自动包装示例
java
// String[] → List<String>
// yml:
// app.tags: tag1,tag2,tag3
// app.tags: [tag1, tag2, tag3]
// 都会转换为 String[],然后通过 ArrayToCollectionConverter 转换为 List<String>
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private List<String> tags; // String[] → List<String>
private Set<String> keywords; // String[] → Set<String>
private List<Integer> counts; // String[] → List<Integer>(元素级转换)
}
// Spring MVC 中的使用:
@PostMapping("/batch")
public String batch(@RequestParam("ids") List<Integer> ids) {
// 请求: POST /batch?ids=1&ids=2&ids=3
// ids 参数以 String[] 收集
// ArrayToCollectionConverter 转换为 List<Integer>
// 每个元素由 StringToIntegerConverter 转换
return "OK";
}9. CollectionToArrayConverter 的拆包
9.1 源码
java
// CollectionToArrayConverter.java
final class CollectionToArrayConverter
implements ConditionalGenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(
new ConvertiblePair(Collection.class, Object[].class));
}
@Override
public boolean matches(TypeDescriptor sourceType,
TypeDescriptor targetType) {
// 源类型必须是 Collection
if (!Collection.class.isAssignableFrom(
sourceType.getObjectType())) {
return false;
}
// 目标类型必须是数组
return targetType.isArray();
}
@Override
@Nullable
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (source == null) {
return null;
}
Collection<?> sourceCollection = (Collection<?>) source;
// 1. 创建目标数组
// 获取数组的组件类型: int[], String[] 等
Class<?> arrayComponentType = targetType.getElementTypeDescriptor()
.getObjectType();
// 2. 使用 Array.newInstance() 创建数组
Object array = Array.newInstance(
arrayComponentType, sourceCollection.size());
// 3. 逐个元素拆包
// 如果目标元素类型是基本类型(int、long 等)
// 自动进行拆箱(Integer → int)
int i = 0;
for (Object element : sourceCollection) {
// 如果有必要,对每个元素进行类型转换
Object converted = element;
if (element != null &&
!arrayComponentType.isInstance(element)) {
converted = this.conversionService.convert(
element,
sourceType.getElementTypeDescriptor(),
targetType.getElementTypeDescriptor());
}
Array.set(array, i++, converted);
}
return array;
}
}9.2 自动拆包示例
java
// List<Integer> → int[]
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private int[] scores; // Spring Boot 内部处理,但 ConversionService 支持
}
// Spring MVC 中的使用:
@GetMapping("/batch")
public String batch(@RequestParam("ids") int[] ids) {
// 请求: GET /batch?ids=1,2,3
// 参数以 String[] 收集
// CollectionToArrayConverter 转换为 int[]
return "OK";
}
// Jackson 反序列化:
// JSON: {"values": [1, 2, 3]}
// List<Integer> → int[] 自动拆包9.3 自动装箱/拆箱支持
| 源类型 | 目标类型 | 是否支持 | 说明 |
|---|---|---|---|
List<Integer> | int[] | ✅ | 自动拆箱 Integer → int |
List<Long> | long[] | ✅ | 自动拆箱 Long → long |
List<Boolean> | boolean[] | ✅ | 自动拆箱 Boolean → boolean |
List<Double> | double[] | ✅ | 自动拆箱 Double → double |
int[] | List<Integer> | ✅ | 自动装箱 int → Integer |
String[] | List<String> | ✅ | 无需转换,直接映射 |
Integer[] | int[] | ✅ | 通过 ArrayToCollectionConverter + CollectionToArrayConverter |
10. StringToUUIDConverter 的 3 种 UUID 格式兼容
10.1 源码
java
// StringToUUIDConverter.java
final class StringToUUIDConverter implements Converter<String, UUID> {
@Override
@Nullable
public UUID convert(String source) {
String value = source.trim();
if (value.isEmpty()) {
return null;
}
// 使用 UUID.fromString() 解析 UUID
// 内部调用 java.util.UUID 的标准解析
return UUID.fromString(value);
}
}10.2 支持的 UUID 格式
java
// 格式 1: 标准 UUID
UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
// 格式: 8-4-4-4-12
// 时间戳-版本-变体-序列号
// 格式 2: 不带连字符的 32 位十六进制
// 注意: UUID.fromString() 原生不支持不带连字符的格式
// 需要手动添加连字符或者通过其他方式转换
public static UUID fromStringWithoutHyphens(String source) {
// source: "550e8400e29b41d4a716446655440000"
return UUID.fromString(
source.substring(0, 8) + "-" +
source.substring(8, 12) + "-" +
source.substring(12, 16) + "-" +
source.substring(16, 20) + "-" +
source.substring(20)
);
}
// 格式 3: 带花括号的 UUID
// {550e8400-e29b-41d4-a716-446655440000}
// UUID.fromString() 也支持此格式
UUID uuid = UUID.fromString("{550e8400-e29b-41d4-a716-446655440000}");10.3 其他 String → X 转换器对照表
java
// StringToCharsetConverter
String source = "UTF-8"; // → Charset.forName("UTF-8")
String source = "ISO-8859-1"; // → Charset.forName("ISO-8859-1")
// StringToCurrencyConverter
String source = "CNY"; // → Currency.getInstance("CNY")
String source = "USD"; // → Currency.getInstance("USD")
String source = "EUR"; // → Currency.getInstance("EUR")
// StringToLocaleConverter
String source = "zh_CN"; // → Locale("zh", "CN")
String source = "en_US"; // → Locale("en", "US")
String source = "ja_JP_JP"; // → Locale("ja", "JP", "JP")
// StringToPropertiesConverter
String source = "key1=value1\nkey2=value2"; // → Properties
// 使用 Properties.load(InputStream) 解析
// StringToUUIDConverter
String source = "550e8400-e29b-41d4-a716-446655440000"; // → UUID
// StringToPatternConverter
String source = "\\d{3}-\\d{4}"; // → Pattern.compile(source)
// StringToInetAddressConverter
String source = "192.168.1.1"; // → InetAddress.getByName(source)
String source = "localhost"; // → InetAddress.getByName(source)
// StringToZoneIdConverter
String source = "Asia/Shanghai"; // → ZoneId.of("Asia/Shanghai")
String source = "UTC+8"; // → ZoneId.of("UTC+8")总结
| # | 转换器 | 核心要点 |
|---|---|---|
| ① | StringToIntegerConverter | 支持十进制、八进制(0)、十六进制(0x) 格式 |
| ② | StringToBooleanConverter | 8 个 true 值("true"/"yes"/"1"/"on") 和 8 个 false 值("false"/"no"/"0"/"off"),不区分大小写 |
| ③ | StringToEnumConverterFactory | 泛型工厂,每个枚举类型创建专属 Converter,支持精确/大小写不敏感/宽松匹配 |
| ④ | NumberToNumberConverterFactory | 安全转换(溢出检查),Long→Integer 超范围抛异常 |
| ⑤ | StringToDurationConverter | 简洁格式(10s)/ISO-8601(PT10S),支持 s/m/h/d/ms/ns 等后缀 |
| ⑥ | StringToDataSizeConverter | 二进制标准(10MB),支持 B/KB/MB/GB/TB 单位 |
| ⑦ | ObjectToObjectConverter | 通过构造器(new Target(src))、工厂方法(src.toTarget())、静态工厂(Target.valueOf(src)) 实现通用转换 |
| ⑧ | ArrayToCollectionConverter | String[] → List<String> 自动包装,支持元素级类型转换 |
| ⑨ | CollectionToArrayConverter | List<Integer> → int[] 自动拆包,支持基本类型数组 |
| ⑩ | StringToUUIDConverter | UUID.fromString() 解析,支持标准格式和带花括号格式 |