在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们思量这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定花样或者指定数据类型来知足我们挪用需求,同样的,后端开发也需要将返回数据调整成指定花样或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支持我们的需求了。这个时刻,花样化的作用就很显著了,这篇文章我们就来先容Spring中花样化的一套系统。本文主要涉及官网中的3.5及3.6小结

Formatter

接口界说

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

可以看到,自己这个接口没有界说任何方式,只是聚合了另外两个接口的功效

  • Printer
// 将T类型的数据凭据Locale信息打印成指定花样,即返回字符串的花样
public interface Printer<T> {
    String print(T fielDValue, Locale locale);
}
  • Parser
public interface Parser<T> {
 // 将指定的字符串凭据Locale信息转换成指定的T类型数据
    T parse(String CLientValue, Locale locale) throws ParseException;
}

从上面可以看出,这个两个接口维护了两个功效相反的方式,划分完成对String类型数据的剖析以及花样化。

继续树


在这里插入图片形貌

可以发现整个继续关系并不庞大,甚至可以说异常简朴。只有一个抽象子类,AbstractNumberFormatter,这个类抽象了对数字举行花样化时的一些方式,它有三个子类,划分处置差别的数字类型,包罗钱币,百分数,正常数字。其余的子类都是直接实现了Formatter接口。其中我们对照熟悉的可能就是DateFormatter了

使用如下:

public class MAIn {
 public static void main(String[] args) throws Exception {
  DateFormatter dateFormatter = new DateFormatter();
  dateFormatter.setIso(DateTimeFormat.ISO.DATE);
  System.out.println(dateFormatter.print(new Date(), Locale.CHINA));
  System.out.println(dateFormatter.parse("2020-03-26", Locale.CHINA));
        // 程序打印:
        // 2020-03-26
  // Thu Mar 26 08:00:00 CST 2020
 }
}

注解驱动的花样化

我们在设置花样化时,除了凭据类型举行花样外(好比常见的凭据Date类型举行花样化),还可以凭据注解来举行花样化,最常见的注解就是org.springframework.format.annotation.DateTimeFormat。除此之外另有NumberFormat,它们都在format包下。


在这里插入图片形貌
为了将一个注解绑定到指定的花样化器上,我们需要借助到一个接口AnnotationFormatterFactory

AnnotationFormatterFactory

public interface AnnotationFormatterFactory<A extends Annotation> {
 // 可能被添加注解的字段的类型
 Set<Class<?>> getFieldTypes();

    // 凭据注解及字段类型获取一个花样化器
 Printer<?> getPrinter(A annotation, Class<?> fieldType);

    // 凭据注解及字段类型获取一个剖析器
 Parser<?> getParser(A annotation, Class<?> fieldType);

}

以Spring内置的一个DateTimeFormatAnnotationFormatterFactory来说,这个类实现的功效就是将DateTimeFormat注解绑定到指定的花样化器,源码如下:

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来完成花样化
  DateFormatter formatter = new DateFormatter();
  String style = resolveEmbeddedValue(annotation.style());
  if (StringUtils.hasLength(style)) {
   formatter.setStylePattern(style);
  }
  formatter.setIso(annotation.iso());
  String pattern = resolveEmbeddedValue(annotation.pattern());
  if (StringUtils.hasLength(pattern)) {
   formatter.setPattern(pattern);
  }
  return formatter;
 }

}

使用@DateTimeFormat,我们只需要在字段上添加即可

public class MyModel {
    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}

关于日期的花样化,Spring还提供了一个类似的AnnotationFormatterFactory,专门用于处置jAVa8中的日期花样,如下

public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
    implements AnnotationFormatterFactory<DateTimeFormat> {

    private static final Set<Class<?>> FIELD_TYPES;

    static {
        // 这里添加了对Java8日期的支持
        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);
    }
    ........

学习到现在,对Spring的脾性人人应该都有所领会,上面这些都是界说了详细的功效实现,它们肯定会有一个管理者,一个Registry,用来注册这些花样化器

FormatterRegistry

接口界说

// 继续了ConverterRegistry,以是它同时照样一个Converter注册器
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);
}

UML类图


在这里插入图片形貌
我们可以发现FormatterRegistry默认只有两个实现类

FormattingConversionService

// 继续了GenericConversionService ,以是它能对Converter举行一系列的操作
// 实现了接口FormatterRegistry,以是它也可以注册花样化器了
// 实现了EmbeddedValueResolverAware,以是它还能有异常壮大的功效:处置占位符
public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware {
 // ....

 // 最终也是交给addFormatterForFieldType去做的
 // getFieldType:它会拿到泛型类型。而且支持DecoratingProxy
 @Override
 public void addFormatter(Formatter<?> formatter) {
  addFormatterForFieldType(getFieldType(formatter), formatter);
 }
 // 存储都是离开存储的  读写星散
 // PrinterConverter和ParserConverter都是一个GenericConverter  接纳内部类实现的
 // 注重:他们的ConvertiblePair必有一个类型是String.class
 // Locale一样平常都可以这么获取:LocaleContextHolder.getLocale()
    // 在举行printer之前,会先判断是否能举行类型转换,若是能举行类型转换会先举行类型转换,之后再花样化
 // 在parse之后,会判断是否还需要举行类型转换,若是需要类型转换会先举行类型转换
 @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  都注册一个AnnotaT.O.PrinterConverter去处置
  // 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));
  }
 }
 // .......

    // 持有的一个内部类
    private static class PrinterConverter implements GenericConverter {

        private final Class<?> fieldType;

        private final TypeDescriptor printerObjectType;

        @SuppressWarnings("rawtypes")
        private final Printer printer;

        // 最终也是通过conversionService完成类型转换
        private final ConversionService conversionService;

        public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
            this.fieldType = fieldType;
            this.printerObjectType = 
                // 会通过剖析Printer中的泛型获取详细类型,主要是为了判断是否需要举行类型转换
                TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
            this.printer = printer;
            this.conversionService = conversionService;
        }
 // ......

}

