Spring 注解驱动格式化体系解析:AnnotationFormatterFactory 与 @DateTimeFormat 底层实现
文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载导读AnnotationFormatterFactory是 Spring 类型转换与格式化体系org.springframework.format中的核心扩展点它让开发者可以基于字段上的自定义注解为不同类型如Date、Long动态选取对应的Printer对象转字符串与Parser字符串转对象从而把注解声明与格式化策略解耦。本文以 Spring-AnnotationFormatterFactory.md 为骨架逐方法拆解该接口的设计意图并通过仓库中的DateTimeFormatAnnotationFormatterFactory、DateFormatter、Parser、Printer等配套实现与源码佐证讲清DateTimeFormat从注解声明到格式化器生效的完整链路。读完你将掌握 Spring 格式化 SPI 的接口契约、扩展步骤以及如何在 Spring MVC / Web 环境中落地注解格式化。一、接口定位Spring 格式化体系中的注解工厂Spring 的格式化体系位于org.springframework.format包整体呈三件套 一个工厂的结构角色接口 / 类职责输出PrinterT将对象T打印成字符串print(T object, Locale locale)输入ParserT将字符串解析成对象Tparse(String text, Locale locale)组合FormatterT同时继承PrinterT与ParserT一个类兼顾双向转换工厂AnnotationFormatterFactoryA按注解A 字段类型动态产出Printer/Parser其中AnnotationFormatterFactory是连接注解与格式化器的桥也是本文的主角。它的类全路径为org.springframework.format.AnnotationFormatterFactory泛型参数A extends Annotation表示它只服务于某一类注解。仓库中docs/Spring/clazz/format/目录恰好收录了这套体系的核心文档可与本文互相印证Spring-Formatter.mdFormatterT extends PrinterT, ParserT组合接口Spring-Parser.md字符串转对象的FunctionalInterfaceSpring-Printer.md对象转字符串的FunctionalInterface。二、接口契约逐方法解析AnnotationFormatterFactory源码非常精简只有三个方法public interface AnnotationFormatterFactoryA extends Annotation { /** * The types of fields that may be annotated with the A annotation. * 字段类型 */ SetClass? getFieldTypes(); /** * 通过注解和字段类型获取输出接口 */ Printer? getPrinter(A annotation, Class? fieldType); /** * 通过注解和字段类型获取解析接口 */ Parser? getParser(A annotation, Class? fieldType); }1.getFieldTypes()声明可注解的字段类型集合返回一个SetClass?用于声明哪些类型的字段允许被该注解修饰。例如日期格式化工厂只允许Date、Calendar、Long三类字段挂DateTimeFormat。从实现角度看这个集合通常是静态不可变的见下文DateTimeFormatAnnotationFormatterFactory的static {}块使用Collections.unmodifiableSet(...)包装防止运行时被意外篡改。2.getPrinter(A annotation, Class? fieldType)获取输出接口根据注解实例与字段类型返回一个Printer?用于把该字段值打印序列化成字符串典型场景是后台对象 → 前端展示。接口注释特别说明如果Printer接受的类型T不能赋值给fieldType在调用Printer之前会先尝试把fieldType强制转换为T。也就是说框架会对返回值做一层类型适配兜底工厂本身不需要负责所有强转。3.getParser(A annotation, Class? fieldType)获取解析接口与getPrinter对称返回Parser?用于把提交上来的字符串解析成字段值典型场景是表单提交 → 绑定到对象属性。同样存在适配兜底如果解析器返回的对象不能赋值给fieldType在设置字段之前会尝试强制转换到fieldType。4. 设计要点小结按注解聚合每个工厂只针对一种注解泛型A不同注解对应不同工厂互不干扰按字段类型分发同一种注解挂在不同类型字段上时工厂可以返回不同的Printer/Parser实现精细控制与Formatter互补FormatterT解决一种类型的格式化AnnotationFormatterFactoryA解决一类注解 多种字段类型的格式化后者是前者的注解驱动升级版。三、官方经典实现DateTimeFormatAnnotationFormatterFactory接口本身不产生价值真正的价值在实现类。仓库中 Spring-DateTimeFormatAnnotationFormatterFactory.md 完整收录了官方对DateTimeFormat的实现org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory这是理解该接口的最佳样本。1. 类声明与继承体系public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactoryDateTimeFormat { // ... }实现AnnotationFormatterFactoryDateTimeFormat明确服务对象是DateTimeFormat注解继承EmbeddedValueResolutionSupport获得解析占位符如${...}的能力——这正是DateTimeFormat(pattern ${app.date.pattern})这种写法能生效的底层原因EmbeddedValueResolutionSupport实现EmbeddedValueResolverAware容器会为其注入StringValueResolver从而把注解属性中的占位符解析为真实配置值。仓库图片 images/spring/DateTimeFormatAnnotationFormatterFactory.png 展示了该类的完整继承关系Aware→EmbeddedValueResolverAware→EmbeddedValueResolutionSupport→DateTimeFormatAnnotationFormatterFactory。2. 字段类型声明静态不可变集合private static final SetClass? 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); }DateTimeFormat支持三类字段java.util.Date、java.util.Calendar以及Long毫秒时间戳。用Collections.unmodifiableSet包装成只读集合保证线程安全与不可变性。3. 双向共用同一个 FormatterOverride public Printer? getPrinter(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); } Override public Parser? getParser(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); }输出与解析返回的是同一个FormatterDateDateFormatter同时实现了Printer和Parser见 Spring-Formatter.md因为日期的解析与打印模式天然对称——用同一套模式pattern双向转换。4. 核心逻辑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的关键style读取DateTimeFormat.style()如SS调用resolveEmbeddedValue先解析占位符非空才调用setStylePattern设置iso无条件调用setIso(annotation.iso())将注解的ISO枚举ISO.DATE、ISO.DATE_TIME、ISO.TIME、ISO.NONE传给DateFormatterpattern读取DateTimeFormat.pattern()同样先做占位符解析非空才调用setPattern覆盖式设置。即pattern拥有最高优先级——当pattern非空时style与iso设定的格式会被pattern覆盖这也与 Spring 官方文档pattern优先于style的行为一致。5. 与注解属性的一一对应DateTimeFormat注解的核心属性与工厂方法参数完全对应注解属性类型工厂中对应逻辑styleStringresolveEmbeddedValue→setStylePatternisoISO枚举setIsopatternStringresolveEmbeddedValue→setPattern三者都支持占位符表达式例如public class Event { DateTimeFormat(pattern ${event.date.pattern:yyyy-MM-dd HH:mm:ss}) private Date startTime; }当event.date.pattern配置缺失时:yyyy-MM-dd HH:mm:ss作为默认值兜底——这是 Spring 占位符解析的标准语法。四、配套 Parser / Printer 实现Joda-Time 视角除了日期包下的DateTimeFormatAnnotationFormatterFactoryorg.springframework.format.datetime.joda包还提供了针对 Joda-Time 的Parser/Printer实现进一步展示工厂产出处理器的模式仓库均有收录1. DateTimeParser字符串 → Joda DateTime见 Spring-DateTimeParser.md类全路径org.springframework.format.datetime.joda.DateTimeParserpublic final class DateTimeParser implements ParserDateTime { private final DateTimeFormatter formatter; public DateTimeParser(DateTimeFormatter formatter) { this.formatter formatter; } Override public DateTime parse(String text, Locale locale) throws ParseException { // DateTimeFormatter 转换字符串为时间类型 return JodaTimeContextHolder.getFormatter(this.formatter, locale).parseDateTime(text); } }关键点在于JodaTimeContextHolder.getFormatter(...)它会结合当前Locale与线程绑定的JodaTimeContext时区、周起始日等包装底层DateTimeFormatter实现本地化解析。JodaTimeContextHolder的线程绑定特性意味着解析结果受当前线程的 Joda-Time 上下文影响。2. MillisecondInstantPrinterLong 毫秒时间戳 → 字符串见 Spring-MillisecondInstantPrinter.md类全路径org.springframework.format.datetime.joda.MillisecondInstantPrinterpublic final class MillisecondInstantPrinter implements PrinterLong { private final DateTimeFormatter formatter; public MillisecondInstantPrinter(DateTimeFormatter formatter) { this.formatter formatter; } Override public String print(Long instant, Locale locale) { // DateTimeFormatter.print return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(instant); } }它把Long类型的毫秒时间戳直接交给DateTimeFormatter.print(...)输出印证了AnnotationFormatterFactory.getFieldTypes()中允许Long字段存在的意义——工厂可以在getPrinter/getParser里针对Long字段返回MillisecondInstantPrinter这类专门处理器。五、注册与生效注解工厂如何接入转换服务工厂定义好了还需要注册到类型转换体系中才会被框架调用。这涉及两条仓库可印证的线索1. ConversionService 在容器中的注入Spring IoC 容器在启动时会为BeanWrapper设置ConversionService。在 docs/Spring/IoC/4、依赖注入(DI).md.md) 中可以看到这是 Spring 3 之后新增的代码路径// 这是 Spring3 以后新加的代码为容器指定一个转换服务 (ConversionService) if (beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); }容器会优先检查名为conversionService的 bean若类型匹配则注入到BeanFactory。而在 Spring-beanFactory.md 中也有bw.setConversionService(getConversionService())的调用说明BeanWrapper在属性填充数据绑定阶段就会使用ConversionService完成类型转换——这正是注解格式化在 MVC 数据绑定中生效的入口。2. 注册注解工厂的标准姿势AnnotationFormatterFactory的注册入口是FormatterRegistry推荐通过实现FormatterRegistrar或直接向FormattingConversionService注册。一个典型的扩展实现注册到 Spring MVC 的WebMvcConfigurer#addFormatters如下public class MyDateTimeFormatFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactoryDateTimeFormat { private static final SetClass? FIELD_TYPES Collections.unmodifiableSet(new HashSet(Arrays.asList(Date.class, Calendar.class, Long.class))); Override public SetClass? getFieldTypes() { return FIELD_TYPES; } Override public Printer? getPrinter(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); } Override public Parser? getParser(DateTimeFormat annotation, Class? fieldType) { return getFormatter(annotation, fieldType); } protected FormatterDate getFormatter(DateTimeFormat annotation, Class? fieldType) { DateFormatter formatter new DateFormatter(); formatter.setIso(annotation.iso()); String pattern resolveEmbeddedValue(annotation.pattern()); if (StringUtils.hasLength(pattern)) { formatter.setPattern(pattern); } return formatter; } }在配置类中注册Configuration public class WebConfig implements WebMvcConfigurer { Override public void addFormatters(FormatterRegistry registry) { registry.addFormatterForFieldAnnotation(new MyDateTimeFormatFactory()); } }FormatterRegistry#addFormatterForFieldAnnotation(AnnotationFormatterFactory?)是注册注解工厂的标准 API。注册后凡是被DateTimeFormat注解的字段在 MVC 参数绑定字符串 → 对象与视图渲染对象 → 字符串时都会自动走工厂产出的Printer/Parser。六、全文总结AnnotationFormatterFactoryA以极简的三方法契约getFieldTypes/getPrinter/getParser撬动了 Spring 注解驱动格式化的大厦契约清晰getFieldTypes声明适用范围getPrinter/getParser按注解实例与字段类型分发处理器框架自带类型适配兜底实现典范DateTimeFormatAnnotationFormatterFactory展示了继承EmbeddedValueResolutionSupport获得占位符解析能力 组装DateFormatter 双向复用同一 Formatter 静态不可变字段类型集合的完整套路优先级明确patternstyle/iso且三者均支持${...}占位符支持外部化配置配套完整Parser、Printer、Formatter接口与 Joda-Time 包下的DateTimeParser、MillisecondInstantPrinter构成了可组合、可替换的处理器生态落地清晰通过FormatterRegistry.addFormatterForFieldAnnotation(...)注册借由ConversionService注入BeanWrapper的链路在 MVC 数据绑定中自动生效。掌握这一接口你不仅能读懂DateTimeFormat的底层原理还能举一反三为任意自定义注解编写格式化工厂实现真正的注解即配置。延伸阅读接口契约Spring-AnnotationFormatterFactory.md官方实现Spring-DateTimeFormatAnnotationFormatterFactory.md组合接口Spring-Formatter.md基础接口Spring-Parser.md、Spring-Printer.mdJoda-Time 配套Spring-DateTimeParser.md、Spring-MillisecondInstantPrinter.md容器注入线索4、依赖注入(DI).md.md)、Spring-beanFactory.md赞分享文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载相关推荐Spring 源码解析DateTimeFormatAnnotationFormatterFactory 与 DateTimeFormat 注解驱动的日期格式化工厂Spring 源码解析DateTimeFormatAnnotationFormatterFactory 与 DateTimeFormat 注解驱动的日期格式文档教程技术博客知识库Spring AnnotationFormatterFactory 源码解析注解驱动的格式化工厂如何打通 Date/Calendar 等类型转换Spring AnnotationFormatterFactory 源码解析注解驱动的格式化工厂如何打通 Date/Calendar 等类型转换 导读 Ann文档教程技术博客知识库Spring Formatter 格式化 SPI 源码解析Printer、Parser 与注解驱动的日期格式化体系Spring Formatter 格式化 SPI 源码解析Printer、Parser 与注解驱动的日期格式化体系 导读 org.springframewor文档教程技术博客知识库上一篇从零到一Univer组件设计系统构建可复用UI组件库全指南下一篇终极指南如何快速搭建Arduino ESP32物联网开发环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考