做 Spring Boot Web 开发的小伙伴,想必都做过请求入参类型转换吧?比如前端传过来的日期字符串(像 "2026-02-11"),后端用 Date 类型接收;前端传的数字字符串(像 "1,000"),转成 Integer 类型......
以前遇到这种情况,咱们大概率会写一堆 @InitBinder 方法,手动配置类型转换器,代码又多又繁琐,还不好维护。但今天我要告诉你:**别再死磕 @InitBinder 做日期转换了!**用 Spring 提供的 @DateTimeFormat 注解,就能轻松搞定,省时又省力~
一、核心注解:不止 @DateTimeFormat,还有这些"神器"
除了咱们刚才说的 @DateTimeFormat(专门处理日期/时间类型转换),Spring 还贴心提供了 @NumberFormat 注解,用来处理数字类型的入参转换,这两个注解堪称表单请求的"黄金搭档",上手零难度。
1. 表单请求入参转换:6种方式,按需选择不踩坑
表单请求(比如 GET 请求的参数、POST 表单提交的参数)的入参转换,Spring 提供了多种方案,从简单注解到自定义扩展,覆盖所有场景,咱们按"简单到复杂"的节奏一步步说:
(1)最便捷:@DateTimeFormat 处理日期/时间
这是目前最常用、最简洁的日期转换方式,无需额外配置,直接在实体类属性或方法参数上添加注解,指定日期格式即可。
完整代码示例(两种用法全覆盖):
- 方法参数用法(GET请求):
java
// Controller 方法
@RequestMapping("/getDate")
public ResponseEntity<String> getDate(
// 前端传参:?date=2026-02-11
@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
return ResponseEntity.ok("转换后的日期:" + date);
}
- 实体类属性用法(POST表单提交):
java
// 实体类
public class UserForm {
// 前端表单提交:date=2026-02-11
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
// 其他属性 + getter/setter
}
// Controller 方法
@RequestMapping("/submitForm")
public ResponseEntity<String> submitForm(UserForm userForm) {
Date birthday = userForm.getBirthday();
return ResponseEntity.ok("转换后的生日:" + birthday);
}
(2)同系列:@NumberFormat 处理数字格式
和 @DateTimeFormat 用法类似,专门解决前端数字字符串(带千分位、百分比等)转后端数字类型的问题。
完整代码示例(结合实体类,贴合实际开发):
java
// 表单实体类(接收POST表单/GET参数)
public class ProductForm {
// 前端传参:num=1,000(千分位字符串)
@NumberFormat(pattern = "#,###")
private Integer num;
// 前端传参:rate=50%(百分比字符串)
@NumberFormat(pattern = "0.00%")
private Double rate;
// getter/setter 方法
public Integer getNum() { return num; }
public void setNum(Integer num) { this.num = num; }
public Double getRate() { return rate; }
public void setRate(Double rate) { this.rate = rate; }
}
// Controller 方法
@RequestMapping("/product")
public ResponseEntity<Map<String, Object>> getProduct(ProductForm form) {
Map<String, Object> result = new HashMap<>();
result.put("转换后数量", form.getNum()); // 1000(Integer类型)
result.put("转换后比例", form.getRate()); // 0.5(Double类型)
return ResponseEntity.ok(result);
}
说明:前端传参无论是 GET(?num=1,000&rate=50%)还是 POST 表单提交,Spring 都会自动通过注解完成转换。
(3)更灵活:自定义注解
如果 @DateTimeFormat 和 @NumberFormat 满足不了需求(比如自定义特殊格式、多格式兼容),可以使用 自定义注解 + AnnotationFormatterFactory 方式
核心逻辑:AnnotationFormatterFactory 是 Spring 提供的核心接口,专门用于**"绑定自定义注解与类型转换逻辑"**。当Spring检测到参数/属性上有自定义注解时,会通过该工厂直接执行转换逻辑
完整代码示例(支持多日期格式):
- 自定义注解(标记需要转换的字段,定义可配置的日期格式):
java
// 自定义日期转换注解(核心:定义支持的多格式)
@Target({ElementType.FIELD, ElementType.PARAMETER}) // 可用于实体属性和方法参数
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomDateTimeFormat {
// 默认支持两种格式,可根据需求自定义修改
String[] patterns() default {"yyyy-MM-dd", "yyyy/MM/dd"};
}
- 核心实现:AnnotationFormatterFactory
实现AnnotationFormatterFactory接口,在接口方法中编写转换逻辑
java
// 核心类:关联自定义注解 + 实现转换逻辑,无需依赖Formatter接口
public class CustomDateTimeFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<CustomDateTimeFormat> {
// 第一步:指定该工厂支持转换的目标类型(此处仅支持Date类型)
@Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> fieldTypes = new HashSet<>();
fieldTypes.add(Date.class); // 明确适配Date类型,避免无效转换
return fieldTypes;
}
// 第二步:实现"格式化输出"逻辑(可选,根据业务需求实现)
@Override
public Printer<Date> getPrinter(CustomDateTimeFormat annotation, Class<?> fieldType) {
return (date, locale) -> {
if (date == null) {
return "";
}
// 使用注解中配置的第一个格式作为输出格式
SimpleDateFormat sdf = new SimpleDateFormat(annotation.patterns()[0]);
return sdf.format(date);
};
}
// 第三步:核心实现"入参解析"逻辑(前端字符串 → 后端Date)
@Override
public Parser<Date> getParser(CustomDateTimeFormat annotation, Class<?> fieldType) {
return (text, locale) -> {
if (text == null || text.trim().isEmpty()) {
return null; // 空值处理,避免空指针
}
String[] supportPatterns = annotation.patterns();
// 遍历注解中支持的所有格式,尝试解析前端传参
for (String pattern : supportPatterns) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setLenient(false); // 严格校验格式,避免非法日期(如2026-02-30)
return sdf.parse(text.trim());
} catch (ParseException e) {
continue; // 一种格式失败,尝试下一种
}
}
// 所有格式均解析失败,抛出明确异常,便于排查问题
throw new ParseException(
"日期格式错误,支持的格式:" + Arrays.toString(supportPatterns),
text.length()
);
};
}
}
- 全局注册工厂(一次配置,全局生效):
将AnnotationFormatterFactory注册到Spring容器中,无需额外配置,所有添加了@CustomDateTimeFormat注解的参数/属性均可自动完成转换:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 注册自定义注解工厂,替代传统的Formatter注册,全局生效
registry.addFormatterForFieldAnnotation(new CustomDateTimeFormatAnnotationFormatterFactory());
}
}
- 使用方式(和@DateTimeFormat完全一致,简洁易用):
无论是Controller方法参数,还是实体类属性,只需添加自定义注解,即可自动完成转换,无需额外代码:
java
// 用法1:Controller方法参数(GET请求,前端传参:?date=2026/02/11 或 ?date=2026-02-11)
@RequestMapping("/customDate")
public ResponseEntity<String> customDate(
@CustomDateTimeFormat(patterns = {"yyyy-MM-dd", "yyyy/MM/dd"}) Date date) {
return ResponseEntity.ok("转换成功:" + date);
}
// 用法2:实体类属性(POST表单提交,前端传参:birthday=2026-02-11)
public class UserForm {
// 无需额外配置,注解贴哪里,转换就生效哪里
@CustomDateTimeFormat
private Date birthday;
// getter/setter 方法(必须提供,Spring需通过反射获取/设置属性值)
public Date getBirthday() { return birthday; }
public void setBirthday(Date birthday) { this.birthday = birthday; }
}
(4)通用适配:实现Formatter接口(全局类型转换)
除了注解方式,表单请求还可通过直接实现Formatter接口,配置全局类型转换规则,无需在参数/实体上添加任何注解,适合同一类型(如数字、日期)需统一转换格式的场景。
核心逻辑:Formatter接口是Spring表单请求的通用转换接口,专门处理**"前端字符串 → 后端目标类型"的转换,实现接口后注册到Spring容器,即可对指定类型的所有表单入参**自动生效,无需额外绑定注解。
完整代码示例(以数字格式转换为例,适配所有Integer类型表单入参):
- 实现Formatter接口,编写数字转换逻辑(千分位字符串转Integer):
java
// 实现Formatter接口,指定转换类型为Integer(前端千分位字符串 → 后端Integer)
public class IntegerFormatter implements Formatter<Integer> {
@Override
public Integer parse(String text, Locale locale) throws ParseException {
if (text == null || text.trim().isEmpty()) {
return null;
}
// 处理前端千分位字符串(如"1,000" → 1000)
try {
// 去除千分位逗号,转换为Integer
String numStr = text.replace(",", "");
return Integer.parseInt(numStr);
} catch (NumberFormatException e) {
throw new ParseException("数字格式错误,支持格式:带千分位或普通数字(如1,000、1000)", 0);
}
}
@Override
public String print(Integer object, Locale locale) {
// 格式化输出(可选,用于后端向前端返回时的格式处理)
if (object == null) {
return "";
}
// 后端返回时,自动格式化为千分位字符串
return String.format("%,d", object);
}
}
- 注册Formatter到Spring容器,全局生效:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 注册自定义IntegerFormatter,全局所有Integer类型表单入参自动适配
registry.addFormatter(new IntegerFormatter());
// 可同时注册多个Formatter,适配不同类型(如Date、Double等)
}
}
- 使用方式(无需注解,自动适配):
前端传参(GET/POST表单):?num=1,000 或 ?num=2000,后端直接用Integer接收,无需添加@NumberFormat注解:
java
// Controller方法(无需任何转换注解)
@RequestMapping("/formatterNum")
public ResponseEntity<String> formatterNum(Integer num) {
// 前端传"1,000",自动转换为1000(Integer类型)
return ResponseEntity.ok("Formatter转换后:" + num);
}
// 实体类属性(无需注解,同样自动转换)
public class OrderForm {
// 前端传num=3,500,自动转换为3500
private Integer num;
// getter/setter方法
public Integer getNum() { return num; }
public void setNum(Integer num) { this.num = num; }
}
关键说明:
-
此方式无注解依赖 ,注册后对指定类型(如示例中的Integer)的所有表单入参全局生效,适合项目中同一类型需统一转换格式的场景;
-
可实现多个Formatter,分别适配Date、Double等不同类型,覆盖多种表单转换需求;
-
与@NumberFormat注解对比:注解方式灵活(可单独配置单个参数/属性格式),Formatter方式通用(全局统一格式),按需选择即可。
(5)古老但可用:@InitBinder + PropertyEditor
这是 Spring 早期的日期/类型转换方式,也是咱们以前常用的"笨办法"------通过 @InitBinder 注解,在 Controller 中手动注册 PropertyEditor 转换器,指定日期格式。
缺点很明显:代码繁琐,每个 Controller 都要写一遍(除非抽成全局拦截器),维护成本高;优点是兼容性好,适合老项目改造,不建议新项目使用。
完整代码示例(传统 @InitBinder 用法):
java
@Controller
@RequestMapping("/old")
public class OldConverterController {
// 每个Controller都要写一遍,繁琐!
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注册日期转换器,指定格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 严格校验格式,不匹配则报错
dateFormat.setLenient(false);
// 绑定到PropertyEditor
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
// 前端传参:?date=2026-02-11
@RequestMapping("/date")
public ResponseEntity<String> getDate(Date date) {
return ResponseEntity.ok("转换后的日期:" + date);
}
}
说明:若要全局生效,可将 @InitBinder 方法抽成 BaseController,所有 Controller 继承 BaseController,避免重复编码,但仍不如全局 Formatter 简洁。
(6)特殊场景:自定义 Converter(仅适用于方法参数)
除了 Formatter,Spring 还提供了 Converter 接口 用于类型转换,但要注意一个关键细节:Converter 不能用于实体类属性转换,只能用于 Controller 方法的单个参数转换。
适用场景:比如前端传一个特殊格式的字符串(如 "2026|02|11"),需要转成 Date 类型,用 Converter 实现转换逻辑,再注册到 Spring 容器中,即可直接在方法参数上使用。
完整代码示例(自定义 Converter,仅适用于方法参数):
- 实现 Converter 接口,编写转换逻辑:
java
// 自定义Converter:String(2026|02|11)→ Date
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
try {
// 适配前端特殊格式:2026|02|11
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(source);
} catch (ParseException e) {
throw new IllegalArgumentException("日期格式错误,正确格式:yyyy|MM|dd");
}
}
}
- 注册 Converter 到 Spring 容器:
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 注册自定义Converter
registry.addConverter(new StringToDateConverter());
}
}
- 使用方式(仅支持方法参数,实体类属性无效):
java
// 前端传参:?date=2026|02|11
@RequestMapping("/converterDate")
public ResponseEntity<String> converterDate(Date date) {
// 无需加任何注解,Spring自动调用Converter转换
return ResponseEntity.ok("Converter转换成功:" + date);
}
🔔 关键提醒:若将 Date 类型放在实体类中,前端传参后 Converter 不会生效,仍需用 Formatter 或 @DateTimeFormat 注解。
2. JSON 请求入参转换:Jackson 注解搞定一切
现在大部分项目都是前后端分离,常用 JSON 格式传参(比如 POST 请求的 RequestBody),这种场景下,上面说的 Formatter、Converter 就失效了,此时需要用 Jackson 相关注解 来完成类型转换。
Jackson 是 Spring Boot 默认的 JSON 解析工具,提供了丰富的注解,覆盖日期、数字、布尔值等所有常见类型的转换,用法和前面的 Spring 注解类似,直接贴在实体类属性上即可。
常用 Jackson 转换注解(必记):
-
@JsonFormat:最常用,处理日期/时间转换,支持指定格式、时区(解决时区偏移问题),比如 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-
@JsonDeserialize:自定义反序列化器,用于复杂类型转换(比如自定义字符串转枚举、转复杂对象)
-
@JsonNumberFormat:处理 JSON 中的数字格式转换(类似 @NumberFormat,但仅适用于 JSON 请求)
注意:JSON 请求的转换,核心是"Jackson 反序列化",所有注解都是作用于 Jackson 的反序列化过程,和表单请求的 Formatter 是两个完全独立的体系,不要混淆哦~
二、总结:入参转换选型指南,按需Pick不踩坑
看完上面的内容,相信你已经清楚不同场景下该用哪种入参转换方式了,这里做个总结,帮你快速选型,提高开发效率:
表单请求(GET/POST 表单):
-
简单日期/数字转换:优先用 @DateTimeFormat、@NumberFormat(最便捷)
-
复杂格式/多格式兼容:用 自定义注解 或 Formatter(灵活)
-
老项目兼容:用 @InitBinder + PropertyEditor(不推荐新项目)
-
单个方法参数特殊转换:用自定义 Converter
JSON 请求(@RequestBody):
-
简单日期/数字转换:用 @JsonFormat、@JsonNumberFormat
-
复杂类型转换:用 @JsonDeserialize + 自定义反序列化器
最后补充一句:建议在项目中统一配置全局转换规则(比如全局日期格式、全局数字格式),避免每个地方重复配置,提升代码可维护性。
从此,告别繁琐的 @InitBinder,用对注解和自定义方式,Spring Boot Web 入参转换就能高效又省心!