👋 前言
前面我们已经玩转了 Spring 核心、Web 流程、配置体系与事件驱动:IOC → 生命周期 → 循环依赖 → AOP → 事务 → SpringMVC → Boot 自动配置 → 资源加载 → 事件驱动
接下来了解 我们在 Controller 中写的 @RequestParam String id 如何自动转为 Long?@Valid 注解如何实现参数校验?前端传的字符串如何绑定到实体类的日期、枚举字段?
本篇,我们基于 Spring 5.3.x 拆解类型转换与校验体系:
- Converter、ConversionService 核心类型转换机制;
- Formatter、PropertyEditor 传统类型绑定的区别与用法;
- @Valid、Validator 校验原理,参数校验如何生效;
- SpringMVC 参数自动绑定的底层逻辑;
- 自定义类型转换器、自定义校验器的实战实现。
补上 “数据绑定” 的最后一块拼图,我们清楚:前端传入的字符串,如何一步步变成业务代码中可用的 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@NullableT convert(S source);}
Spring 的类型转换 SPI 包含四个层次的转换器接口:
转换器类型 | 适用场景 | 示例 |
| 单一源类型 → 单一目标类型的转换 | 实现 |
| 支持多组源类型和目标类型之间的转换 | 返回 |
| 提供条件判断能力 | 实现 |
| 具备条件判断的通用转换器 | = |
Converter 是最基础的转换器接口,适用于简单的 1:1 转换场景;当需要更灵活的功能时,可以根据需求选择合适的 SPI 接口。
自定义 Converter 实战(简单易实现)
如果内置转换器不满足需求(如 String → 自定义实体类),可手动实现:
// 示例:将 String(格式:name,age)转换为 User 实体public class StringToUserConverter implements Converter<String, User> {@Overridepublic 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 转换为目标类型 Tboolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);// 2. 核心:执行类型转换@Nullable<T> T convert(@Nullable Object source, Class<T> targetType);// 重载方法:指定源类型和目标类型(更精准)boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);@NullableObject 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 类型)T parse(String text, Locale locale) throws ParseException;// 2. 目标类型 T → 字符串(Java 类型 → 页面显示)String print(T object, Locale locale);}
与 Converter 的区别
特性 | Converter<S, T> | Formatter<T> |
转换方向 | 从源类型到目标类型的单向转换(S → T) | 双向(String ↔ T) |
适用场景 | 任意类型之间转换 | 字符串与 Java 类型转换(Web 场景) |
底层机制 | 直接实现转换逻辑 | 通过 |
依赖 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 到 PropertyEditorRegistryvoidregisterCustomEditors(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),能自动转为 Long、User 类型,底层就是 类型转换体系 在工作,结合 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. 获取 ConversionServiceConversionService 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. 判断当前校验器是否支持校验该类型的 Beanboolean supports(Class<?> clazz);// 2. 核心:校验 Bean,将校验结果存入 Errors 对象void validate(Object target, Errors errors);}
核心实现类:LocalValidatorFactoryBean
Spring 提供 LocalValidatorFactoryBean,实现了 Validator 接口,同时适配 JSR-380 规范(如 Hibernate Validator),是日常开发中最常用的校验器实现。
2. @Valid 注解底层原理
核心流程
- 配置类添加@EnableWebMvc(或 Spring Boot 自动配置),Spring 会自动注册 LocalValidatorFactoryBean;
- 在 Controller 方法的参数(实体类)前添加 @Valid,触发参数校验;
- @Validated 是 Spring 的扩展注解,用于支持分组校验(Groups)和方法级别校验,可替代 @Valid 使用。
- SpringMVC 中,
RequestResponseBodyMethodProcessor(处理 @RequestBody)或ModelAttributeMethodProcessor(处理 @ModelAttribute),会调用Validator进行校验; - 校验失败时,抛出
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 规范)
注解 | 作用 |
| 字段不能为 null |
| 字符串不能为 null 且不能为空白(空格、换行等) |
| 集合不能为 null 且不能为空,字符串不能为 null 且长度 > 0 |
| 数字最小值 |
| 数字最大值 |
| 字符串符合指定正则表达式 |
| 字符串符合邮箱格式 |
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<Phone, String> {// 手机号正则private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";@Overridepublic 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}
五、核心总结
- 类型转换核心:
Converter(单个转换)+ConversionService(统一管理),Spring 内置大量转换器; - 双向转换:
Formatter(支持国际化,Web 场景),区别于Converter的单向转换; - 传统转换:
PropertyEditor(Java 原生,双向,Spring 兼容,不推荐新用); - SpringMVC 参数绑定:
HandlerMethodArgumentResolver结合ConversionService,完成字符串 → 目标类型转换; - 数据校验:
Validator为核心,LocalValidatorFactoryBean为实现,适配 JSR-380 注解(支持@NotNull、@NotBlank、@Min、@Max等约束注解;);
- @Valid(JSR-303/349 标准):触发参数校验和级联校验;
- @Validated(Spring 扩展):支持分组校验(Groups),可标注在类级别触发方法校验。
- 扩展方式:自定义 Converter、自定义 Formatter、自定义 Validator,满足业务个性化需求。
六、高频面试题
- Spring 类型转换体系的核心组件是什么?答:核心是
Converter(单个类型转换实现)和ConversionService(转换器统一管理容器),DefaultConversionService是默认实现,自动注册内置转换器。 - Converter 和 Formatter 的区别是什么?答:① Converter 是单向转换(S→T),Formatter 是双向转换(String↔T);② Converter 适用于任意类型转换,Formatter 仅适用于字符串与 Java 类型转换;③ Formatter 支持国际化,Converter 不支持。
- SpringMVC 中,前端传入的字符串参数如何自动转为 Java 类型?答:底层是
HandlerMethodArgumentResolver(参数解析器)获取请求参数,调用ConversionService或Formatter,将字符串参数转为目标类型,最终注入到 Controller 方法中。 - @Valid 注解的底层原理是什么?答:@Valid 是 JSR-380 规范注解,Spring 通过
LocalValidatorFactoryBean(实现 Validator 接口)进行校验;Controller 方法参数添加 @Valid 后,SpringMVC 的参数解析器会触发校验,校验失败抛出MethodArgumentNotValidException。 - 如何实现自定义类型转换 / 自定义校验?答:① 自定义类型转换:实现
Converter接口,添加到ConversionService中;② 自定义校验:自定义校验注解,实现ConstraintValidator接口,指定注解的校验器。
下篇预告
第十一篇・Spring 5 源码深度拆解:Spring 扩展点大全
- 全面梳理所有核心扩展点,按使用频率排序
- 每个扩展点:核心作用 + 源码入口 + 适用场景
- 重点拆解:BeanFactoryPostProcessor、BeanPostProcessor、FactoryBean 等高频扩展点
- 实战总结:不同场景该用哪个扩展点,避免踩坑把前面所有钩子集中梳理,讲清 Spring 到底给了你哪些 “插手” 底层的入口。
夜雨聆风