bigywater.tech
Spring · 15

类型转换:ConversionService、Formatter 与 PropertyEditor

字符串怎么变成 int、枚举、List、日期?自己的类型怎么注册转换器?绑定表单时写错了会怎样?旧的 PropertyEditor 还有用吗?这篇在 Spring Framework 7.0.9 上逐项验证。

5 个
全部写错的表单输入产生的绑定错误数(都是 typeMismatch)
PropertyEditor 优先
同时有 PropertyEditor 和 ConversionService 时,绑定结果是 PropertyEditor 的 Point(-1,-1)
ConverterNotFoundException
DefaultConversionService 把 "PT1M30S" 或 "2026-10-19" 转成 Duration / LocalDate 时的错误

默认转什么

实测 · 本机执行输出
  "42"     → Integer        Integer:42
  "true"   → Boolean        Boolean:true
  "yes"    → Boolean        Boolean:true
  "RED"    → Color          Color:RED
  "red"    → Color          ✗ ConversionFailedException: Failed to convert from type [java.lang.String] to type [demo.Main$Color] for value [red]
  "1,2,3"  → int[]          int[][1, 2, 3]
  "1,2,3"  → List<Integer>  ArrayList:[1, 2, 3]
  "PT1M30S" → Duration       ✗ ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.time.Duration]
  "30s"    → Duration       ✗ ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.time.Duration]
  "2026-10-19" → LocalDate      ✗ ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.time.LocalDate]
  "3.14"   → BigDecimal     BigDecimal:3.14
  "a"      → Character      Character:a
  "zh-CN"  → Locale         Locale:zh_CN
  "1,2"    → Point          ✗ ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [demo.Main$Point]

基本类型、枚举、数组与集合开箱即用;Duration、LocalDate 要用带格式化的 ConversionService。

Converter 与 Formatter

实测 · 本机执行输出
  注册 Converter<String, Point> 后 convert("3, 4", Point) → Point[x=3, y=4];canConvert(String→Point) = true;canConvert(Point→String) = false
  Point → String(没有反向转换器):ConverterNotFoundException: No converter found capable of converting from type [demo.Main$Point] to type [java.lang.String]
  注册 Formatter<Point> 后:"(5;6)" → Point[x=5, y=6];Point(5,6) → String:(5;6)(一个 Formatter 同时注册了两个方向)
  没有注册任何转换器的 DefaultConversionService:ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [demo.Main$Point]

Converter 单向,Formatter 同时负责 parse 和 print。

数据绑定

实测 · 本机执行输出
  绑定成功的字段:day=2026-10-19 price=9.90 count=7 color=GREEN pt=Point[x=10, y=20] nums=[4, 5, 6] timeout=PT5S formatted(@DateTimeFormat yyyy/MM/dd)=2026-10-19 money(@NumberFormat #,##0.00)=1234.50;错误数 0
  全部写错的输入:错误数 5
    字段 day:code=typeMismatch 被拒绝的值=19/10/2026
    字段 count:code=typeMismatch 被拒绝的值=seven
    字段 color:code=typeMismatch 被拒绝的值=BLUE
    字段 pt:code=typeMismatch 被拒绝的值=1;2
    字段 price:code=typeMismatch 被拒绝的值=9.9.9
  出错的字段没有被赋值:g.day=null g.count=0 g.color=null(保持默认值)

DataBinder 把转换失败变成 FieldError,出错的字段保持默认值。

PropertyEditor

实测 · 本机执行输出
  只有 ConversionService:pt = Point[x=3, y=4]
  同时有 ConversionService 和给 Point 注册的 PropertyEditor(它总是产生 Point(-1,-1)):pt = Point[x=-1, y=-1](谁优先)

给绑定器注册的 PropertyEditor 会盖过 ConversionService。

分步演示

类型转换
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9;DataBinder 开了 initDirectFieldAccess(表单类的字段是 public、没有 setter);Boot 的 ApplicationConversionService(支持 "30s" 这样的 Duration 写法)这篇没有测
    • 没有验证:Boot 的 ApplicationConversionService 与 @DurationUnit;Spring MVC 里的 WebDataBinder 与 @InitBinder;GenericConverter / ConverterFactory 的写法;ConversionService 的线程安全(只用了单线程);PropertyEditor 的线程不安全问题
    京ICP备2026067502号-1京公网安备11010502063487号