尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring MillisecondInstantPrinter 源码解析:Joda-Time 毫秒时间戳的格式化打印机制

Spring MillisecondInstantPrinter 源码解析:Joda-Time 毫秒时间戳的格式化打印机制 Spring MillisecondInstantPrinter 源码解析Joda-Time 毫秒时间戳的格式化打印机制【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunterMillisecondInstantPrinter 是 Spring 格式化体系中针对 Joda-Time 时间戳的打印器负责将Long类型的毫秒时间戳按照指定DateTimeFormatter模板格式化为可读字符串。本文结合 Springorg.springframework.format包下的 Printer/Formatter/AnnotationFormatterFactory 接口族从类定义、构造注入、打印调用链到与DateTimeFormat注解及JodaTimeContextHolder的协作关系完整还原毫秒时间戳格式化输出的底层实现并给出可直接落地的使用方式。一、类定位时间格式化体系中的输出端在 Spring 的格式化Format体系中存在两个对称的抽象角色它们共同构成对象 ⇄ 字符串的双向转换PrinterT负责把对象打印成字符串输出端ParserT负责把字符串解析回对象输入端。MillisecondInstantPrinter正是Printer 接口的一个具体实现它专门针对 Joda-Time 时间库中以毫秒时间戳long/Long表示的时刻做格式化输出。1.1 类基本信息类全路径org.springframework.format.datetime.joda.MillisecondInstantPrinter所属包org.springframework.format.datetime.joda即 Spring 对 Joda-Time 的格式化适配包实现接口PrinterLong泛型参数固定为Long说明该 Printer 只能处理毫秒级时间戳long类型在装箱后即为Long类修饰符public final class不可被继承属于典型的工具型/适配型实现1.2 Printer 接口的契约MillisecondInstantPrinter所实现的接口定义在 docs/Spring/clazz/format/Spring-Printer.md 中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); }接口契约非常清晰输入一个T类型的对象和当前用户的Locale输出一个字符串。MillisecondInstantPrinter将T具体化为Long因此它的职责就是把毫秒时间戳instant与locale组合成一段格式化后的日期时间文本。补充说明PrinterT与ParserT合并后就是FormatterT接口定义见 docs/Spring/clazz/format/Spring-Formatter.md日常开发中最常见的DateFormatter便实现了FormatterDate。二、源码逐行解析MillisecondInstantPrinter的完整源码位于 docs/Spring/clazz/format/Printer/Spring-MillisecondInstantPrinter.md全文如下public final class MillisecondInstantPrinter implements PrinterLong { private final DateTimeFormatter formatter; /** * Create a new ReadableInstantPrinter. * param formatter the Joda DateTimeFormatter instance */ 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); } }整个类只有两个核心成员一个DateTimeFormatter字段和一个print方法。下面逐一拆解。2.1 构造器模板注入一次成型private final DateTimeFormatter formatter; public MillisecondInstantPrinter(DateTimeFormatter formatter) { this.formatter formatter; }字段被final修饰且通过构造器注入说明Printer 一旦创建其格式化模板即固定不变这是不可变immutable设计天然线程安全可被多个线程安全共享。注入的formatter是 Joda-Time 的org.joda.time.format.DateTimeFormatter负责真正定义输出格式如yyyy-MM-dd HH:mm:ss。构造器注释中保留了ReadableInstantPrinter的旧注释说明该类在历史上与ReadableInstant家族的 Printer 同源演进Joda-Time 中DateTime、Instant等均实现了ReadableInstant接口而从实现看它最终被改造为直接面向毫秒时间戳Long输出。2.2 print 方法委托 Joda-Time 完成核心格式化Override public String print(Long instant, Locale locale) { // DateTimeFormatter .print return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(instant); }print方法内部实际做了两件事按 Locale 适配 Formatter调用JodaTimeContextHolder.getFormatter(this.formatter, locale)根据当前线程的 Joda-Time 上下文JodaTimeContext与目标locale重新组装一个适合该语言环境的DateTimeFormatter真正打印调用 Joda-TimeDateTimeFormatter#print(Object)把Long时间戳渲染成字符串。这里有两个值得深挖的细节为什么先经过JodaTimeContextHolder因为 Joda-Time 的格式化可能受线程级上下文影响例如通过JodaTimeContextHolder.setJodaTimeContext()设置的用户时区DateTimeZone、Chronology等。getFormatter会把上下文中的时区/纪年信息织入当前 formatter再配合传入的locale调整语言保证输出符合当前用户环境而不是永远使用默认时区。这正是 Spring 对 Joda-Time 封装的价值所在。print(Object)的重载行为Joda-Time 的DateTimeFormatter提供了print(long instant)、print(ReadableInstant)等重载当传入的Long被拆箱为基本类型long时走的是按毫秒时间戳直接打印的路径无需构造Instant对象性能更优。对称地看同一包下的 DateTimeParser 实现了ParserDateTime其parse方法同样借助JodaTimeContextHolder.getFormatter(this.formatter, locale).parseDateTime(text)将字符串解析回DateTime。两者一打印一解析配合Formatter即可完成 Joda 时间对象的双向转换。三、JodaTimeContextHolderlocale 与线程上下文的适配器JodaTimeContextHolder是 Spring 在 Joda-Time 封装层中的关键基础设施MillisecondInstantPrinter每次打印都要经由它。其核心职责可概括为维护一个线程绑定的JodaTimeContext内部持有可覆盖的DateTimeZone、Chronology等提供getFormatter(DateTimeFormatter formatter, Locale locale)若当前线程上下文携带了时区/纪年配置则对传入的 formatter 进行withZone()/withChronology()等克隆式覆盖再应用locale通过formatter.withLocale(locale)最终返回一个全新的、上下文感知的formatter 实例由于 Joda-Time 的DateTimeFormatter是不可变对象所有覆盖操作都返回新实例因此这里的适配过程不会污染共享的原始 formatter多线程下依然安全。正是这一层适配让MillisecondInstantPrinter输出的字符串能随locale动态切换语言风格如中文环境输出2026-09-12、美式英文环境可能输出9/12/26等同时尊重用户在JodaTimeContext中设定的时区。四、在 Spring 格式化体系中的定位从注解到 Printer 的完整链路要理解MillisecondInstantPrinter何时被创建、何时被调用需要把它放回 Spring 的注解格式化工厂体系中。4.1 AnnotationFormatterFactory注解驱动的工厂契约Spring 通过AnnotationFormatterFactoryA extends Annotation接口把字段上的注解与对应的 Printer/Parser桥接起来接口定义见 docs/Spring/clazz/format/Spring-AnnotationFormatterFactory.mdpublic interface AnnotationFormatterFactoryA extends Annotation { // 允许被该注解标注的字段类型集合 SetClass? getFieldTypes(); // 通过注解和字段类型获取输出接口 Printer? getPrinter(A annotation, Class? fieldType); // 通过注解和字段类型获取解析接口 Parser? getParser(A annotation, Class? fieldType); }其中getFieldTypes()限定了该注解能作用在哪些字段类型上getPrinter/getParser则按需返回格式化器。4.2 DateTimeFormatAnnotationFormatterFactory毫秒时间戳的入口在 Spring 中DateTimeFormat注解由 DateTimeFormatAnnotationFormatterFactory 处理。该工厂继承自EmbeddedValueResolutionSupport实现AnnotationFormatterFactoryDateTimeFormat其静态初始化块明确声明了可注解的字段类型static { SetClass? fieldTypes new HashSet(4); // 加入字段类型 fieldTypes.add(Date.class); fieldTypes.add(Calendar.class); fieldTypes.add(Long.class); FIELD_TYPES Collections.unmodifiableSet(fieldTypes); }注意Long.class被显式包含在内——这正是MillisecondInstantPrinter发挥作用的应用场景当一个 POJO 字段是Long类型的毫秒时间戳并被DateTimeFormat注解修饰时Spring 格式化服务便可能为该字段装配面向 Joda 毫秒时间戳的 Printer。工厂通过getFormatter(annotation, fieldType)组装DateFormatter核心逻辑为通过resolveEmbeddedValue(annotation.style())解析占位符得到 style若 style 非空则formatter.setStylePattern(style)对应DateTimeFormat(styleS-)这类写法formatter.setIso(annotation.iso())设置 ISO 模式如ISO.DATE_TIME通过resolveEmbeddedValue(annotation.pattern())解析得到 pattern若 pattern 非空则formatter.setPattern(pattern)对应DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)。即pattern/iso/style 三选一或组合决定了最终输出模板而MillisecondInstantPrinter持有的DateTimeFormatter正是从这套模板规则中派生出来的。4.3 典型的字段声明示例public class Event { // 毫秒时间戳 Joda DateTimeFormatter 模板 DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss) private Long startTimeMillis; // 省略 getter/setter }当 Spring MVC 绑定请求参数到Event时格式化注册表会命中字段上的DateTimeFormat通过DateTimeFormatAnnotationFormatterFactory生成对应的 Printer内部持有按 pattern 构建的 JodaDateTimeFormatter即MillisecondInstantPrinter的同源形态输出阶段调用print(Long instant, Locale locale)得到2026-09-12 15:30:00这样的可读字符串。需要说明的是MillisecondInstantPrinter位于org.springframework.format.datetime.joda包面向Joda-Time 库而DateTimeFormatAnnotationFormatterFactory默认生成的是DateFormatter面向java.util.Date。两者通过注解声明 Formatter 注册解耦——在启用 Joda 格式化注册器JodaTimeFormatterRegistrar的环境中毫秒时间戳的打印任务即可由MillisecondInstantPrinter这类实现承接。具体装配方式取决于项目引入的是标准DateTimeFormatterRegistrar还是 Joda 变体。五、如何注册与启用让 Printer 真正生效MillisecondInstantPrinter不会自动生效它需要被注册进 Spring 的FormatterRegistry通常由FormattingConversionService实现才能在数据绑定、消息转换等场景中被发现。典型的 Java 配置如下Configuration public class DateTimeConfig implements WebMvcConfigurer { Override public void addFormatters(FormatterRegistry registry) { // 启用 Joda-Time 格式化注册器 JodaTimeFormatterRegistrar registrar new JodaTimeFormatterRegistrar(); registrar.setUseIsoFormat(true); // 使用 ISO 8601 风格 // registrar.setDateTimeStyle(MM-dd); // 或指定 style // registrar.setDateTimeFormatter(new DateTimeFormatterBuilder() // .appendPattern(yyyy-MM-dd HH:mm:ss).toFormatter()); registrar.registerFormatters(registry); } }注册器内部会为Long毫秒时间戳、DateTime、LocalDate等 Joda 类型分别装配 Printer 与 Parser——其中面向Long的 Printer 实现即为MillisecondInstantPrinter及其同族的ReadableInstantPrinter等。注册完成之后表单/URL 参数绑定到Long字段时会先经 Parser 解析模型渲染输出时调用 Printer 的print完成展示格式化。若使用 Spring Boot则只需在classpath引入joda-time依赖并配置spring.mvc.format.date或直接以DateTimeFormatterRegistrar风格配置spring.mvc.format.date-time即可Spring Boot 的WebMvcAutoConfiguration会自动装配对应的格式化注册器。六、设计要点与使用注意事项6.1 设计要点总结设计点说明不可变设计formatter为final构造注入创建后不可变线程安全、可安全共享关注点分离只负责打印解析交给同包的DateTimeParser职责单一上下文感知借助JodaTimeContextHolder注入locale与线程时区上下文接口对齐面向PrinterLong把 Joda 能力适配进 Spring 统一格式化抽象注解驱动与DateTimeFormat工厂协作字段声明即得格式化能力6.2 注意事项MillisecondInstantPrinter面向的是毫秒级时间戳若你的字段存的是秒级时间戳如部分第三方接口返回的 10 位数字直接交给该 Printer 会得到错误的输出时间应先换算为毫秒Locale参数由框架在调用时传入如 Spring MVC 根据LocaleResolver解析出的当前语言环境无需手动指定若需强制固定格式可在注册时通过自定义DateTimeFormatter指定 pattern由于 Joda-Time 已逐步被java.time取代若项目不使用 Joda-Time可忽略joda包下的实现转而使用 Spring 对java.time的DateTimeFormatterRegistrar如LocalDateTime的打印但理解MillisecondInstantPrinter的委托模型有助于举一反三。七、小结MillisecondInstantPrinter虽然只有二十余行代码却是 Spring 时间格式化体系输出端的一个典型缩影它通过构造器注入 Joda 的DateTimeFormatter在print中借助JodaTimeContextHolder完成 locale 与时区上下文的适配最终把Long毫秒时间戳渲染成可读字符串。与同包的 DateTimeParser、上层的 DateTimeFormatAnnotationFormatterFactory 以及 Printer/Formatter 接口族 一起构成了从注解声明 → 工厂装配 → Printer 打印/Parser 解析的完整闭环。理解了这条链路你在面对任何对象与字符串互转的 Spring 场景时都能快速定位到正确的实现类并预判其行为。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表