【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(上)

简介: 【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(上)

前言


Converter只完成了数据类型的转换,却不负责输入输出数据的格式化工作,日期时间、货币等虽都以字符串形式存在,却有不同的格式。


Spring格式化框架要解决的问题是:从格式化的数据中获取真正的数据,绑定数据,将处理完成的数据输出为格式化的数据。Formatter接口就承担着这样的责任.


Converter主要是做Object与Object之间的类型转换,Formatter则是要完成任意Object与String之间的类型转换。前者适合于任何一层,而后者则主要用于web层


Formatter


org.springframework.format.Formatter顾名思义,它表示格式化。

// @since 3.0
public interface Formatter<T> extends Printer<T>, Parser<T> {
}


它自己一个方法都木有定义,因此需要看看它的两个父接口。


Printer


格式化显示接口,将T类型的对象根据Locale信息以某种格式进行打印显示(即返回字符串形式


@FunctionalInterface
public interface Printer<T> {
  String print(T object, Locale locale);
}


Parser


解析接口,根据Locale信息解析字符串到T类型的对象


@FunctionalInterface
public interface Parser<T> {
  T parse(String text, Locale locale) throws ParseException;
}


从这两个接口定义中我们可以很清晰的看出定义了两个相反的接口。代表着格式化和解析(功能上和转换器Converter还是蛮像的)


Formatter它的继承树如下:


image.png


从包结构中看:


image.png


发现Spring竟然内置了对joda的支持,可见当初joda这个包的流行的程度。但是随着Java8中的JSR310日期的普及,我预言joda必将走向死亡(毕竟亲儿子才是最好的)。因此本文涉及到joda的实现都略过,只看JSR310标准实现。


InstantFormatter


对java.time.Instant时间戳的转换和解析:(相信一般很少这么使用吧~~~)

public class InstantFormatter implements Formatter<Instant> {
  // 如果你的请求入参串为:2007-12-03T10:15:30.00Z这种格式,是可以使用Instant接收的~~~
  @Override
  public Instant parse(String text, Locale locale) throws ParseException {
    if (text.length() > 0 && Character.isDigit(text.charAt(0))) {
      // assuming UTC instant a la "2007-12-03T10:15:30.00Z"
      return Instant.parse(text);
    }
    else {
      // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
      return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
    }
  }
  // System.out.println(Instant.now())输出:2019-06-03T13:11:22.638Z
  @Override
  public String print(Instant object, Locale locale) {
    return object.toString();
  }
}


CurrencyUnitFormatter


它需要javax.money.包的支持属于`JSR-354`的内容,暂时略过


PeriodFormatter/DurationFormatter/MonthDayFormatter/YearMonthFormatter/YearFormatter/MonthFormatter


他们的实现都很简单,都是调各自的parse()和toString()方法~ 就不详细说明了


DateFormatter


注意处理Java8中JSR310日期的叫做DateTimeFormatter,但它并没有实现Formatter接口,注意区分

另外注意和java.text.DateFormat的区分,它是JDK的。而这个是Spring的~ 但是Spring的这个底层实现其实还是依赖的java.text.DateFormat


这个是最为重要的一个转换,因为Spring MVC中我们经常会使用Date来接收参数和返回,因此这个转换器个人建议有必要了解一下,非常有助于了解序列化的原理啥的~~~依赖于java.text.DateFormat来处理的。

// @since 3.0 // 处理java.util.Date 和JSR310无关
public class DateFormatter implements Formatter<Date> {
  // 使用的标准时区~
  private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
  // 因为Date包含日期、时间   所以这里表述的是各自的默认支持的模版格式~~~
  // System.out.println(new Date()); //Mon Jun 03 21:18:45 CST 2019
  // System.out.println(new Timestamp(Instant.now().toEpochMilli())); //2019-06-03 21:18:45.346
  private static final Map<ISO, String> ISO_PATTERNS;
  static {
    Map<ISO, String> formats = new EnumMap<>(ISO.class);
    formats.put(ISO.DATE, "yyyy-MM-dd");
    formats.put(ISO.TIME, "HH:mm:ss.SSSXXX");
    formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
    ISO_PATTERNS = Collections.unmodifiableMap(formats);
  }
  @Nullable
  private String pattern;
  private int style = DateFormat.DEFAULT; //FULL LONG MEDIUM SHORT  默认是MEDIUM 
  @Nullable
  private String stylePattern;
  @Nullable
  private ISO iso;
  @Nullable
  private TimeZone timeZone;
  // 指定分析是否要宽松  默认是false
  private boolean lenient = false;
  // ==========备注:上面所有参数和getDateFormat()格式化模版有关===========
  // 省略get/set方法
  public String print(Date date, Locale locale) {
    return getDateFormat(locale).format(date);
  }
  @Override
  public Date parse(String text, Locale locale) throws ParseException {
    return getDateFormat(locale).parse(text);
  }
  // ====getDateFormat()方法,就是根据上面定义的参数生成~~~
  // 1、若指定了pattern参数,那就直接使用new SimpleDateFormat(this.pattern, locale)
  // 2、若没指定,那就根据配置项,DateFormat.getXXXInstance()...
}


Demo如下:


    public static void main(String[] args) {
        Date date = new Date();
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
        DateFormatter dateFormatter = new DateFormatter();
        System.out.println(dateFormatter.print(date, Locale.CHINA)); //2019-6-3
        System.out.println(dateFormatter.print(timestamp, Locale.CHINA)); //2019-6-3
        dateFormatter.setIso(DateTimeFormat.ISO.DATE_TIME);
        //dateFormatter.setStyle(DateFormat.FULL);
        System.out.println(dateFormatter.print(date, Locale.CHINA)); //2019-06-03T13:28:44.252Z
        System.out.println(dateFormatter.print(timestamp, Locale.CHINA)); //2019-06-03T13:28:44.252Z
    }


AbstractNumberFormatter

对java.lang.Number进行格式化。依赖于java.text.NumberFormat来处理的,java.text.DecimalFormat是它的子类。


CurrencyStyleFormatter

以BigDecimal的格式来处理数字,当作钱币处理。


// @since 4.2
public class CurrencyStyleFormatter extends AbstractNumberFormatter {
  private int fractionDigits = 2; // 默认保留两位小数点
  @Nullable
  private RoundingMode roundingMode; // 四舍五入
  @Nullable
  private Currency currency; // 货币 java.util.Currency
  // 例如:#,#00.0# --> 1,234.56
  @Nullable
  private String pattern;
  @Override
  public BigDecimal parse(String text, Locale locale) throws ParseException {
    BigDecimal decimal = (BigDecimal) super.parse(text, locale);
    // 对结果做四舍五入处理~~~~~~~~~~~
    if (this.roundingMode != null) {
      decimal = decimal.setScale(this.fractionDigits, this.roundingMode);
    } else {
      decimal = decimal.setScale(this.fractionDigits);
    }
    return decimal;
  }
}


Demo:

    public static void main(String[] args) throws ParseException {
        String curr = "1,234.56";
        CurrencyStyleFormatter formatter = new CurrencyStyleFormatter();
        //formatter.setRoundingMode(RoundingMode.DOWN);
        formatter.setPattern("#,#00.0#"); // 若不设置格式 抛错ParseException
        System.out.println(formatter.parse(curr, Locale.CHINA)); //1234.56
    }


PercentStyleFormatter


对百分数进行格式化,@since 4.2

    public static void main(String[] args) throws ParseException {
        String curr = "12%";
        PercentStyleFormatter formatter = new PercentStyleFormatter();
        System.out.println(formatter.parse(curr, Locale.CHINA)); //0.12
        System.out.println(formatter.print(0.12, Locale.CHINA)); //12%
    }

NumberStyleFormatter


数字的格式进行转换,也可以指定pattern

Demo:


    public static void main(String[] args) throws ParseException {
        String curr = "12,000.1567";
        NumberStyleFormatter formatter = new NumberStyleFormatter();
        formatter.setPattern("#,#00.0#");
        System.out.println(formatter.parse(curr, Locale.CHINA)); //12000.1567
        System.out.println(formatter.print(0.12, Locale.CHINA)); // 00.12 看这格式化的威力
    }


以上。

其中最为主要的是Date的转换,以及对Number的转换(它可以转为货币、百分比、数字


FormatterRegistry


从接口继承关系中可以看出,它既可以注册格式化器,又可议注册转换器


// @since 3.0
public interface FormatterRegistry extends ConverterRegistry {
  void addFormatter(Formatter<?> formatter);
  void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
  // 单独指定Printer和parser也是被允许的
  void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
  // 注册处理注解的格式化器~~~~~ AnnotationFormatterFactory的实现类~~
  void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}


FormattingConversionService



// @since 3.0  它继承自GenericConversionService ,所以它能对Converter进行一系列的操作~~~
// 实现了接口FormatterRegistry,所以它也可以注册格式化器了
// 实现了EmbeddedValueResolverAware,所以它还能有非常强大的功能:处理占位
public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware {
  @Nullable
  private StringValueResolver embeddedValueResolver;
  private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters = new ConcurrentHashMap<>(64);
  private final Map<AnnotationConverterKey, GenericConverter> cachedParsers = new ConcurrentHashMap<>(64);
  // 最终也是交给addFormatterForFieldType去做的
  // getFieldType:它会拿到泛型类型。并且支持DecoratingProxy~~~
  @Override
  public void addFormatter(Formatter<?> formatter) {
    addFormatterForFieldType(getFieldType(formatter), formatter);
  }
  // 存储都是分开存储的  读写分离
  // PrinterConverter和ParserConverter都是一个GenericConverter  采用内部类实现的~~~  this代表一个ConversionService
  // 注意:他们的ConvertiblePair必有一个类型是String.class
  // Locale一般都可以这么获取:LocaleContextHolder.getLocale()
  // 最终parse出来的result有可能也会交给conversionService.convert()  若类型能够匹配上的话
  @Override
  public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
    addConverter(new PrinterConverter(fieldType, formatter, this));
    addConverter(new ParserConverter(fieldType, formatter, this));
  }
  // 哪怕你是一个AnnotationFormatterFactory,最终也是被适配成了GenericConverter(ConditionalGenericConverter)
  @Override
  public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
    Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
    // 若你自定义的实现了EmbeddedValueResolverAware接口,还可以使用占位符哟~~~~
    // AnnotationFormatterFactory是下面的重点内容~~~~
    if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
      ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
    }
    // 对每一种字段的type  都注册一个AnnotationPrinterConverter去处理~~~~~
    // AnnotationPrinterConverter是一个ConditionalGenericConverter
    // matches方法为:sourceType.hasAnnotation(this.annotationType);
    // 这个判断是呼应的:因为annotationFormatterFactory只会作用在指定的字段类型上的~~~不符合类型条件的不用添加
    Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
    for (Class<?> fieldType : fieldTypes) {
      addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
      addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
    }
  }
  ...
}


