FormatterRegistrar和
要使用Formatter,除了将其配置在FormattingConversionServiceFactoryBean的formatters属性中外,还可以FormatterRegistrar注册进去。下面看到FormattingConversionServiceFactoryBean的时候会很清晰:
Registers {@link Converter Converters} and {@link Formatter Formatters} with a FormattingConversionService through the {@link FormatterRegistry} SPI.
java doc里说它是一个注册Converter和Formatter的SPI
public interface FormatterRegistrar { // Register Formatters and Converters with a FormattingConversionService through a FormatterRegistry SPI. void registerFormatters(FormatterRegistry registry); }
JodaTimeFormatterRegistrar:
格式化joda的LocalDate、LocalTime、LocalDateTime、ReadableInstant、Period…等等
DateTimeFormatterRegistrar:
对JSR310的那些时间类进行支持。包括:LocalDateTime、ZonedDateTime、OffsetDateTime、OffsetTime等等
@since 4.0。各种内部转换请参见:DateTimeConverters.registerConverters(registry);
DateFormatterRegistrar:
单词上注意和DateTimeFormatterRegistrar的区别~~
这个和@DateTimeFormat也有关系,内部依赖的是上面说到的DateFormatter。
public class DateFormatterRegistrar implements FormatterRegistrar { @Override public void registerFormatters(FormatterRegistry registry) { addDateConverters(registry); // 它是个静态方法 // 对`@DateTimeFormat`的支持~~~~~ // 所以如果你导入了joda包,这个注解可能会失效的~~~~需要特别注意~~~~~~~~~~~ 但下面的DateToLongConverter之类的依旧好使~ // 但是你导入的是JSR310 没有这个问题~~~~ registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory()); // In order to retain back compatibility we only register Date/Calendar // types when a user defined formatter is specified (see SPR-10105) // 如果你指定了dateFormatter,那么注册它 也来处理Calendar以及Date if (this.dateFormatter != null) { registry.addFormatter(this.dateFormatter); registry.addFormatterForFieldType(Calendar.class, this.dateFormatter); } } // 注意:这些converter全部为内部类实现~~~~ public static void addDateConverters(ConverterRegistry converterRegistry) { converterRegistry.addConverter(new DateToLongConverter()); converterRegistry.addConverter(new DateToCalendarConverter()); converterRegistry.addConverter(new CalendarToDateConverter()); converterRegistry.addConverter(new CalendarToLongConverter()); converterRegistry.addConverter(new LongToDateConverter()); converterRegistry.addConverter(new LongToCalendarConverter()); } }
AnnotationFormatterFactory
它是一个工厂,专门创建出处理(格式化)指定字段field上标注有指定注解的。(Spring内助了两个常用注解:@DateTimeFormat和@NumberFormat)
我们常说的,要自定义注解来处理参数的格式化,就需要实现接口来自定义一个处理类。
// @since 3.0 public interface AnnotationFormatterFactory<A extends Annotation> { // 此注解 可以作用的字段的类型~~~比如@DateTimeFormat只能作用域Date、Calendar、Long类型上~ 标注在被的类型上无效~~~ Set<Class<?>> getFieldTypes(); // 对标注有指定注解的字段进行格式化输出~~ Printer<?> getPrinter(A annotation, Class<?> fieldType); // 对标注有指定注解的字段进行格式化解析~~~ Parser<?> getParser(A annotation, Class<?> fieldType); }
AnnotationFormatterFactory的继承树如下,可以看到Spring 给我们内置了一些处理器的:
总的来说是支持了数值和日期类型(Date和JSR310、甚至joda)
NumberFormatAnnotationFormatterFactory
处理@NumberFormat
对数字进行格式化。
// 还继承自EmbeddedValueResolutionSupport,所以有resolveEmbeddedValue()方法,能够处理占位符 public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactory<NumberFormat> { // 处理Byte、Short、Integer、Long、Float、Double、BigInteger、BigDecimal等类型~~~ @Override public Set<Class<?>> getFieldTypes() { return NumberUtils.STANDARD_NUMBER_TYPES; } @Override public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation); } @Override public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation); } // 可以看到,根据Style不同,返回的格式化器也是不同的~~~~ // 显然pattern非常强大,支持到了占位符,el取值~~~ private Formatter<Number> configureFormatterFrom(NumberFormat annotation) { String pattern = resolveEmbeddedValue(annotation.pattern()); // 若指定了pattern,此处可以看出:直接当做数字处理NumberStyleFormatter if (StringUtils.hasLength(pattern)) { return new NumberStyleFormatter(pattern); } // 可能是钱币、百分比、数字 注意:都是使用的默认处理方式了~~~~ // @NumberFormat并不支持自定义 比如保留小数位、四舍五入等等 else { Style style = annotation.style(); if (style == Style.CURRENCY) { return new CurrencyStyleFormatter(); } else if (style == Style.PERCENT) { return new PercentStyleFormatter(); } else { return new NumberStyleFormatter(); } } } }
@NumberFormat是用来验证输入的数字格式。比如一般我们这样来格式化数值:@NumberFormat(pattern="#,###.##")
@NumberFormat注解内容:
// @since 3.0 类比效果参见:java.text.NumberFormat // 可以标注在方法上、属性field上、参数上~~~~ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface NumberFormat { Style style() default Style.DEFAULT; // 格式化数字的模版~~~ 若指定了pattern 那就使用new NumberStyleFormatter(pattern)进行格式化 String pattern() default ""; enum Style { // 默认值 同 NUMBER DEFAULT, NUMBER, PERCENT, CURRENCY } }
Jsr354NumberFormatAnnotationFormatterFactory
也就是Jsr354的相关类型(MonetaryAmount),也是支持@NumberFormat注解的
JSR 354定义了一套新的Java货币API:目前还是javax包内~
CurrencyUnit代表的是货币。它有点类似于现在的java.util.Currency类
MontetaryAmount代表的是某种货币的具体金额。通常它都会与某个CurrencyUnit绑定。
(略)
JodaDateTimeFormatAnnotationFormatterFactory
略
DateTimeFormatAnnotationFormatterFactory
它和@DateTimeFormat这个注解有关,作用在Date、Calendar、Long类型上。
public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactory<DateTimeFormat> { // 该注解只能放在下面这集中类型上面~~~~才会生效 private static final Set<Class<?>> FIELD_TYPES; static { Set<Class<?>> fieldTypes = new HashSet<>(4); fieldTypes.add(Date.class); fieldTypes.add(Calendar.class); fieldTypes.add(Long.class); FIELD_TYPES = Collections.unmodifiableSet(fieldTypes); } @Override public Set<Class<?>> getFieldTypes() { return FIELD_TYPES; } @Override public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) { return getFormatter(annotation, fieldType); } @Override public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) { return getFormatter(annotation, fieldType); } protected Formatter<Date> getFormatter(DateTimeFormat annotation, Class<?> fieldType) { DateFormatter formatter = new DateFormatter(); // style属性支持使用占位符的形式~ setStylePattern // 'S' = Small 'M' = Medium 'L' = Long 'F' = Full '-' = Omitted // 注意:这里需要同时设置两个。比如SS SM等等 // 第一个表示Date日期格式,第二个表示Time事件格式~~~~ 注解默认值是SS String style = resolveEmbeddedValue(annotation.style()); if (StringUtils.hasLength(style)) { formatter.setStylePattern(style); } formatter.setIso(annotation.iso()); // patter也支持占位符~~~ // DateFormatter里说过,若pattern指定了,就直接使用SimpleDateFormat格式化了 // 否则根据stylePattern来进行拿模版实例:return DateFormat.getTimeInstance(timeStyle, locale) //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); //} String pattern = resolveEmbeddedValue(annotation.pattern()); if (StringUtils.hasLength(pattern)) { formatter.setPattern(pattern); } return formatter; } }
@DateTimeFormat注解内容:
// @since 3.0 它比Number多一个ElementType.ANNOTATION_TYPE,表示它还能作为元注解标注在注解上 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface DateTimeFormat { // 默认是SS 表示否是SMALL的 String style() default "SS"; // 默认为null。若指定了ISO,最终也会使用SimpleDateFormat去格式化Date。 // 因为String pattern = ISO_PATTERNS.get(this.iso)都对应着patter值的~~~ 见下面 ISO iso() default ISO.NONE; // 默认不给你指定pattern 但是我们使用时一般都要指定~ String pattern() default ""; enum ISO { DATE, // yyyy-MM-dd 2000-10-31 TIME, // HH:mm:ss.SSSXXX 01:30:00.000-05:00 // 注意:若你什么都没有指定,默认就会按照此种格式转换为Date~~~ DATE_TIME, // yyyy-MM-dd'T'HH:mm:ss.SSSXXX 2000-10-31T01:30:00.000-05:00 NONE } }
Jsr310DateTimeFormatAnnotationFormatterFactory
它和@DateTimeFormat这个注解有关,作用在JSR310相关类型上。
注意,它也是处理标注有@DateTimeFormat注解的字段的。DateTimeFormatterRegistrar#registerFormatters方法里注册了它,从而提供了该注解对JSR310也是支持的,并且我认为比上面还重要些,大势所趋~
public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport implements AnnotationFormatterFactory<DateTimeFormat> { // 可以标注在这些类型上面~~~~ private static final Set<Class<?>> FIELD_TYPES; static { // Create the set of field types that may be annotated with @DateTimeFormat. Set<Class<?>> fieldTypes = new HashSet<>(8); fieldTypes.add(LocalDate.class); fieldTypes.add(LocalTime.class); fieldTypes.add(LocalDateTime.class); fieldTypes.add(ZonedDateTime.class); fieldTypes.add(OffsetDateTime.class); fieldTypes.add(OffsetTime.class); FIELD_TYPES = Collections.unmodifiableSet(fieldTypes); } // 往外输出的时候~~~~~~ @Override public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) { // 使用DateTimeFormatterFactory根据注解信息创建一个java.time.format.DateTimeFormatter DateTimeFormatter formatter = getFormatter(annotation, fieldType); // Efficient ISO_LOCAL_* variants for printing since they are twice as fast... // ISO.DATE -> DateTimeFormatter.ISO_DATE // ISO.TIME -> DateTimeFormatter.ISO_TIME // ISO.DATE_TIME -> DateTimeFormatter.ISO_DATE_TIME // ISO.NONE 没有指定,就走最后的TemporalAccessorPrinter了~~~~ // isLocal(fieldType) --> fieldType.getSimpleName().startsWith("Local"); // System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now())); //2019-06-04 标准格式输出~~~~ if (formatter == DateTimeFormatter.ISO_DATE) { if (isLocal(fieldType)) { formatter = DateTimeFormatter.ISO_LOCAL_DATE; } } else if (formatter == DateTimeFormatter.ISO_TIME) { if (isLocal(fieldType)) { formatter = DateTimeFormatter.ISO_LOCAL_TIME; } } else if (formatter == DateTimeFormatter.ISO_DATE_TIME) { if (isLocal(fieldType)) { formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; } } // 它的print方法为:return DateTimeContextHolder.getFormatter(this.formatter, locale).format(partial); return new TemporalAccessorPrinter(formatter); } // 它的parse方法,依赖于LocalDate.parse、OffsetTime.parse等等各自的parse方法~ @Override @SuppressWarnings("unchecked") public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) { DateTimeFormatter formatter = getFormatter(annotation, fieldType); return new TemporalAccessorParser((Class<? extends TemporalAccessor>) fieldType, formatter); } }
有了它,我们处理Date、JSR310之类的日期能达到统一的效果了。
DateTimeFormatterFactory
说白了,它就是根据一些参数比如:pattern、org.springframework.format.annotation.DateTimeFormat.ISO、java.time.format.FormatStyle、java.util.TimeZone等等来创建一个java.time.format.DateTimeFormatter
// @since 4.0 public class DateTimeFormatterFactory { @Nullable private String pattern; @Nullable private ISO iso; @Nullable private FormatStyle dateStyle; @Nullable private FormatStyle timeStyle; @Nullable private TimeZone timeZone; // ... public void setStylePattern(String style) { Assert.isTrue(style.length() == 2, "Style pattern must consist of two characters"); this.dateStyle = convertStyleCharacter(style.charAt(0)); this.timeStyle = convertStyleCharacter(style.charAt(1)); } @Nullable private FormatStyle convertStyleCharacter(char c) { switch (c) { case 'S': return FormatStyle.SHORT; case 'M': return FormatStyle.MEDIUM; case 'L': return FormatStyle.LONG; case 'F': return FormatStyle.FULL; case '-': return null; default: throw new IllegalArgumentException("Invalid style character '" + c + "'"); } } public DateTimeFormatter createDateTimeFormatter() { return createDateTimeFormatter(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); } // fallbackFormatter表示最后的格式化器的默认值~~~~ public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) { // 若指定了pattern 那就简单了~~~ if (StringUtils.hasLength(this.pattern)) { // 这一句是为了兼容Joda-Time到JSR里~~ String patternToUse = StringUtils.replace(this.pattern, "yy", "uu"); // 采用STRICT方式格式化~~~ dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT); } else if (this.iso != null && this.iso != ISO.NONE) { //ISO不能为null和NONE switch (this.iso) { case DATE: dateTimeFormatter = DateTimeFormatter.ISO_DATE; break; case TIME: dateTimeFormatter = DateTimeFormatter.ISO_TIME; break; case DATE_TIME: dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME; break; default: throw new IllegalStateException("Unsupported ISO format: " + this.iso); } } ... // 根据dateStyle和timeStyle来生成实例~~~~略 } }
通过它,我们只需要关注一些元信息,就能很快的生成出一个DateTimeFormatter
来。比如我们根据注解信息,就生成出来就是这么个原理。
DateTimeFormatterFactoryBean
这里指的是org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean,而不是joda包的,需要稍微注意。它借助了DateTimeFormatterFactory然后实现了一波FactoryBean,猛如虎有木有~
public class DateTimeFormatterFactoryBean extends DateTimeFormatterFactory implements FactoryBean<DateTimeFormatter>, InitializingBean { @Nullable private DateTimeFormatter dateTimeFormatter; @Override public void afterPropertiesSet() { // 父类创建~~ this.dateTimeFormatter = createDateTimeFormatter(); } @Override @Nullable public DateTimeFormatter getObject() { return this.dateTimeFormatter; } @Override public Class<?> getObjectType() { return DateTimeFormatter.class; } @Override public boolean isSingleton() { return true; } }
FormattingConversionServiceFactoryBean
它和上面的不同,它是用于管理转换器、格式化器们的。比如我们自己自定义了一个转换器、格式化器需要注册,都以交给它。从名字可以看出,它主要是创建一个FormattingConversionService,而它上面说了它既还有转换器,又有格式化器~~~
public class FormattingConversionServiceFactoryBean implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean { @Nullable private Set<?> converters; @Nullable private Set<?> formatters; // 由此可见,我们要注册formatter不仅仅可以直接注册,也可通过formatterRegistrars注册进来~ @Nullable private Set<FormatterRegistrar> formatterRegistrars; private boolean registerDefaultFormatters = true; @Nullable private StringValueResolver embeddedValueResolver; @Nullable private FormattingConversionService conversionService; // 最终是它用于管理所有 备注:所有的formatter最终都是一个converter // 这里把上面字段set进来的值,进行解析~~~~拆分~~~ @Override public void afterPropertiesSet() { // 由此可见,最终返回的是一个DefaultFormattingConversionService this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters); // 把set进来的这些converters都注册进去保存着~~~ ConversionServiceFactory.registerConverters(this.converters, this.conversionService); // 这里处理注册formatters和formatterRegistrars们~~~~ registerFormatters(this.conversionService); } private void registerFormatters(FormattingConversionService conversionService) { if (this.formatters != null) { for (Object formatter : this.formatters) { if (formatter instanceof Formatter<?>) { conversionService.addFormatter((Formatter<?>) formatter); } else if (formatter instanceof AnnotationFormatterFactory<?>) { conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory<?>) formatter); } else { throw new IllegalArgumentException( "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory"); } } } if (this.formatterRegistrars != null) { for (FormatterRegistrar registrar : this.formatterRegistrars) { registrar.registerFormatters(conversionService); } } } @Override @Nullable public FormattingConversionService getObject() { return this.conversionService; } // 类型实际上为DefaultFormattingConversionService @Override public Class<? extends FormattingConversionService> getObjectType() { return FormattingConversionService.class; } @Override public boolean isSingleton() { return true; } }
FormattingConversionServiceFactoryBean
它和上面的不同,它是用于管理转换器、格式化器们的。比如我们自己自定义了一个转换器、格式化器需要注册,都以交给它。从名字可以看出,它主要是创建一个FormattingConversionService,而它上面说了它既还有转换器,又有格式化器~~~
public class FormattingConversionServiceFactoryBean implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean { @Nullable private Set<?> converters; @Nullable private Set<?> formatters; // 由此可见,我们要注册formatter不仅仅可以直接注册,也可通过formatterRegistrars注册进来~ @Nullable private Set<FormatterRegistrar> formatterRegistrars; private boolean registerDefaultFormatters = true; @Nullable private StringValueResolver embeddedValueResolver; @Nullable private FormattingConversionService conversionService; // 最终是它用于管理所有 备注:所有的formatter最终都是一个converter // 这里把上面字段set进来的值,进行解析~~~~拆分~~~ @Override public void afterPropertiesSet() { // 由此可见,最终返回的是一个DefaultFormattingConversionService this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters); // 把set进来的这些converters都注册进去保存着~~~ ConversionServiceFactory.registerConverters(this.converters, this.conversionService); // 这里处理注册formatters和formatterRegistrars们~~~~ registerFormatters(this.conversionService); } private void registerFormatters(FormattingConversionService conversionService) { if (this.formatters != null) { for (Object formatter : this.formatters) { if (formatter instanceof Formatter<?>) { conversionService.addFormatter((Formatter<?>) formatter); } else if (formatter instanceof AnnotationFormatterFactory<?>) { conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory<?>) formatter); } else { throw new IllegalArgumentException( "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory"); } } } if (this.formatterRegistrars != null) { for (FormatterRegistrar registrar : this.formatterRegistrars) { registrar.registerFormatters(conversionService); } } } @Override @Nullable public FormattingConversionService getObject() { return this.conversionService; } // 类型实际上为DefaultFormattingConversionService @Override public Class<? extends FormattingConversionService> getObjectType() { return FormattingConversionService.class; } @Override public boolean isSingleton() { return true; } }
有了它,上篇文章我们讲到我们若要注册自定义的converter的话,使用的ConversionServiceFactoryBean,而本文我们就可以使用更加强大的FormattingConversionServiceFactoryBean了。
一般情况下,若是Web环境下比如Spring MVC使用转换器、格式化器。建议使用FormattingConversionServiceFactoryBean注册,其余的无所谓了。