乐于分享
好东西不私藏

第十篇・Spring 5 源码深度拆解:Spring 类型转换与校验体系

第十篇・Spring 5 源码深度拆解:Spring 类型转换与校验体系

👋 前言

前面我们已经玩转了 Spring 核心、Web 流程、配置体系与事件驱动:IOC → 生命周期 → 循环依赖 → AOP → 事务 → SpringMVC → Boot 自动配置 → 资源加载 → 事件驱动

接下来了解 我们在 Controller 中写的 @RequestParam String id 如何自动转为 Long@Valid 注解如何实现参数校验?前端传的字符串如何绑定到实体类的日期、枚举字段?

本篇,我们基于 Spring 5.3.x 拆解类型转换与校验体系:

  1. Converter、ConversionService 核心类型转换机制;
  2. Formatter、PropertyEditor 传统类型绑定的区别与用法;
  3. @Valid、Validator 校验原理,参数校验如何生效;
  4. SpringMVC 参数自动绑定的底层逻辑;
  5. 自定义类型转换器、自定义校验器的实战实现。

补上 “数据绑定” 的最后一块拼图,我们清楚:前端传入的字符串,如何一步步变成业务代码中可用的 Java 类型。

一、核心基础:类型转换体系(Converter 与 ConversionService)

Spring 为了解决 不同类型之间的转换问题(如 String → Long、String → Date、String → 枚举),提供了一套统一的类型转换体系,核心是 Converter 接口和 ConversionService 接口,替代了 Java 原生的 PropertyEditor(有局限性),是 Spring 类型转换的核心。

1. 核心接口:Converter(最基础的类型转换器)

Converter<S, T> 是一个函数式接口,负责将 源类型 S 转换为目标类型 T,是所有类型转换器的基础。

源码(Spring 5.3.x)

@FunctionalInterfacepublic interface Converter<S, T> {    // 核心方法:将源类型 S 转换为目标类型 T    @Nullable    T convert(S source);}

Spring 的类型转换 SPI 包含四个层次的转换器接口:

转换器类型

适用场景

示例

Converter<S, T>

单一源类型 → 单一目标类型的转换

实现convert(S)方法,无条件执行

GenericConverter

支持多组源类型和目标类型之间的转换

返回Set<ConvertiblePair>,可处理一对多、多对多

ConditionalConverter

提供条件判断能力

实现matches(TypeDescriptor, TypeDescriptor)方法

ConditionalGenericConverter

具备条件判断的通用转换器

=GenericConverter+ConditionalConverter

Converter 是最基础的转换器接口,适用于简单的 1:1 转换场景;当需要更灵活的功能时,可以根据需求选择合适的 SPI 接口。

自定义 Converter 实战(简单易实现)

如果内置转换器不满足需求(如 String → 自定义实体类),可手动实现:

// 示例:将 String(格式:name,age)转换为 User 实体public class StringToUserConverter implements Converter<StringUser> {    @Override    public User convert(String source) {        if (source == null || source.isEmpty()) {            return null;        }        String[] parts = source.split(",");        return new User(parts[0], Integer.parseInt(parts[1]));    }}

2. 核心接口:ConversionService(转换器的统一管理)

Converter 是单个转换器,而 ConversionService 是 转换器的容器,负责管理所有 Converter,提供统一的类型转换入口,是 Spring 类型转换的核心入口。

核心源码(Spring 5.3.x)

public interface ConversionService {    // 1. 判断是否能将源类型 S 转换为目标类型 T    boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);    // 2. 核心:执行类型转换    @Nullable    <T> T convert(@Nullable Object source, Class<T> targetType);    // 重载方法:指定源类型和目标类型(更精准)    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);    @Nullable    Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);}

核心实现类:DefaultConversionService

Spring 默认使用 DefaultConversionService,它会 自动注册所有内置转换器,同时支持手动添加自定义转换器,是日常开发中最常用的 ConversionService 实现。

// 示例:创建 ConversionService 并添加自定义转换器ConversionService conversionService = new DefaultConversionService();((DefaultConversionService) conversionService).addConverter(new StringToUserConverter());// 执行转换User user = conversionService.convert("zhangsan,20", User.class);

3. 核心结论