image.png


DefaultFormattingConversionService


实际使用时,基本就是使用它。它的模式属于默认模式:就是注册了一些常用的,默认支持的转换器们。


public class DefaultFormattingConversionService extends FormattingConversionService {
  // 再一次看出来joda这个库的成功啊~~~
  private static final boolean jsr354Present;
  private static final boolean jodaTimePresent;
  static {
    ClassLoader classLoader = DefaultFormattingConversionService.class.getClassLoader();
    jsr354Present = ClassUtils.isPresent("javax.money.MonetaryAmount", classLoader);
    jodaTimePresent = ClassUtils.isPresent("org.joda.time.LocalDate", classLoader);
  }
  public DefaultFormattingConversionService(
      @Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) {
    if (embeddedValueResolver != null) {
      setEmbeddedValueResolver(embeddedValueResolver);
    }
    // 由此可见,它是DefaultConversionService的超集,比它强大得多的~~~
    DefaultConversionService.addDefaultConverters(this);
    if (registerDefaultFormatters) {
      addDefaultFormatters(this);
    }
  }
  // 默认添加进去的格式化器们~~~~
  public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
    // Default handling of number values
    // 支持@NumberFormat注解~~~~~对数字进行格式化~
    formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
    // Default handling of monetary values
    // JSR354使用较少~略过  银行、金融项目使用多~
    if (jsr354Present) {
      formatterRegistry.addFormatter(new CurrencyUnitFormatter());
      formatterRegistry.addFormatter(new MonetaryAmountFormatter());
      formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
    }
    // Default handling of date-time values
    // just handling JSR-310 specific date and time types
    // 对JSR310的转换的支持 DateTimeFormatterRegistrar是一个FormatterRegistrar
    new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);
    // 如没有导入joda的包  那就默认使用Date吧~~~~~
    if (jodaTimePresent) {
      // handles Joda-specific types as well as Date, Calendar, Long
      new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
    } else {
      // regular DateFormat-based Date, Calendar, Long converters
      new DateFormatterRegistrar().registerFormatters(formatterRegistry);
    }
  }
}


Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):

  1. DefaultConversionService:默认的类型转换服务实现;
  2. DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。


备注:自定义Converter的场景其实蛮多的,比如最常见的StringToPhoneNumberConverter它俩的互转,就可以定义个转换器,支持中间空格的电话号码格式~



相关文章
|
11月前
|
前端开发 Java Spring
《Spring MVC》 第六章 MVC类型转换器、格式化器
《Spring MVC》 第六章 MVC类型转换器、格式化器
149 0
|
12月前
|
前端开发 Java Spring
Spring MVC-06循序渐进之Converter和Formatter
Spring MVC-06循序渐进之Converter和Formatter
58 0
|
前端开发 Java uml
Spring官网阅读(十五)Spring中的格式化(Formatter)
在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们考虑这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支撑我们的需求了。这个时候,格式化的作用就很明显了,这篇文章我们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.5及3.6小结
241 0
Spring官网阅读(十五)Spring中的格式化(Formatter)
|
Java API Spring
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
|
存储 前端开发 安全
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)
|
XML 前端开发 Java
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
|
2天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
57 0
|
2天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
138 0
|
2天前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
48 2