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 → LongString → DateString → 枚举),提供了一套统一的类型转换体系。其核心是 Converter 接口ConversionService 接口 ,它们替代了 Java 原生的 PropertyEditor(存在局限性),是 Spring 类型转换的现代基石。

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

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

源码(Spring 5.3.x)

java 复制代码
@FunctionalInterface
public 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 → 自定义实体类),可手动实现:

java 复制代码
// 示例:将 String(格式:name,age)转换为 User 实体
public class StringToUserConverter implements Converter<String, User> {
    @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)

java 复制代码
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 实现。

java 复制代码
// 示例:创建 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 还提供了 FormatterPropertyEditor 用于类型转换,二者有明确的使用场景区别,核心用于 页面参数绑定、配置文件参数绑定

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

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

核心源码

java 复制代码
public interface Formatter<T> {
    // 1. 字符串 → 目标类型 T(页面输入 → Java 类型)
    T parse(String text, Locale locale) throws ParseException;

    // 2. 目标类型 T → 字符串(Java 类型 → 页面显示)
    String print(T object, Locale locale);
}

与 Converter 的区别

特性 Converter<S, T> Formatter
转换方向 从源类型到目标类型的单向转换(S → T) 双向(String ↔ T)
适用场景 任意类型之间转换 字符串与 Java 类型转换(Web 场景)
底层机制 直接实现转换逻辑 通过 FormattingConversionService 适配为 Converter
依赖 Locale 不依赖 依赖(支持国际化格式化,如日期、数字)

常用内置 Formatter

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

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

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

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

核心源码(Java 原生)

java 复制代码
public interface PropertyEditor {
    // 核心:将字符串转换为目标类型
    void setAsText(String text) throws IllegalArgumentException;

    // 获取转换后的目标对象
    Object getValue();

    // 将目标对象转换为字符串(用于显示)
    String getAsText();
}

Spring 扩展:PropertyEditorRegistrar

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

java 复制代码
public interface PropertyEditorRegistrar {
    // 注册自定义 PropertyEditor 到 PropertyEditorRegistry
    void registerCustomEditors(PropertyEditorRegistry registry);
}

3. 核心结论

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

三、SpringMVC 参数自动绑定底层

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

java 复制代码
@GetMapping("/user")
public String getUser(@RequestParam Long id, @ModelAttribute User user) {
    // ...
}

前端传入的 字符串参数id=123user.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 参数)为例:

java 复制代码
@Override
public 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 进行校验,判断字段是否符合规则。

核心源码

java 复制代码
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,触发参数校验;
    • @Validated 是 Spring 的扩展注解,用于支持分组校验(Groups)和方法级别校验,可替代 @Valid 使用。
  3. SpringMVC 中,RequestResponseBodyMethodProcessor(处理 @RequestBody)或 ModelAttributeMethodProcessor(处理 @ModelAttribute),会调用 Validator 进行校验;
  4. 校验失败时,抛出 MethodArgumentNotValidException,可通过全局异常处理器捕获并返回提示。

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

java 复制代码
// 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

java 复制代码
// 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<Phone, String> {
    // 手机号正则
    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),可标注在类级别触发方法校验。
  6. 扩展方式 :自定义 Converter、自定义 Formatter、自定义 Validator,满足业务个性化需求。

六、高频面试题

  1. Spring 类型转换体系的核心组件是什么?
    :核心是 Converter(单个类型转换实现)和 ConversionService(转换器统一管理容器),DefaultConversionService 是默认实现,自动注册内置转换器。

  2. Converter 和 Formatter 的区别是什么?
    :① Converter 是单向转换(S→T),Formatter 是双向转换(String↔T);② Converter 适用于任意类型转换,Formatter 仅适用于字符串与 Java 类型转换;③ Formatter 支持国际化,Converter 不支持。

  3. SpringMVC 中,前端传入的字符串参数如何自动转为 Java 类型?
    :底层是 HandlerMethodArgumentResolver(参数解析器)获取请求参数,调用 ConversionServiceFormatter,将字符串参数转为目标类型,最终注入到 Controller 方法中。

  4. @Valid 注解的底层原理是什么?
    @Valid 是 JSR-380 规范注解,Spring 通过 LocalValidatorFactoryBean(实现 Validator 接口)进行校验;Controller 方法参数添加 @Valid 后,SpringMVC 的参数解析器会触发校验,校验失败抛出 MethodArgumentNotValidException

  5. 如何实现自定义类型转换 / 自定义校验?
    :① 自定义类型转换:实现 Converter 接口,添加到 ConversionService 中;② 自定义校验:自定义校验注解,实现 ConstraintValidator 接口,指定注解的校验器。

相关推荐
Access开发易登软件2 小时前
Access 和 SQLite,根本不在一个赛道上
java·jvm·数据库·sqlite·excel·vba·access开发
长谷深风1112 小时前
Java 面试高频:反射机制与异常体系全面解析
java·开发语言·面试·exception·java 反射·java 异常·class 对象
过期动态2 小时前
【LeetCode 热题 100】盛最多水的容器
java·数据结构·spring boot·算法·leetcode·spring cloud·职场和发展
一 乐2 小时前
疫苗发布和接种预约|基于Java+vue疫苗发布和接种预约系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·疫苗发布和接种预约系统系统
2301_780789662 小时前
高防cdn如何缓存网页静态资源
java·spring·web安全·缓存·kubernetes·ddos
小马爱打代码2 小时前
Spring源码 第十一篇:Spring 扩展点全解析 - 从容器启动到 Bean 生命周期的完整执行时序
java·后端·spring
RainCity3 小时前
Java Swing 自定义组件库分享(九)
java·笔记·后端
NE_STOP3 小时前
Docker--容器常用命令
java
摇滚侠4 小时前
MSYS2 Builds Hashes Cygwin Builds Hashes 区别
java