8. 格式化器大一统 -- Spring的Formatter抽象(下)

简介: 8. 格式化器大一统 -- Spring的Formatter抽象(下)
  • DateTimeFormatterFactoryBean


顾名思义,DateTimeFormatterFactory用于生成一个DateTimeFormatter实例,而本类用于把生成的Bean放进IoC容器内,完成和Spring容器的整合。客气的是,它直接继承自DateTimeFormatterFactory,从而自己同时就具备这两项能力:


  1. 生成DateTimeFormatter实例
  2. 将该实例放进IoC容器


多说一句:虽然这个工厂Bean非常简单,但是它释放的信号可以作为编程指导:


  1. 一个应用内,对日期、时间的格式化尽量只存在1种模版规范。比如我们可以向IoC容器里扔进去一个模版,需要时注入进来使用即可  

            注意:这里指的应用内,一般不包含协议转换层使用的模版规范。如Http协议层可以使用自己单独的一套转换模版机制

  1. 日期时间模版不要在每次使用时去临时创建,而是集中统一创建好管理起来(比如放IoC容器内),这样维护起来方便很多


说明:DateTimeFormatterFactoryBean这个API在Spring内部并未使用,这是Spring专门给使用者用的,因为Spring也希望你这么去做从而把日期时间格式化模版管理起来


代码示例


@Test
public void test1() {
    // DateTimeFormatterFactory dateTimeFormatterFactory = new DateTimeFormatterFactory();
    // dateTimeFormatterFactory.setPattern("yyyy-MM-dd HH:mm:ss");
    // 执行格式化动作
    System.out.println(new DateTimeFormatterFactory("yyyy-MM-dd HH:mm:ss").createDateTimeFormatter().format(LocalDateTime.now()));
    System.out.println(new DateTimeFormatterFactory("yyyy-MM-dd").createDateTimeFormatter().format(LocalDate.now()));
    System.out.println(new DateTimeFormatterFactory("HH:mm:ss").createDateTimeFormatter().format(LocalTime.now()));
    System.out.println(new DateTimeFormatterFactory("yyyy-MM-dd HH:mm:ss").createDateTimeFormatter().format(ZonedDateTime.now()));
}


运行程序,输出:

2020-12-26 22:44:44
2020-12-26
22:44:44
2020-12-26 22:44:44


说明:虽然你也可以直接使用DateTimeFormatter#ofPattern()静态方法得到一个实例,但是 若在Spring环境下使用它我还是建议使用Spring提供的工厂类来创建,这样能保证统一的编程体验,B格也稍微高点。


使用建议:以后对日期时间类型(包括JSR310类型)就不要自己去写原生的SimpleDateFormat/DateTimeFormatter了,建议可以用Spring包装过的DateFormatter/DateTimeFormatterFactory,使用体验更佳。


数字格式化


通过了上篇文章的学习之后,对数字的格式化就一点也不陌生了,什么数字、百分数、钱币等都属于数字的范畴。Spring提供了AbstractNumberFormatter抽象来专门处理数字格式化议题:


public abstract class AbstractNumberFormatter implements Formatter<Number> {
  ...
  @Override
  public String print(Number number, Locale locale) {
    return getNumberFormat(locale).format(number);
  }
  @Override
  public Number parse(String text, Locale locale) throws ParseException {
    // 伪代码,核心逻辑就这一句
    return getNumberFormat.parse(text, new ParsePosition(0));
  }
  // 得到一个NumberFormat实例
  protected abstract NumberFormat getNumberFormat(Locale locale);
  ...
}

这和DateFormatter的实现模式何其相似,简直一模一样:底层实现依赖于(委托给)java.text.NumberFormat去完成。


image.png


此抽象类共有三个具体实现:


  • NumberStyleFormatter:数字格式化,如小数,分组等
  • PercentStyleFormatter:百分数格式化
  • CurrencyStyleFormatter:钱币格式化


数字格式化


NumberStyleFormatter使用NumberFormat的数字样式的通用数字格式化程序。可定制化参数为:pattern。核心源码如下:


NumberStyleFormatter:
  @Override
  public NumberFormat getNumberFormat(Locale locale) {
    NumberFormat format = NumberFormat.getInstance(locale);
    ...
    // 解析时,永远返回BigDecimal类型
    decimalFormat.setParseBigDecimal(true);
    // 使用格式化模版
    if (this.pattern != null) {
      decimalFormat.applyPattern(this.pattern);
    }
    return decimalFormat;
  }


代码示例:

@Test
public void test2() throws ParseException {
    NumberStyleFormatter formatter = new NumberStyleFormatter();
    double myNum = 1220.0455;
    System.out.println(formatter.print(myNum, Locale.getDefault()));
    formatter.setPattern("#.##");
    System.out.println(formatter.print(myNum, Locale.getDefault()));
    // 转换
    // Number parsedResult = formatter.parse("1,220.045", Locale.getDefault()); // java.text.ParseException: 1,220.045
    Number parsedResult = formatter.parse("1220.045", Locale.getDefault());
    System.out.println(parsedResult.getClass() + "-->" + parsedResult);
}


运行程序,输出:


1,220.045
1220.05
class java.math.BigDecimal-->1220.045


  1. 可通过setPattern()指定数字格式化的模版(一般建议显示指定)
  2. parse()方法返回的是BigDecimal类型,从而保证了数字精度


