Spring Formatter 格式化接口源码解析:从 Printer/Parser 到日期格式化实战

发布时间:2026/9/20 16:38:15
Spring Formatter 格式化接口源码解析:从 Printer/Parser 到日期格式化实战 Spring Formatter 格式化接口源码解析从 Printer/Parser 到日期格式化实战【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/doocs/source-code-hunter导读org.springframework.format.FormatterT是 Spring 类型转换与格式化体系spring-context 的org.springframework.format包中的核心接口它同时继承PrinterT与ParserT实现了对象 → 字符串与字符串 → 对象的双向能力。本文基于 doocs/source-code-hunter 仓库中的源码笔记从接口定义出发逐层剖析 Printer、Parser、Formatter 三者关系并结合DateFormatter、DateTimeFormatAnnotationFormatterFactory、Joda-Time 系实现等典型类讲解 Spring 如何借助这些接口完成 Web 层参数绑定、DateTimeFormat注解驱动的日期格式化最终落到FormatterRegistry/ConversionService的注册与调用链路上。读完本文你将掌握 Spring 格式化接口族的完整脉络并能独立实现自定义 Formatter 接入 Spring MVC。一、Formatter 接口总览类全路径org.springframework.format.Formatter。public interface FormatterT extends PrinterT, ParserT { }该接口本身是一个空接口marker interface全部能力都来自其继承的两个父接口父接口职责核心方法PrinterT对象 → 字符串输出/展示String print(T object, Locale locale)ParserT字符串 → 对象输入/解析T parse(String text, Locale locale) throws ParseException从设计上看Formatter把打印与解析这对互逆操作收敛到一个类型上形成对某个领域类型T的完整格式化描述。方法签名中均携带Locale参数说明格式化天然具备国际化i18n能力——不同区域下日期、数字的呈现方式可以不同。关联文档中明确指出该接口继承了 printer 和 parser 两个接口比较常见的有DateFormatter就是继承这个接口。这也是整个格式化体系中最重要的一个实现下文会重点展开。二、两个父接口Printer 与 Parser2.1 Printer把对象打印成字符串类全路径org.springframework.format.Printer类作用对象转换成字符串。FunctionalInterface public interface PrinterT { /** * Print the object of type T for display. * 打印对象 * param object the instance to print * param locale the current user locale * return the printed text string */ String print(T object, Locale locale); }要点标注了FunctionalInterface是函数式接口可用 Lambda / 方法引用快速实现print(T object, Locale locale)接收待展示的对象与当前用户区域返回展示用字符串典型用途Web 页面上将Date、Number等对象渲染为符合地区习惯的文本。2.2 Parser把字符串解析成对象类全路径org.springframework.format.Parser类作用字符串转换成 java 对象。FunctionalInterface public interface ParserT { /** * Parse a text String to produce a T. * 将字符串转换成对象 * param text the text string * param locale the current user locale * return an instance of T * throws ParseException when a parse exception occurs in a java.text parsing library * throws IllegalArgumentException when a parse exception occurs */ T parse(String text, Locale locale) throws ParseException; }要点同样是FunctionalInterfaceparse(String text, Locale locale)将用户提交的文本转换为T类型实例是 MVC 层表单/请求参数绑定的关键入口异常约定java.text 解析库抛ParseException其余情况抛IllegalArgumentException两个接口分别对应Formatter的出与入Parser方向的实现类如 Spring-DateTimeParserPrinter方向的实现类如 Spring-MillisecondInstantPrinter。从源码结构看Printer与Parser均为独立顶层接口为不需要双向能力的场景如只读展示提供了更细粒度的抽象选择。三、Formatter 的典型实现DateFormatter在 Spring 的org.springframework.format.datetime包中DateFormatter implements FormatterDate是Formatter接口最经典的落地实现。它完整体现了接口设计意图打印方向print(Date date, Locale locale)内部通过DateFormat如SimpleDateFormat把Date格式化为字符串解析方向parse(String text, Locale locale)内部通过DateFormat.parse(text)把字符串解析为Date同时支持三种配置维度stylesetStylePattern如S-对应 SHORT 风格日期、ISOsetIso遵循DateTimeFormat.ISO枚举、patternsetPattern自定义如yyyy-MM-dd HH:mm:ss格式串。DateFormatter之所以被反复强调是因为它不仅是Formatter的直接实现还被DateTimeFormatAnnotationFormatterFactory内部复用见第五节形成了注解 → 工厂 → Formatter的完整链路。四、注册与调度Formatter 如何进入 Spring 体系Formatter本身只是接口要让其生效必须注册进FormatterRegistryConversionService的子接口Spring 会在需要类型转换时按目标类型查找并调用对应的 FormatterFormattingConversionService实现FormatterRegistry提供addFormatter(Formatter?)、addFormatterForFieldType(Class?, Formatter?)、addFormatterForFieldAnnotation(AnnotationFormatterFactory?)等注册入口Spring MVC 启动时会通过FormattingConversionServiceFactoryBean/WebConversionService自动装配一批内置 Formatter如DateFormatter、NumberFormatAnnotationFormatterFactory等请求到达后WebDataBinder借助ConversionService完成字符串请求参数 → 方法参数对象的转换parse被调用渲染视图时print被调用。由此可以看出Formatter在整个 Spring 类型体系中的位置它比PropertyEditor更类型安全、比底层Converter多了 Locale 国际化上下文是 Web 层格式化的首选抽象。开发者自定义类型也可以通过实现FormatterT并注入FormatterRegistry无缝接入。五、注解驱动的格式化AnnotationFormatterFactory 与 DateTimeFormat5.1 工厂接口定义类全路径org.springframework.format.AnnotationFormatterFactory。public interface AnnotationFormatterFactoryA extends Annotation { /** * The types of fields that may be annotated with the A annotation. * 字段类型 */ SetClass? getFieldTypes(); /** * Get the Printer to print the value of a field of {code fieldType} annotated with * {code annotation}. * 通过注解和字段类型获取输出接口 */ Printer? getPrinter(A annotation, Class? fieldType); /** * Get the Parser to parse a submitted value for a field of {code fieldType} * annotated with {code annotation}. * 通过注解和字段类型获取解析接口 */ Parser? getParser(A annotation, Class? fieldType); }该工厂把注解信息与字段类型作为两个维度动态决定返回哪个 Printer / Parser。当字段上标注了某注解A且字段类型命中getFieldTypes()时Spring 就会调用工厂获取对应的格式化组件从而实现了注解驱动 类型匹配的声明式格式化。5.2 日期注解工厂实现类全路径org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory它是AnnotationFormatterFactoryDateTimeFormat的典型实现支持Date、Calendar、Long三类字段static { SetClass? fieldTypes new HashSet(4); // 加入字段类型 fieldTypes.add(Date.class); fieldTypes.add(Calendar.class); fieldTypes.add(Long.class); FIELD_TYPES Collections.unmodifiableSet(fieldTypes); }其核心getFormatter方法负责组装DateFormatter读取注解上的style、iso、pattern三个配置项并映射到 Formatterprotected 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; }注意resolveEmbeddedValue工厂继承自EmbeddedValueResolutionSupport支持解析占位符如${date.pattern:yyyy-MM-dd}可以把格式化模式外置到配置文件中。getPrinter与getParser均委托给getFormatter因为DateFormatter本身同时具备双向能力一个实例即可复用。对应到实际使用实体/POJO 字段可以这样声明public class User { DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) private Date createTime; DateTimeFormat(iso DateTimeFormat.ISO.DATE) private LocalDate birthday; DateTimeFormat(style S-) private Date lastLogin; }请求绑定阶段2026-09-19 08:30:00通过DateFormatter.parse被解析为Date注入createTime渲染阶段Date对象通过DateFormatter.print按配置格式输出到视图。5.3 其他注解工厂类似机制同样存在于数字格式化中NumberFormatAnnotationFormatterFactory处理NumberFormat注解内部返回NumberFormatter。整个org.springframework.format包围绕注解 类型这一模式为日期、数字等常见类型提供了开箱即用的格式化能力。六、Joda-Time 方向的 Formatter 应用仓库补充在早期 Spring 版本中org.springframework.format.datetime.joda包还提供了一套基于 Joda-Time 的格式化实现仓库笔记中有两个代表性类可作佐证6.1 DateTimeParser字符串 → Joda DateTime类全路径org.springframework.format.datetime.joda.DateTimeParser实现ParserDateTimepublic 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); } }要点内部持有 JodaDateTimeFormatter通过JodaTimeContextHolder结合当前Locale获取适配的格式化器再调用parseDateTime完成解析。可见Parser接口对第三方时间库同样开放。6.2 MillisecondInstantPrinter时间戳 → 字符串类全路径org.springframework.format.datetime.joda.MillisecondInstantPrinter实现PrinterLongpublic 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毫秒时间戳为输入类型将其格式化为可读字符串恰好覆盖了DateTimeFormatAnnotationFormatterFactory中Long.class这一字段类型的打印需求。这两个类印证了Formatter/Printer/Parser接口族任意领域类型 Locale 感知的通用设计。七、实战自定义一个 Formatter 并接入 Spring MVC掌握了接口族结构后实现自定义格式化组件只需三步步骤 1实现 Formatter 接口public class PhoneNumberFormatter implements FormatterPhoneNumber { Override public String print(PhoneNumber object, Locale locale) { // 输出格式化138****1234 return object.getAreaCode() - object.getNumber(); } Override public PhoneNumber parse(String text, Locale locale) throws ParseException { // 解析输入138-12345678 - PhoneNumber String[] parts text.split(-); if (parts.length ! 2) { throw new ParseException(电话号码格式应为 area-number, 0); } return new PhoneNumber(parts[0], parts[1]); } }步骤 2注册到 FormatterRegistryConfiguration public class FormatConfig implements WebMvcConfigurer { Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new PhoneNumberFormatter()); } }步骤 3在 Controller 中使用GetMapping(/user) public String getUser(RequestParam(phone) PhoneNumber phone) { // 请求 /user?phone010-88888888 时phone 参数已被自动解析 return phone.getAreaCode() | phone.getNumber(); }自此Spring MVC 的参数绑定与视图渲染都会自动调用你的 Formatter与内置的DateFormatter走完全相同的调度链路。八、总结org.springframework.format.Formatter是 Spring 格式化体系的总接口它将Printer对象 → 字符串与Parser字符串 → 对象双向能力合二为一并通过FormatterRegistry/ConversionService融入 Spring MVC 的参数绑定与视图渲染流程接口层FormatterT extends PrinterT, ParserT两个父接口均为FunctionalInterface签名携带Locale以支持国际化实现层DateFormatter是FormatterDate的代表实现支持 style / ISO / pattern 三种格式配置注解驱动层AnnotationFormatterFactoryA按注解 字段类型返回 Printer/ParserDateTimeFormatAnnotationFormatterFactory为Date、Calendar、Long字段提供日期格式化扩展层Joda-Time 方向的DateTimeParser、MillisecondInstantPrinter展示了接口族对任意领域类型的适配能力。如需深入阅读仓库中的原始源码笔记可依次参阅 Spring-Formatter、Spring-Printer、Spring-Parser、Spring-AnnotationFormatterFactory 及 Spring-DateTimeFormatAnnotationFormatterFactory配合 Parser 类图 与 DateTimeFormatAnnotationFormatterFactory 类图 可快速建立整体认知。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/doocs/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考