在上篇文章中,咱们已经学习过了Spring中的类型转换机制。如今咱们考虑这样一个需求:在咱们web应用中,咱们常常须要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来知足咱们调用需求,一样的,后端开发也须要将返回数据调整成指定格式或者指定类型返回到前端页面。这种状况下,Converter已经无法直接支撑咱们的需求了。这个时候,格式化的做用就很明显了,这篇文章咱们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.5及3.6小结前端
Formatterjava
接口定义程序员
public interface Formatter<T> extends Printer<T>, Parser<T> { }
能够看到,自己这个接口没有定义任何方法,只是聚合了另外两个接口的功能web
// 将T类型的数据根据Locale信息打印成指定格式,即返回字符串的格式 public interface Printer<T> { String print(T fieldValue, Locale locale); }
public interface Parser<T> { // 将指定的字符串根据Locale信息转换成指定的T类型数据 T parse(String clientValue, Locale locale) throws ParseException; }
从上面能够看出,这个两个接口维护了两个功能相反的方法,分别完成对String类型数据的解析以及格式化。spring
继承树后端
在这里插入图片描述mvc
能够发现整个继承关系并不复杂,甚至能够说很是简单。只有一个抽象子类,AbstractNumberFormatter,这个类抽象了对数字进行格式化时的一些方法,它有三个子类,分别处理不一样的数字类型,包括货币,百分数,正常数字。其他的子类都是直接实现了Formatter接口。其中咱们比较熟悉的可能就是DateFormatter了ide
使用以下:学习
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 } }
注解驱动的格式化ui
咱们在配置格式化时,除了根据类型进行格式外(好比常见的根据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 都注册一个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)); } } // ....... // 持有的一个内部类 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添加格式化器便可 } }
配置实现的原理
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
// 继承了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点赞、转发、在看,多谢多谢!钟意做者