2023-09-12  阅读(1)
原文作者:一直不懂 原文地址: https://blog.csdn.net/shenchaohao12321/article/details/80263940

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

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

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

202309122022545781.png

    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用来扩展格式化器。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文