Spring处理字段格式化的实战指南

 更新时间:2025年10月27日 10:15:29   作者:lang20150928  
本文讲解 Spring 如何在客户端环境(如 Web 应用)中处理 字段的格式化与解析,特别是字符串与对象之间的双向转换,并支持国际化(Locale),将从 整体架构、核心概念、SPI 接口、使用方式和实际应用 五个维度,帮你系统性地理解,需要的朋友可以参考下

一、整体架构:Spring 类型转换体系

Spring 提供了两套互补的类型转换机制:

模块用途特点
core.convert 包下的 Converter SPI通用类型转换(如 LongDate通用性强,不涉及字符串格式化或 Locale
format 包下的 Formatter SPI客户端字段格式化(如 StringDate,带格式和语言)面向 UI 层,支持 Locale,适合 Web 应用

它们都由一个统一的服务接口管理:
ConversionService —— 是 Spring 内部进行类型转换的核心接口。

关键点

  • Converter:用于任意类型之间转换(后台逻辑用)。
  • Formatter:专门用于 String 和目标类型之间转换(前端展示/提交用)。
  • 两者都被 ConversionService 管理,可共存。

二、核心概念:Formatter SPI

1. Formatter<T> 接口

这是 Spring 为“客户端字段格式化”设计的核心接口:

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

它由两个子接口组成:

(1) Printer<T>:对象 → 字符串(用于显示)

String print(T fieldValue, Locale locale);
  • 把 Java 对象(如 Date)按指定语言环境(Locale)格式化成字符串,用于页面展示。
  • 例如:new Date()"2025-04-05"

(2) Parser<T>:字符串 → 对象(用于解析请求)

T parse(String clientValue, Locale locale) throws ParseException;
  • 把用户提交的字符串(如表单输入)解析为 Java 对象。
  • 例如:"2025-04-05"java.util.Date

要求

  • 实现必须是 线程安全 的(因为会被多个请求共享)。
  • 解析失败应抛出 ParseException 或 IllegalArgumentException。

2. 内置 Formatter 实现(开箱即用)

Spring 提供了一些常用的 Formatter 实现:

Formatter作用
NumberStyleFormatter格式化数字(支持千分位、小数点等)
CurrencyStyleFormatter显示货币(如 ¥1,234.00)
PercentStyleFormatter百分比显示(如 50%)
DateFormatterSimpleDateFormat 格式化日期

示例:DateFormatter 实现了 print()parse(),使用 patternLocale 来格式化日期。

三、高级特性:注解驱动的格式化(Annotation-driven Formatting)

为了更方便地配置格式化规则,Spring 支持通过 注解 + 工厂模式 来绑定格式化逻辑。

1. AnnotationFormatterFactory<A extends Annotation>

这个接口的作用是:将某个注解映射到具体的 PrinterParser

public interface AnnotationFormatterFactory<A extends Annotation> {
    Set<Class<?>> getFieldTypes();        // 支持哪些字段类型?
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

示例:NumberFormatAnnotationFormatterFactory

它处理 @NumberFormat 注解:

@NumberFormat(style = Style.CURRENCY)
private BigDecimal price;
  • 当 Spring 遇到这个字段时,会查找注册的 AnnotationFormatterFactory
  • 找到后调用 getPrinter() / getParser() 获取对应的格式化工厂
  • 最终使用 CurrencyStyleFormatter 来格式化金额

2. 常见格式化注解(在 org.springframework.format.annotation 包下)

注解用途
@NumberFormat数字格式化(支持 pattern 或 style)
@DateTimeFormat日期时间格式化(支持 ISO 格式、自定义 pattern)

示例:

public class MyModel {
    @DateTimeFormat(iso = ISO.DATE)  // 格式:yyyy-MM-dd
    private Date birthDate;

    @NumberFormat(pattern = "#,###.00")
    private Double salary;
}

这样就不需要在每个 controller 里手动写 new SimpleDateFormat(...),而是统一管理。

四、注册机制 SPI:如何让 Spring 知道你的 Formatter?

Spring 提供了几个 SPI 接口来注册格式化器。

1. FormatterRegistry(核心注册中心)

它是 ConverterRegistry 的扩展,允许你注册:

void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory);

所有 formatter/converter 都要注册到这里才能生效。

2. FormattingConversionService(推荐实现类)

这是一个标准实现,实现了 ConversionService + FormatterRegistry,适合大多数场景。

你可以:

  • 编程式注册(Java Config)
  • 声明式注册(XML)

3. FormatterRegistrar(批量注册工具)

有时候你需要一次性注册多个 formatter(比如所有日期相关的),但直接注册不方便(比如类型擦除问题),这时可以用:

public interface FormatterRegistrar {
    void registerFormatters(FormatterRegistry registry);
}

常见用途:

  • 注册 Joda-Time 所有格式化器
  • 设置全局日期格式
  • 批量注册 JSR-310 (java.time) 类型的 formatter

五、实战应用:如何配置全局日期格式?

默认情况下,未标注 @DateTimeFormatDate 字段会用 DateFormat.SHORT(如 4/5/25)格式化,这通常不符合中国习惯。

目标:设置全局日期格式为 yyyyMMdd

方法一:Java 配置(推荐)

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {
        // 创建 ConversionService,但不注册默认 formatter
        DefaultFormattingConversionService service = new DefaultFormattingConversionService(false);

        // 保留 @NumberFormat 支持
        service.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // 设置全局日期格式:yyyyMMdd
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(service);

        return service;
    }
}

