在上篇文章中,咱們已經學習過了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點贊、轉發、在看,多謝多謝!鐘意做者