  • Converter:单个类型转换的实现,负责具体的转换逻辑;
  • ConversionService:转换器的统一管理容器,提供统一的转换入口;
  • Spring 容器启动时,会自动创建 DefaultConversionService,并注册所有内置转换器。

二、传统类型绑定:Formatter 与 PropertyEditor

除了Converter 体系,Spring 还提供了 Formatter 和 PropertyEditor 用于类型转换,二者有明确的使用场景区别,核心用于 页面参数绑定、配置文件参数绑定

1. Formatter(格式化转换器,面向用户输入)

Formatter<T> 专门用于 字符串与目标类型的双向转换(如页面输入的字符串 → Java 类型,Java 类型 → 页面显示的字符串),常用于 Web 场景(SpringMVC 参数绑定)。

核心源码

public interface Formatter<T> {    // 1. 字符串 → 目标类型 T(页面输入 → Java 类型)    parse(String text, Locale locale) throws ParseException;    // 2. 目标类型 T → 字符串(Java 类型 → 页面显示)    String print(object, Locale locale);}

与 Converter 的区别

特性

Converter<S, T>

Formatter<T>

转换方向

从源类型到目标类型的单向转换(S → T)

双向(String  T)

适用场景

任意类型之间转换

字符串与 Java 类型转换(Web 场景)

底层机制

直接实现转换逻辑

通过 FormattingConversionService 适配为 Converter

依赖 Locale

不依赖

依赖(支持国际化格式化,如日期、数字)

常用内置 Formatter

  • NumberFormatter:数字与字符串双向转换(支持国际化)
  • DateFormatter:日期与字符串双向转换(支持自定义格式)

2. PropertyEditor(Java 原生,Spring 兼容)

PropertyEditor 是 JavaBeans 规范定义的接口,通过setAsText(String)getAsText()也支持双向转换(String  对象)。Spring 的 DataBinder 和 BeanWrapper 底层长期依赖 PropertyEditor 进行属性解析和类型转换。

定位:Spring 3.0 引入 ConversionService 体系作为更现代、类型安全且支持泛型的替代方案。Spring 5.x 中仍保留 PropertyEditor 体系作为兼容方案,但在新代码中推荐使用 Converter/Formatter 体系

核心源码(Java 原生)

public interface PropertyEditor {    // 核心:将字符串转换为目标类型    voidsetAsText(String text) throws IllegalArgumentException;    // 获取转换后的目标对象    Object getValue();    // 将目标对象转换为字符串(用于显示)    String getAsText();}

Spring 扩展:PropertyEditorRegistrar

Spring 提供 PropertyEditorRegistrar,用于注册自定义 PropertyEditor,适配传统配置场景:

public interface PropertyEditorRegistrar {    // 注册自定义 PropertyEditor 到 PropertyEditorRegistry    voidregisterCustomEditors(PropertyEditorRegistry registry);}

核心结论

