05、Spring源码分析:字段格式化

上一篇分析了继承图的右半部分,这次来分析左半部分。

Spring 3为此引入了一个方便的Formatter SPI来直接解决这些问题,这个接口为客户端环境提供一种简单强大并且替代PropertyEditor的方案。

一般来说,当你需要实现通用的类型转换逻辑时请使用Converter SPI,例如,在java.util.Date和java.lang.Long之间进行转换。当你在一个客户端环境(比如web应用程序)工作并且需要解析和打印本地化的字段值时,请使用Formatter SPI。ConversionService接口为这两者提供了一套统一的类型转换API。

*

public interface Formatter<T> extends Printer<T>, Parser<T> {}
public interface Printer<T> {
	String print(T object, Locale locale);
}
public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

实现print()操作可以将类型T的实例按客户端区域设置的显示方式打印出来。实现parse()操作可以从依据客户端区域设置返回的格式化表示中解析出类型T的实例。如果解析尝试失败,你的格式化器应该抛出一个ParseException或者IllegalArgumentException。请注意确保你的格式化器实现是线程安全的。

DateFormatter作为Formatter实现的一个例子:

package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
    private String pattern;
    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }
    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }
    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}

字段格式化可以通过字段类型或者注解进行配置,要将一个注解绑定到一个格式化器,可以实现AnnotationFormatterFactory:

package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
    Set<Class<?>> getFieldTypes();
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

泛型参数A代表你想要关联格式化逻辑的字段注解类型,例如org.springframework.format.annotation.DateTimeFormat。让getFieldTypes()方法返回可能使用注解的字段类型,让getPrinter()方法返回一个可以打印被注解字段的值的打印机(Printer),让getParser()方法返回一个可以解析被注解字段的客户端值的解析器(Parser)。

下面这个AnnotationFormatterFactory实现的示例把@NumberFormat注解绑定到一个格式化器,此注解允许指定数字样式或模式:

public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
      implements AnnotationFormatterFactory<NumberFormat> {

   @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);
   }

   private Formatter<Number> configureFormatterFrom(NumberFormat annotation) {
      String pattern = resolveEmbeddedValue(annotation.pattern());
      if (StringUtils.hasLength(pattern)) {
         return new NumberStyleFormatter(pattern);
      }
      else {
         Style style = annotation.style();
         if (style == Style.CURRENCY) {
            return new CurrencyStyleFormatter();
         }
         else if (style == Style.PERCENT) {
            return new PercentStyleFormatter();
         }
         else {
            return new NumberStyleFormatter();
         }
      }
   }
}

FormatterRegistry是一个用于注册格式化器和转换器的服务提供接口(SPI)。FormattingConversionService是一个适用于大多数环境的FormatterRegistry实现,可以以编程方式或利用FormattingConversionServiceFactoryBean声明成Spring bean的方式来进行配置。由于它也实现了ConversionService,所以可以直接配置它与Spring的DataBinder以及Spring表达式语言(SpEL)一起使用。

public interface FormatterRegistry extends ConverterRegistry {
    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
    void addFormatterForFieldType(Formatter<?> formatter);
    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}

FormatterRegistrar是一个通过FormatterRegistry注册格式化器和转换器的服务提供接口(SPI),当要为一个给定的格式化类别(比如时间格式化)注册多个关联的转换器和格式化器时,FormatterRegistrar会非常有用。

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

接下来看一下FormatterRegistry的实现类FormattingConversionService。

@Override
public void addFormatter(Formatter<?> formatter) {
   addFormatterForFieldType(getFieldType(formatter), formatter);
}

@Override
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
   addConverter(new PrinterConverter(fieldType, formatter, this));
   addConverter(new ParserConverter(fieldType, formatter, this));
}

@Override
public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
   addConverter(new PrinterConverter(fieldType, printer, this));
   addConverter(new ParserConverter(fieldType, parser, this));
}

@Override
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
   Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
   if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
      ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
   }
   Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
   for (Class<?> fieldType : fieldTypes) {
      addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
      addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
   }
}

四个addFormatter()方法,最终目的都是调用 addConverter()方法新增两个转换器,PrinterConverter是将fieldType转换为String的转换器,ParserConverter是将String转换为fieldType的转换器,注解形式的分别为AnnotationPrinterConverter和AnnotationParserConverter。这四个接口都是GenericConverter的实现类,需要实现自己的getConvertibleTypes()方法和convert()方法。

private static class PrinterConverter implements GenericConverter {
   private final Class<?> fieldType;
   private final TypeDescriptor printerObjectType;
   @SuppressWarnings("rawtypes")
   private final Printer printer;
   private final ConversionService conversionService;

   public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
      this.fieldType = fieldType;
      this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
      this.printer = printer;
      this.conversionService = conversionService;
   }

   @Override
   public Set<ConvertiblePair> getConvertibleTypes() {
      return Collections.singleton(new ConvertiblePair(this.fieldType, String.class));
   }

   @Override
   @SuppressWarnings("unchecked")
   public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
      if (!sourceType.isAssignableTo(this.printerObjectType)) {
         source = this.conversionService.convert(source, sourceType, this.printerObjectType);
      }
      if (source == null) {
         return "";
      }
      return this.printer.print(source, LocaleContextHolder.getLocale());
   }

   @Nullable
   private Class<?> resolvePrinterObjectType(Printer<?> printer) {
      return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
   }

   @Override
   public String toString() {
      return (this.fieldType.getName() + " -> " + String.class.getName() + " : " + this.printer);
   }
}