百分数格式化


PercentStyleFormatter表示使用百分比样式去格式化数字。核心源码(其实是全部源码)如下:


PercentStyleFormatter:
  @Override
  protected NumberFormat getNumberFormat(Locale locale) {
    NumberFormat format = NumberFormat.getPercentInstance(locale);
    if (format instanceof DecimalFormat) {
      ((DecimalFormat) format).setParseBigDecimal(true);
    }
    return format;
  }


这个就更简单啦,pattern模版都不需要指定。代码示例:


@Test
public void test3() throws ParseException {
    PercentStyleFormatter formatter = new PercentStyleFormatter();
    double myNum = 1220.0455;
    System.out.println(formatter.print(myNum, Locale.getDefault()));
    // 转换
    // Number parsedResult = formatter.parse("1,220.045", Locale.getDefault()); // java.text.ParseException: 1,220.045
    Number parsedResult = formatter.parse("122,005%", Locale.getDefault());
    System.out.println(parsedResult.getClass() + "-->" + parsedResult);
}


运行程序,输出:

122,005%
class java.math.BigDecimal-->1220.05


百分数的格式化不能指定pattern,差评。


钱币格式化


使用钱币样式格式化数字,使用java.util.Currency来描述货币。代码示例:


@Test
public void test3() throws ParseException {
    CurrencyStyleFormatter formatter = new CurrencyStyleFormatter();
    double myNum = 1220.0455;
    System.out.println(formatter.print(myNum, Locale.getDefault()));
    System.out.println("--------------定制化--------------");
    // 指定货币种类(如果你知道的话)
    // formatter.setCurrency(Currency.getInstance(Locale.getDefault()));
    // 指定所需的分数位数。默认是2
    formatter.setFractionDigits(1);
    // 舍入模式。默认是RoundingMode#UNNECESSARY
    formatter.setRoundingMode(RoundingMode.CEILING);
    // 格式化数字的模版
    formatter.setPattern("#.#¤¤");
    System.out.println(formatter.print(myNum, Locale.getDefault()));
    // 转换
    // Number parsedResult = formatter.parse("¥1220.05", Locale.getDefault());
    Number parsedResult = formatter.parse("1220.1CNY", Locale.getDefault());
    System.out.println(parsedResult.getClass() + "-->" + parsedResult);
}


运行程序,输出:


¥1,220.05
--------------定制化--------------
1220.1CNY
class java.math.BigDecimal-->1220.1


值得关注的是:这三个实现在Spring 4.2版本之前是“耦合”在一起。直到4.2才拆开,职责分离。


✍总结



本文介绍了Spring的Formatter抽象,让格式化器大一统。这就是Spring最强能力:API设计、抽象、大一统。


Converter可以从任意源类型,转换为任意目标类型。而Formatter则是从String类型转换为任务目标类型,有点类似PropertyEditor。可以感觉出Converter是Formater的超集,实际上在Spring中Formatter是被拆解成PrinterConverter和ParserConverter,然后再注册到ConverterRegistry,供后续使用。


关于格式化器的注册中心、注册员,这就是下篇文章内容喽,欢迎保持持续关注。


♨本文思考题♨


看完了不一定懂,看懂了不一定记住,记住了不一定掌握。来,文末3个思考题帮你复盘:


  1. Spring为何没有针对JSR310时间类型提供专用转换器实现?
  2. Spring内建众多Formatter实现,如何管理?
  3. 格式化器Formatter和转换器Converter是如何整合到一起的?
相关文章
|
存储 缓存 监控
【深入浅出Spring原理及实战】「缓存Cache开发系列」带你深入分析Spring所提供的缓存Cache抽象详解的核心原理探索
缓存的工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据,并将数据存入缓存中。通常情况下,我们会将那些经常读取且不经常修改的数据或昂贵(CPU/IO)的且对于相同请求有相同计算结果的数据存储到缓存中。
192 1
|
前端开发 Java Spring
《Spring MVC》 第六章 MVC类型转换器、格式化器
《Spring MVC》 第六章 MVC类型转换器、格式化器
190 0
|
前端开发 Java Spring
Spring MVC-06循序渐进之Converter和Formatter
Spring MVC-06循序渐进之Converter和Formatter
87 0
|
XML 缓存 监控
Spring Cache抽象-基于XML的配置声明(基于EhCache的配置)
Spring Cache抽象-基于XML的配置声明(基于EhCache的配置)
91 0
|
XML 缓存 监控
Spring Cache抽象-基于XML的配置声明(基于ConcurrentMap的配置)
Spring Cache抽象-基于XML的配置声明(基于ConcurrentMap的配置)
94 0
|
缓存 Java Spring
Spring Cache抽象-使用SpEL表达式
Spring Cache抽象-使用SpEL表达式
262 0
|
缓存 Java Spring
Spring Cache抽象-缓存管理器
Spring Cache抽象-缓存管理器
112 0
|
存储 缓存 Java
Spring Cache抽象-使用Java类注解的方式整合EhCache
Spring Cache抽象-使用Java类注解的方式整合EhCache
80 0
|
存储 缓存 搜索推荐
Spring Cache抽象-缓存注解
Spring Cache抽象-缓存注解
118 0
|
XML 安全 Java
Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现(1)
Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现(1)
222 0
Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现(1)