  • Formatter:Spring 推荐,双向转换,支持国际化,适用于 Web 场景;
  • PropertyEditor:Java 原生,双向转换,适用于传统配置场景,Spring 仅做兼容,不推荐新代码使用。

三、SpringMVC 参数自动绑定底层

我们在 SpringMVC 中写的 Controller 方法,如:

@GetMapping("/user")public String getUser(@RequestParamLong id, @ModelAttributeUser user) {    // ...}

前端传入的 字符串参数(id=123、user.name=zhangsan),能自动转为 LongUser 类型,底层就是 类型转换体系 在工作,结合 SpringMVC 的流程,完整底层逻辑如下:

核心流程(SpringMVC 参数绑定 + 类型转换)

1. 前端发送请求(参数为字符串)        ↓2. DispatcherServlet 接收请求,找到对应的 HandlerMethod;    2.1 通过 HandlerMethodArgumentResolver(参数解析器)判断参数类型,选择合适的解析器实现:    2.2 RequestParamMethodArgumentResolver:解析 @RequestParam 参数    2.3 ServletModelAttributeMethodProcessor:解析 @ModelAttribute 参数    2.4 RequestResponseBodyMethodProcessor:解析 @RequestBody 参数3. 解析器获取请求参数后,委托给 WebDataBinder 进行数据绑定和类型转换;4. WebDataBinder 底层调用 ConversionService(或 Formatter)将字符串参数转换为目标类型;5. 转换完成后,通过反射调用 Controller 方法,完成参数注入。

关键源码(HandlerMethodArgumentResolver 调用转换)

以 RequestParamMethodArgumentResolver(解析 @RequestParam 参数)为例:

@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {    // 1. 从请求中获取字符串参数    String[] paramValues = webRequest.getParameterValues(name);    String paramValue = StringUtils.arrayToCommaDelimitedString(paramValues);    // 2. 获取 ConversionService    ConversionService conversionService = mavContainer.getConversionService();    // 3. 执行类型转换(字符串 → 目标类型,如 String → Long)    return conversionService.convert(paramValue, parameter.getParameterType());}

核心结论

SpringMVC 参数自动绑定的核心,是 HandlerMethodArgumentResolver + ConversionService/Formatter:解析器负责获取请求参数,转换器负责将字符串参数转为目标类型,最终完成参数注入。

四、数据校验体系:@Valid 与 Validator

日常开发中,我们常用 @Valid 或 @Validated 注解校验请求参数(如实体类的字段长度、格式),底层依赖 Spring 的校验体系,核心是 Validator 接口,适配 JSR-380 规范(如 @NotNull@NotBlank@Min 等注解)。

1. 核心接口:Validator(校验器)

Validator 是 Spring 校验体系的核心接口,负责对 Java Bean 进行校验,判断字段是否符合规则。

核心源码

public interface Validator {    // 1. 判断当前校验器是否支持校验该类型的 Bean    boolean supports(Class<?> clazz);    // 2. 核心:校验 Bean,将校验结果存入 Errors 对象    void validate(Object target, Errors errors);}

核心实现类:LocalValidatorFactoryBean

Spring 提供 LocalValidatorFactoryBean,实现了 Validator 接口,同时适配 JSR-380 规范(如 Hibernate Validator),是日常开发中最常用的校验器实现。

2. @Valid 注解底层原理

核心流程

  1. 配置类添加@EnableWebMvc(或 Spring Boot 自动配置),Spring 会自动注册 LocalValidatorFactoryBean
  2. 在 Controller 方法的参数(实体类)前添加 @Valid,触发参数校验;
  3. @Validated 是 Spring 的扩展注解,用于支持分组校验(Groups)和方法级别校验,可替代 @Valid 使用。
  4. SpringMVC 中,RequestResponseBodyMethodProcessor(处理 @RequestBody)或 ModelAttributeMethodProcessor(处理 @ModelAttribute),会调用 Validator 进行校验;
  5. 校验失败时,抛出 MethodArgumentNotValidException,可通过全局异常处理器捕获并返回提示。

关键源码(参数校验触发)

// RequestResponseBodyMethodProcessor 解析 @RequestBody 参数时触发校验protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {    // 获取参数上的 @Valid、@Validated 注解    Annotation[] annotations = parameter.getParameterAnnotations();    for (Annotation ann : annotations) {        if (ann.annotationType().getSimpleName().startsWith("Valid")) {            // 调用 Validator 进行校验            binder.validate();            break;        }    }}

3. 常用校验注解(JSR-380 规范)

注解

作用

@NotNull

字段不能为 null

@NotBlank

字符串不能为 null 且不能为空白(空格、换行等)

@NotEmpty

集合不能为 null 且不能为空,字符串不能为 null 且长度 > 0

@Min(value)

数字最小值

@Max(value)

数字最大值

@Pattern(regexp)

字符串符合指定正则表达式

@Email

字符串符合邮箱格式

4. 自定义校验器实战

如果内置校验注解不满足需求(如自定义手机号校验),可手动实现 Validator

// 1. 自定义校验注解@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = PhoneValidator.class// 指定校验器public @interface Phone {    String message() default "手机号格式错误";    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}// 2. 实现校验器public class PhoneValidator implements ConstraintValidator<PhoneString> {    // 手机号正则    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";    @Override    public boolean isValid(String value, ConstraintValidatorContext context) {        if (value == null || value.isEmpty()) {            return true// 允许为空,可配合 @NotNull 使用        }        return value.matches(PHONE_REGEX);    }}// 3. 使用public class User {    @Phone(message = "手机号格式错误")    private String phone;    // getter/setter}

五、核心总结

  1. 类型转换核心:Converter(单个转换)+ ConversionService(统一管理),Spring 内置大量转换器;
  2. 双向转换:Formatter(支持国际化,Web 场景),区别于 Converter 的单向转换;
  3. 传统转换:PropertyEditor(Java 原生,双向,Spring 兼容,不推荐新用);
  4. SpringMVC 参数绑定:HandlerMethodArgumentResolver 结合 ConversionService,完成字符串 → 目标类型转换;
  5. 数据校验:Validator 为核心,LocalValidatorFactoryBean 为实现,适配 JSR-380 注解(支持 @NotNull@NotBlank@Min@Max 等约束注解;);
    • @Valid(JSR-303/349 标准):触发参数校验和级联校验;
    • @Validated(Spring 扩展):支持分组校验(Groups),可标注在类级别触发方法校验。
  1. 扩展方式:自定义 Converter、自定义 Formatter、自定义 Validator,满足业务个性化需求。

六、高频面试题

  1. Spring 类型转换体系的核心组件是什么?答:核心是 Converter(单个类型转换实现)和 ConversionService(转换器统一管理容器),DefaultConversionService 是默认实现,自动注册内置转换器。
  2. Converter 和 Formatter 的区别是什么答:① Converter 是单向转换(S→T),Formatter 是双向转换(StringT);② Converter 适用于任意类型转换,Formatter 仅适用于字符串与 Java 类型转换;③ Formatter 支持国际化,Converter 不支持。
  3. SpringMVC 中,前端传入的字符串参数如何自动转为 Java 类型?答:底层是 HandlerMethodArgumentResolver(参数解析器)获取请求参数,调用 ConversionService 或 Formatter,将字符串参数转为目标类型,最终注入到 Controller 方法中。
  4. @Valid 注解的底层原理是什么?答:@Valid 是 JSR-380 规范注解,Spring 通过 LocalValidatorFactoryBean(实现 Validator 接口)进行校验;Controller 方法参数添加 @Valid 后,SpringMVC 的参数解析器会触发校验,校验失败抛出 MethodArgumentNotValidException
  5. 如何实现自定义类型转换 / 自定义校验?答:① 自定义类型转换:实现 Converter 接口,添加到 ConversionService 中;② 自定义校验:自定义校验注解,实现 ConstraintValidator 接口,指定注解的校验器。

下篇预告

第十一篇・Spring 5 源码深度拆解:Spring 扩展点大全

  • 全面梳理所有核心扩展点,按使用频率排序
  • 每个扩展点:核心作用 + 源码入口 + 适用场景
  • 重点拆解:BeanFactoryPostProcessor、BeanPostProcessor、FactoryBean 等高频扩展点
  • 实战总结:不同场景该用哪个扩展点,避免踩坑把前面所有钩子集中梳理,讲清 Spring 到底给了你哪些 “插手” 底层的入口。