getConvertibleTypes()方法可以看出是fieldType到String的转换,convert()方法是具体转换逻辑,内部使用Printer来格式化fieldType的。具体是先判断printerObjectType是否为sourceType类型,printerObjectType为Printer的泛型类型通过GenericTypeResolver.resolveTypeArgument()得出的。若printerObjectType不是sourceType类型,则使用conversionService将source转换为printerObjectType,在使用printer对象格式化。ParserConverter做了个逆向转化不再说了。

@Nullable
public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) {
   ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc);
   if (!resolvableType.hasGenerics()) {
      return null;
   }
   return getSingleGeneric(resolvableType);
}

@Nullable
private static Class<?> getSingleGeneric(ResolvableType resolvableType) {
   Assert.isTrue(resolvableType.getGenerics().length == 1,
         () -> "Expected 1 type argument on generic interface [" + resolvableType +
         "] but found " + resolvableType.getGenerics().length);
   return resolvableType.getGeneric().resolve();
}

下面看分析一下AnnotationPrinterConverter。

private class AnnotationParserConverter implements ConditionalGenericConverter {
   private final Class<? extends Annotation> annotationType;
   @SuppressWarnings("rawtypes")
   private final AnnotationFormatterFactory annotationFormatterFactory;
   private final Class<?> fieldType;
   public AnnotationParserConverter(Class<? extends Annotation> annotationType,
         AnnotationFormatterFactory<?> annotationFormatterFactory, Class<?> fieldType) {

      this.annotationType = annotationType;
      this.annotationFormatterFactory = annotationFormatterFactory;
      this.fieldType = fieldType;
   }

   @Override
   public Set<ConvertiblePair> getConvertibleTypes() {
      return Collections.singleton(new ConvertiblePair(String.class, fieldType));
   }

   @Override
   public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
      return targetType.hasAnnotation(this.annotationType);
   }

   @Override
   @SuppressWarnings("unchecked")
   @Nullable
   public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
      Annotation ann = targetType.getAnnotation(this.annotationType);
      if (ann == null) {
         throw new IllegalStateException(
               "Expected [" + this.annotationType.getName() + "] to be present on " + targetType);
      }
      AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
      GenericConverter converter = cachedParsers.get(converterKey);
      if (converter == null) {
         Parser<?> parser = this.annotationFormatterFactory.getParser(
               converterKey.getAnnotation(), converterKey.getFieldType());
         converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
         cachedParsers.put(converterKey, converter);
      }
      return converter.convert(source, sourceType, targetType);
   }
   @Override
   public String toString() {
      return (String.class.getName() + " -> @" + this.annotationType.getName() + " " +
            this.fieldType.getName() + ": " + this.annotationFormatterFactory);
   }
}

与PrinterConverter不同,它实现了ConditionalGenericConverter,所以需要实现matches()方法,这个方法返回值是源类型上是否有该AnnotationFormatterFactory的泛型注解类型,这样一个AnnotationFormatterFactory只能处理有且只有一种注解类型。convert()方法就是首先调用annotationFormatterFactory.getPrinter()得到Printer对象后包装成PrinterConverter,调用其convert()方法完成格式化。

那么客户端是如何使用FormattingConversionService完成字段的格式化的呢,下面给出一个Spring以注解格式化的例子。

*
@Test
public void formatFieldForAnnotation() throws Exception {
   formattingService.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
   doTestFormatFieldForAnnotation(Model.class, false);
}
*
public static class Model {
   @org.springframework.format.annotation.DateTimeFormat(style="S-")
   public Date date;
   @org.springframework.format.annotation.DateTimeFormat(pattern="M-d-yy")
   public List<Date> dates;
   public List<Date> getDates() {
      return dates;
   }

   public void setDates(List<Date> dates) {
      this.dates = dates;
   }
}
private void doTestFormatFieldForAnnotation(Class<?> modelClass, boolean directFieldAccess) throws Exception {
   formattingService.addConverter(new Converter<Date, Long>() {
      @Override
      public Long convert(Date source) {
         return source.getTime();
      }
   });
   formattingService.addConverter(new Converter<DateTime, Date>() {
      @Override
      public Date convert(DateTime source) {
         return source.toDate();
      }
   });

   String formatted = (String) formattingService.convert(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime()
         .toDate(), new TypeDescriptor(modelClass.getField("date")), TypeDescriptor.valueOf(String.class));
   assertEquals("10/31/09", formatted);
}

与GenericConversionService一样,FormattingConversionService默认没有任何转换器,并且也没有格式化器,一种是我们调用addFormatter()方法加入自定义格式化器,或者使用它的子类DefaultFormattingConversionService。

public DefaultFormattingConversionService(
      @Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) {

   if (embeddedValueResolver != null) {
      setEmbeddedValueResolver(embeddedValueResolver);
   }
   DefaultConversionService.addDefaultConverters(this);
   if (registerDefaultFormatters) {
      addDefaultFormatters(this);
   }
}

使用DefaultConversionService.addDefaultConverters(this)加入了Spring为我们写好的转化器,addDefaultFormatters()方法使用多个FormatterRegistrar往formatterRegistry批量注册了多个格式化器,当Spring默认的格式化器不能满足我们的需求时,我们可以实现我们自己的FormatterRegistrar批量注册格式化器。

public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
   // Default handling of number values
   formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
   // Default handling of monetary values
   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
   new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);
   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提供了一个FormattingConversionServiceFactoryBean方便使用XML配置FormattingConversionService。

@Override
public void afterPropertiesSet() {
   this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
   ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
   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);
      }
   }
}

formatters和formatterRegistrars用来扩展格式化器。