ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring 源码解析:DateTimeFormatAnnotationFormatterFactory 与 @DateTimeFormat 注解驱动的日期格式化工厂

2026/9/13 2:03:25 拓冰建站 浏览量
Spring 源码解析:DateTimeFormatAnnotationFormatterFactory 与 @DateTimeFormat 注解驱动的日期格式化工厂 Spring 源码解析DateTimeFormatAnnotationFormatterFactory 与 DateTimeFormat 注解驱动的日期格式化工厂【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter导读本文围绕 Spring 格式化框架中的核心工厂类org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory展开剖析它是如何把DateTimeFormat注解翻译成可复用的DateFormatter从而让日期字段在 Web 请求绑定与响应输出时自动完成字符串 ↔ 日期对象的双向转换。读完本文你将掌握AnnotationFormatterFactory工厂模式的落地写法、style / iso / pattern三种日期格式配置维度的优先级关系以及EmbeddedValueResolutionSupport支持的${...}占位符解析能力并能在自己的项目中复刻这套注解驱动 工厂生产格式化器的设计。一、类定位日期格式化工厂在整个格式化体系中的位置在 Spring 的org.springframework.format包中格式化能力由一对对称的顶层接口定义Printer把对象打印成字符串方法签名String print(T object, Locale locale)是FunctionalInterfaceParser把字符串解析成对象方法签名T parse(String text, Locale locale) throws ParseException同样是FunctionalInterfaceFormatter同时继承PrinterT与ParserT一个类即可完成双向转换常见的DateFormatter就是该接口的实现AnnotationFormatterFactory负责根据注解 字段类型生产出对应的Printer与Parser是注解驱动格式化的总开关。DateTimeFormatAnnotationFormatterFactory正是AnnotationFormatterFactoryDateTimeFormat在日期时间领域的具体实现它为标注了DateTimeFormat的字段按注解上声明的格式要求动态地生产格式化器。从源码结构看格式化工厂的调用链大致是FormattingConversionService在注册时扫描所有AnnotationFormatterFactory当遇到带DateTimeFormat注解的字段时委托该工厂生产Printer/Parser再经由类型转换服务完成字段绑定。下图是该类的继承关系与方法概览可以看到它同时实现了AnnotationFormatterFactory接口并继承EmbeddedValueResolutionSupport从而获得占位符解析能力二、类定义与双重身份工厂接口 嵌入式值解析能力类全路径org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactorypublic class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactoryDateTimeFormat { /** * 字段类型 */ private static final SetClass? FIELD_TYPES; ... }DateTimeFormatAnnotationFormatterFactory拥有双重身份实现AnnotationFormatterFactoryDateTimeFormat以DateTimeFormat为注解泛型向 Spring 声明凡是被该注解标注的字段都由我来生产格式化器继承EmbeddedValueResolutionSupport这是本类一个容易被忽略但非常关键的设计。EmbeddedValueResolutionSupport实现了EmbeddedValueResolverAware内部持有EmbeddedValueResolver解析器并暴露受保护的resolveEmbeddedValue(String)方法。正是借助它注解上的style与pattern属性才能写成${my.date.style}这类占位符形式由容器在运行时解析成真实配置值——这让格式化规则具备了配置外部化的能力而不是把日期格式硬编码在注解里。三、支持字段类型FIELD_TYPES 静态集合static { SetClass? fieldTypes new HashSet(4); // 加入字段类型 fieldTypes.add(Date.class); fieldTypes.add(Calendar.class); fieldTypes.add(Long.class); FIELD_TYPES Collections.unmodifiableSet(fieldTypes); } Override public SetClass? getFieldTypes() { return FIELD_TYPES; }FIELD_TYPES在静态初始化块中构建随后被包装成不可修改集合Collections.unmodifiableSet保证外部无法篡改。它声明了本工厂支持的三类字段字段类型说明Datejava.util.Date最常见的日期类型Calendarjava.util.Calendar日历类Long以毫秒时间戳形式承载的日期这里的Long值得单独说明DateTimeFormat可以标注在Long类型的字段上此时该字段被视为毫秒级时间戳工厂会把DateFormatter应用于毫秒值实现时间戳与展示字符串之间的互转。这一点从同包下的 MillisecondInstantPrinter 可以得到印证——该 Printer 接收Longinstant并借助JodaTimeContextHolder获取的DateTimeFormatter完成打印。四、生产格式化器getPrinter 与 getParser 的对称委托Override public Printer? getPrinter(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); } Override public Parser? getParser(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); }getPrinter与getParser都直接委托给受保护的getFormatter(DateTimeFormat, Class?)。这正是FormatterT接口一个类同时承担打印与解析的体现DateFormatter既实现了PrinterDate的print也实现了ParserDate的parse因此工厂只需构建一个格式化器实例即可同时应答打印与解析两个方向的请求。从源码结构可以推断Spring 的FormattingConversionService在绑定请求参数时会先查getFieldTypes()判断字段类型是否匹配再调用getParser()完成字符串 → 对象而在渲染模型输出时调用getPrinter()完成对象 → 字符串。这也是DateTimeFormat能同时作用于表单回显与参数接收的根本原因。五、核心逻辑getFormatter 的三维配置组装getFormatter是本类的灵魂方法它负责把注解上的配置翻译成一个配置完备的DateFormatterprotected FormatterDate getFormatter(DateTimeFormat annotation, Class? fieldType) { DateFormatter formatter new DateFormatter(); // style String style resolveEmbeddedValue(annotation.style()); // 判断时间格式是否存在 if (StringUtils.hasLength(style)) { formatter.setStylePattern(style); } // iso 设置 formatter.setIso(annotation.iso()); // date time pattern String pattern resolveEmbeddedValue(annotation.pattern()); // 设置 if (StringUtils.hasLength(pattern)) { formatter.setPattern(pattern); } return formatter; }整个组装过程清晰对应DateTimeFormat注解的三个配置维度1. style —— 预定义风格样式style属性对应DateTimeFormat.Style如S/M/L/F及日期时间组合写法本类先通过resolveEmbeddedValue(annotation.style())解析可能的占位符再以StringUtils.hasLength判空——只有 style 非空时才调用formatter.setStylePattern(style)。这意味着注解可以只写 style由DateFormatter内部按风格模板生成格式。2. iso —— ISO 标准格式iso属性对应DateTimeFormat.ISO枚举DATE、TIME、DATE_TIME、NONE通过formatter.setIso(annotation.iso())无条件设置ISO.NONE即表示不使用 ISO 格式。与 style、pattern 不同iso 是枚举而非字符串因此不需要也无法进行占位符解析。3. pattern —— 自定义模式串pattern属性是最灵活的配置项支持yyyy-MM-dd HH:mm:ss这类SimpleDateFormat模式串。与 style 相同它也会先经过resolveEmbeddedValue解析——这也是pattern可以写成${...}占位符的原因。同样判空后才调用formatter.setPattern(pattern)。优先级与协作关系从组装顺序可以归纳出三者的协作逻辑注解上没有配置 style 时跳过 style 设置iso总是被写入默认NONE表示不启用 ISO注解上没有配置 pattern 时跳过 pattern 设置。DateFormatter自身维护了 style / iso / pattern 三套内部状态最终在print/parse时按显式 pattern ISO style的次序选择生效的格式模板。工厂在这里只负责读注解、做占位符解析、装配 formatter具体的格式推导与解析打印细节全部下沉到DateFormatter职责划分非常清晰。六、占位符解析能力的意义配置外部化EmbeddedValueResolutionSupport赋予的能力让如下写法成为可能public class User { // pattern 引用外部配置如 application.properties 中的 my.date.pattern DateTimeFormat(pattern ${my.date.pattern}) private Date birthday; // style 也可以走占位符 DateTimeFormat(style ${my.date.style}) private Date registerTime; // 直接写死模式串亦可 DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) private Date lastLoginTime; }resolveEmbeddedValue会委托给容器注入的EmbeddedValueResolver在 Spring MVC/Spring Boot 环境中通常是PropertySourcesPlaceholderConfigurer配套的解析器把${my.date.pattern}解析为配置文件中实际的模式串。这意味着日期格式可以从代码注解中剥离集中到配置文件统一管理同一套代码在不同环境如 zh_CN 与 en_US 站点可以注入不同的日期格式修改格式无需重新编译。七、支撑类型一览DateFormatter 与 Joda 时代的周边实现getFormatter最终产出的DateFormatter位于org.springframework.format.datetime包它实现FormatterDate内部将style、iso、pattern三种配置归一化为底层的格式模式。此外在同包及datetime.joda子包中还分布着若干直接实现Printer/Parser的小工具类它们体现了同一设计意图在不同日期库上的复用DateTimeParserorg.springframework.format.datetime.joda.DateTimeParser实现ParserDateTime构造时接收 JodaDateTimeFormatterparse时通过JodaTimeContextHolder.getFormatter(this.formatter, locale)结合当前 Locale 将字符串解析为 JodaDateTimeMillisecondInstantPrinterorg.springframework.format.datetime.joda.MillisecondInstantPrinter实现PrinterLong借助 JodaDateTimeFormatter将毫秒时间戳打印成字符串。可以看到无论基于java.util还是 Joda TimeSpring 都遵循同一套Printer/Parser抽象DateTimeFormatAnnotationFormatterFactory只是注解 → 格式化器这条生产线上与DateTimeFormat对接的入口之一。八、在 Spring MVC 中的实际应用场景结合 Spring MVC 的数据绑定机制DateTimeFormatAnnotationFormatterFactory的典型工作过程如下前端提交birthday1995-08-24之类的字符串参数WebDataBinder发现目标字段birthday标注了DateTimeFormat向FormattingConversionService查找对应的AnnotationFormatterFactory命中DateTimeFormatAnnotationFormatterFactory其getFieldTypes()确认Date在支持范围内调用getParser获得配置好的DateFormatterDateFormatter.parse(1995-08-24, locale)按 pattern/iso/style 解析出Date对象并完成字段赋值响应渲染如表单回显时通过getPrinter再按同样规则把Date打印回1995-08-24字符串。整个过程对业务代码完全透明——开发者只需在字段上标注DateTimeFormat格式化的生产、装配与调用都由工厂类与转换服务协作完成。九、总结DateTimeFormatAnnotationFormatterFactory用不足百行代码诠释了 Spring 格式化体系的核心设计关注点工厂的实现方式支持哪些字段类型FIELD_TYPES静态集合声明Date、Calendar、Long毫秒时间戳如何响应打印/解析getPrinter/getParser对称委托给getFormatter一个DateFormatter双职复用注解配置如何落地style预定义风格、isoISO 枚举、pattern自定义模式串三维组装如何支持动态配置继承EmbeddedValueResolutionSupport对 style/pattern 执行${...}占位符解析与周边组件的关系实现AnnotationFormatterFactoryDateTimeFormat接入FormattingConversionService注解驱动绑定链路工厂 注解 Printer/Parser抽象的搭配让如何格式化与何时格式化彻底解耦业务代码只声明意图注解Spring 负责在恰当的时机数据绑定用恰当的工厂本类生产恰当的格式化器。理解了这个类也就掌握了 Spring 中所有AnnotationFormatterFactory实现如数字、货币、集合等领域的同类工厂的通用套路可以轻松举一反三。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考