DefaultFormattingConversionService

类比我们上篇文中先容的GenericConversionService跟DefaultConversionService,它相比于FormattingConversionService而言,提供了大量的默认的花样化器,源码如下:

public class DefaultFormattingConversionService extends FormattingConversionService {

 private static final boolean jsr354Present;

 private static final boolean jodaTimePresent;

 static {
  ClassLoader classLoader = DefaultFormattingConversionService.class.getClassLoader();
        // 判断是否导入了jsr354相关的包
  jsr354Present = ClassUtils.isPresent("javax.money.MonetaryAmount", classLoader);
        // 判断是否导入了joda
  jodaTimePresent = ClassUtils.isPresent("org.joda.time.LocalDate", classLoader);
 }

    // 会注册许多默认的花样化器
 public DefaultFormattingConversionService() {
  this(null, true);
 }
 public DefaultFormattingConversionService(boolean registerDefaultFormatters) {
  this(null, registerDefaultFormatters);
 }

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

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

 public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
  // 添加针对@NumberFormat的花样化器
  formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

  // 针对钱币的花样化器
  if (jsr354Present) {
   formatterRegistry.addFormatter(new CurrencyUnitFormatter());
   formatterRegistry.addFormatter(new MonetaryAmountFormatter());
   formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
  }
  new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);

        // 如没有导入joda的包,那就默认使用Date
  if (jodaTimePresent) {
   // 针对Joda
   new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
  }
  else {
            // 没有joda的包,是否Date
   new DateFormatterRegistrar().registerFormatters(formatterRegistry);
  }
 }

}

FormatterRegistrar

在上面DefaultFormattingConversionService的源码中,有这么几行:

new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
new DateFormatterRegistrar().registerFormatters(formatterRegistry);

其中的JodaTimeFormatterRegistrar,DateFormatterRegistrar就是FormatterRegistrar。那么这个接口有什么用呢?我们先来看看它的接口界说:

public interface FormatterRegistrar {
 // 最终也是挪用FormatterRegistry来完成注册
    void registerFormatters(FormatterRegistry registry);
}

我们思索一个问题,为什么已经有了FormatterRegistry,Spring还要开发一个FormatterRegistrar呢?直接使用FormatterRegistry完成注册不好吗?

以这句代码为例:new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry),这段代码是将joda包下所有的默认的转换器已经注册器都注册到formatterRegistry中。

我们可以发现FormatterRegistrar相当于对花样化器及转换器举行了分组,我们挪用它的registerFormatters方式,相当于将这一组花样化器直接添加到指定的formatterRegistry中。这样做的利益在于,若是我们对同一个类型的数据有两组差别的花样化计谋,例如就以上面的日期为例,我们既有可能接纳joda的计谋举行花样化,也有可能接纳Date的计谋举行花样化,通过分组的方式,我们可以更见利便的在确认好计谋后将需要的花样化器添加到容器中。

设置SpringMVC中的花样化器

@ConfIGuration
@EnableWebMvc
public class WEBConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 挪用registry.addFormatter添加花样化器即可
    }
}

设置实现的原理

  1. @EnableWebMvc注解上导入了一个DelegatingWebMvcConfiguration类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
  1. DelegatingWebMvcConfiguration
// 继续了WebMvcConfigurationSupport
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

 private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // 这个方式会注入所有的WebMvcConfigurer,包罗我们的WebConfig
 @Autowired(required = false)
 public void setConfigurers(List<WebMvcConfigurer> configurers) {
  if (!CollectionUtils.isEmpty(configurers)) {
   this.configurers.addWebMvcConfigurers(configurers);
  }
 }

 //.....,省略无关代码

    // 复写了父类WebMvcConfigurationSupport的方式
    // 挪用我们设置的configurer的addFormatters方式
 @Override
 protected void addFormatters(FormatterRegistry registry) {
  this.configurers.addFormatters(registry);
 } 
   //.....,省略无关代码
}

3.WebMvcConfigurationSupport

public class WebMvcConfigurationSupport implements APPlicationContextAware, ServletContextAware {

    // 这就是真相,这里会建立一个FormattingConversionService,而且是一个DefaultFormattingConversionService,然后挪用addFormatters方式
 @Bean
 public FormattingConversionService mvcConversionService() {
  FormattingConversionService conversionService = new DefaultFormattingConversionService();
  addFormatters(conversionService);
  return conversionService;
 }
 protected void addFormatters(FormatterRegistry registry) {
 }

}

总结

Spring中的花样化到此就竣事了,总结绘图如下:

往期精选

Spring官网阅读专辑
Spring杂谈

程序员DMZ
点赞、转发、在看,多谢多谢!
钟意作者