方法二:XML 配置(传统项目)

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="registerDefaultFormatters" value="false"/>
    <property name="formatters">
        <set>
            <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory"/>
        </set>
    </property>
    <property name="formatterRegistrars">
        <set>
            <bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
                <property name="dateFormatter">
                    <bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
                        <property name="pattern" value="yyyyMMdd"/>
                    </bean>
                </property>
            </bean>
        </set>
    </property>
</bean>

六、总结:一句话理解全文

Spring 的 Formatter SPI 是为 客户端环境(如 Web 页面)设计的一套 基于 Locale 的字符串 ↔ 对象 转换机制,相比通用的 Converter,它更适合处理用户输入和输出的格式化需求,且支持注解驱动(如 @DateTimeFormat)、可集中配置、易于扩展。

七、常见问题解答(FAQ)

Q1: Formatter 和 PropertyEditor 有什么区别?

  • PropertyEditor 是 JavaBeans 规范的老技术,线程不安全,API 不够灵活。
  • Formatter 是 Spring 3 新引入的,线程安全、强类型、支持 Locale,推荐替代 PropertyEditor

Q2: 在 Spring MVC 中怎么启用这些 formatter?

只要把自定义的 FormattingConversionService 注入到 Spring MVC 的配置中即可:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new DateFormatter("yyyyMMdd"));
    }
}

或者使用 <mvc:annotation-driven conversion-service="conversionService"/> XML 配置。

Q3: JSR-310 时间类(LocalDate、LocalDateTime)支持吗?

支持!Spring 会自动注册 java.time 类型的 formatter。你可以通过 DateTimeFormatterRegistrar 控制其格式。

如果你正在开发 Web 应用,尤其是需要处理表单提交、日期显示、货币格式等功能,那么这一套 Formatter 机制就是你应该掌握的核心技能之一。

需要我根据你的具体场景(比如 Spring Boot 项目)给出一个完整的配置示例吗?

以上就是Spring处理字段格式化的实战指南的详细内容,更多关于Spring处理字段格式化的资料请关注脚本之家其它相关文章!

相关文章

  • Java Spring AOP源码解析之事务实现原理

    Java Spring AOP源码解析之事务实现原理

    这篇文章主要为大家介绍了Java Spring AOP事务实现原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • MyBatis在Mapper中传递多个参数的四种方法详解

    MyBatis在Mapper中传递多个参数的四种方法详解

    这篇文章主要介绍了MyBatis在Mapper中传递多个参数的四种方法,文章通过代码示例介绍的非常详细,具有一定的参考价值,,需要的朋友可以参考下
    2023-09-09
  • 使用Java和WebSocket实现网页聊天室实例代码

    使用Java和WebSocket实现网页聊天室实例代码

    WebSocket是HTML5一种新的协议,它实现了浏览器与服务器全双工通信,这里就将使用WebSocket来开发网页聊天室,对Java和WebSocket实现网页聊天室的实例代码感兴趣的朋友一起学习吧
    2016-06-06
  • java读取枚举类的值转成list和map方式

    java读取枚举类的值转成list和map方式

    这篇文章主要介绍了java读取枚举类的值转成list和map方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Spring Validation数据校验详解

    Spring Validation数据校验详解

    SpringValidation是Spring框架的数据校验抽象层,支持注解、分组、嵌套校验及国际化,默认集成HibernateValidator,可自定义校验器满足复杂需求,适用于Web参数校验和业务逻辑验证
    2025-09-09
  • JAVA socket.io注解原理及用法图解

    JAVA socket.io注解原理及用法图解

    这篇文章主要介绍了JAVA socket.io注解原理及用法图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Java连接ftp服务器实例代码

    Java连接ftp服务器实例代码

    这篇文章主要介绍了Java连接ftp服务器实例代码 的相关资料,需要的朋友可以参考下
    2015-12-12
  • 浅谈SpringBoot中的@Conditional注解的使用

    浅谈SpringBoot中的@Conditional注解的使用

    这篇文章主要介绍了浅谈SpringBoot中的@Conditional注解的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04
  • java.sql.SQLException问题解决以及注意事项

    java.sql.SQLException问题解决以及注意事项

    这篇文章主要给大家介绍了关于java.sql.SQLException问题解决以及注意事项的相关资料,这个问题其实很好解决,文中通过图文将解决的办法介绍的很详细,需要的朋友可以参考下
    2023-07-07
  • Java毕业设计实战之食品溯源系统的实现

    Java毕业设计实战之食品溯源系统的实现

    这是一个使用了java+Springboot+Maven+mybatis+Vue+mysql+wd开发的食品溯源系统,是一个毕业设计的实战练习,具有食品溯源该有的所有功能,感兴趣的朋友快来看看吧
    2022-01-01

最新评论