本文转载自微信公众号《BAT的乌托邦》作者YourBatman。转载本文请联系BAT的乌托邦公众号。请人出汗不如请人吃饭,给人鱼不如请人出汗。A哥春节期间继续营业。我估计这时候还能看懂这类技术文章的同学有以下三种:要么孤单要么醉,要么醉后寂寞。继续前进是个好主意。上一篇文章阐明了@DateTimeFormat和@NumberFormat注解的实现原理,通过面向元数据的编程屏蔽了理解和实现层面的差异。同时,通过手敲代码案例,让我对@DateTimeFormat等注解的用途、使用方法有了扎实透彻的了解,不再虚伪。AnnotationFormatterFactory、xxxConverter等API属于底层API,一般上手难度较大。一个好的流行的框架至少应该是易于使用的,所以开发者最多应该关注FormattingConversionService/ConversionService这个层面。本文就带大家看看Spring是怎么实现酱的~本文概述了版本协议SpringFramework:5.3.xSpringBoot:2.4.xText以上是通过手动调用API实现数据格式化来实现元数据解析(转换),但在实际应用场景中,作为业务开发者,是不可能直接操作API的。毕竟对开发者太不友好,使用门槛太高。因此,本文将介绍一个更“高级”的使用方案,看看Spring是如何实现高扩展性集成的,对开发者非常友好。相信这也是Spring最吸引人的地方,让我们一起学习和学习。FormatterRegistry:注册表是多组件管理的一个很好的解决方案。FormatterRegistry其实就是:9.具体看真章,Formatter注册表的设计很讨人喜欢。这篇文章分析的很详细,了解到了它非常巧妙的设计。我还建议您花几分钟时间看一下。文末A哥特意留了个小尾巴:注册中心对注解工厂AnnotationFormatterFactory的支持,也就是这个接口方法:FormatterRegistry:voidaddFormatterForFieldAnnotation(AnnotationFormatterFactoryannotationFormatterFactory);现在时机成熟,本文将重点介绍。该接口方法的唯一实现在FormattingConversionService中:①:从AnnotationFormatterFactory的泛型类型中提取注解类型。注意:如果没有指定泛型类型(没有指定注解类型),会抛出异常②:这个工厂类支持的类型③:对于每一个支持的类型,都会注册一个Printer/Parser。重点在步骤③,AnnotationPrinterConverter和AnnotationParserConverter都是一个ConditionalGenericConverter转换器,而底层实现其实是委托给AnnotationFormatterFactory来完成的,所以对AnnotationFormatterFactory的理解极其重要。好在之前的文章已经对它做了详细的分析,点这里直接上电梯。下面以AnnotationPrinterConverter为例查看其源码:①:该转换器只负责将fieldType类型转换为String类型②:只有在fieldType上标注了指定的注解,该转换器才会生效③:转换逻辑。这种缓存处理逻辑很常见。其实核心代码往往只有一句话,就是这里:this.annotationFormatterFactory.getPrinter(...)。获取合适的Printer,然后适配到PrinterConverter完成最后的convert转换动作?注:PrinterConverter和ParserConverter在本系列之前的文章中已经介绍过,相关内容可以在本系列中轻松找到?AnnotationParserConverter实现逻辑一模一样,这里不再啰嗦。FormattingConversionService实现了FormatterRegistry接口的所有接口方法,但不提供一些默认行为。也就是说:它实现了所有的组件注册/管理能力,但是它没有“帮你”注册任何组件,所以不具备直接提供服务的条件。如果要使用,需要“手动干预”,把一些组件放进去即可。一般来说,对于这种情况,在外部包裹一层DefaultXXX,提供默认服务,一般是开发者友好的方案,而春天也是如此。我们来看看DefaultFormattingConversionService默认为我们注册了哪些基础。组件提供什么功能?DefaultFormattingConversionService是默认的格式化程序转换服务。此默认行为适用于大多数应用程序对格式化程序和转换器的需求。继承自FormattingConversionService,这个默认行为是为本实例设计的,但是为了方便使用,暴露了它的static静态方法addDefaultFormatters(),与DefaultConversionService暴露的静态方法addDefaultConverters()完全一样。默认注册了哪些组件?对于一个默认的Service服务,最关心的是它提供了哪些能力。换句话说:它默认为我们注册了哪些组件?要回答这个问题,不能靠“往复回答”。方法其实很简单。爬入它的源码可以看到:该类(其实是父类)实现了EmbeddedValueResolverAware接口,但是构造时仍然可以指定占位符处理器StringValueResolver,当然一般情况下可以传入null②:调用DefaultConversionService的静态方法将默认的转换器注册进来。那么,默认注册了哪些转换器呢?静态方法DefaultConversionService.addDefaultConverters(this)其实就是本系列上一篇文章的内容。这里A哥顺便在这里贴一下:③:如果registerDefaultFormatters为true,则添加默认的formatters,一般来说这个值为true。那么,默认注册了哪些格式化程序?①:提供对@NumberFormat注解的支持,格式数字(货币、数字、百分比等)②:提供对JSR354硬币类型javax.money.CurrencyUnit、Monetary等的支持。一般情况下不需要,所以这部分不会真正注册③:提供对JSR-310日期时间格式化的支持。这里,其专用寄存器DateTimeFormatterRegistrar用于统一操作④、⑤:步骤4和步骤5为互斥操作。如果有Jota-Time,会在不触发java.util.Date寄存器的情况下为其提供支持,否则使用后者寄存器。注:你认为④和⑤真的互斥吗?导入joda-time包后java.util.Date相关模块是否失效?显然情况并非如此。让你“放心”的是JodaTimeFormatterRegistrar的注册java.util.Dateformatter的注册是包含在formatter内部的,所以一切只能在xxxRegistrar中查找才能揭晓。简而言之,DefaultFormattingConversionService是默认的格式化转换服务,它是DefaultConversionService的超集,在此基础上扩展了格式化器、格式化注解支持等相关能力。在Spring环境中,大多数情况下使用它来代替DefaultConversionService。现在,我对FormatterRegistry这个类有了一个大概的了解,知道它默认注册了哪些组件,支持哪些功能,但是细节还是不太清楚。例如:支持哪些数据类型?支持哪些格式?这些都隐藏在对应的xxxRegistrar~FormatterRegistrar:registrarregistrar:registrar;注册商。xxxRegistrar是“倒排”思想的一种设计体现,可以达到很高的内聚效果。Spring和SpringBoot常用的“tricks”,比如随便搜一下就可以看到很多:FormatterRegistrar表示格式化器注册器接口,接口定义:publicinterfaceFormatterRegistrar{voidregisterFormatters(FormatterRegistryregistry);}接口方法含义:注册Converter和Formatter进入FormatterRegistry注册表。至于注册哪些组件,由各个子类自己管理和负责,而不是由Registry注册中心主动安排。这是一种倒置的设计思路,可以很好的达到高内聚的目的。?注意:虽然有ConverterRegistry和FormatterRegistry两个接口,但是只有FormatterRegistrar,没有ConverterRegistrar。DateFormatterRegistrar:DateRegistrar为java.util.Date、java.util.Calendar和long类型的日期和时间提供注册支持。接口方法的实现如下:①:添加正则转换器,支持DateToLong、DateToCalendar、LongToCalendar等基本转换能力②:如果有个性化的formatter,专用于Calendar。当然,大多数情况下不会这样做。这一步逻辑只是为了向后兼容,一般可以忽略③:添加@DateTimeFormat注解解析支持代码示例下面是使用DateFormatterRegistrar注册器的示例。常见用法中最常见的转换,Date、Long、Calendar等日期和时间类型似乎是可以互换的。@Testpublicvoidtest1(){FormattingConversionServiceconversionService=newFormattingConversionService();//注册器负责添加格式化器,支持Date系列的转换newDateFormatterRegistrar().registerFormatters((FormatterRegistry)conversionService);//1.常用longcurrMills=System.currentTimeMillis();System.out.println("当前时间戳:"+currMills);//Date->CalendarSystem.out.println(conversionService.convert(newDate(currMills),Calendar.class));//Long->DateSystem.out.println(conversionService.convert(currMills,Date.class));//Calendar->LongCalendarcalendar=Calendar.getInstance(TimeZone.getDefault());calendar.setTimeInMillis(currMillis);System.out.println(conversionService.convert(calendar,Long.class));}运行程序,输出:currenttimestamp:1612741385457java.util.GregorianCalendar[time=1612741385457...MonFeb0807:43:05CST20211612741385457完美。注解使用使用更高级的注解方式,比如@DateTimeFormat//准备一个JavaBean:@Data@AllArgsConstructorclassSon{@DateTimeFormat(iso=DateTimeFormat.ISO.DATE)privateDatebirthday;}测试代码:@Testpublicvoidtest1(){FormattingConversionServiceconversionService=newFormattingConversionService();//重要:重要:重要:注册基础转换能力DefaultConversionService.addDefaultConverters((ConverterRegistry)conversionService);//注册器负责添加格式化器支持Date系列的转换newDateFormatterRegistrar().registerFormatters((FormatterRegistry)转换服务);//1.AnnotationusesSonson=newSon(newDate());//输出:输出Date类型为Long类型System.out.println(conversionService.convert(son.getBirthday(),Long.class));//Output:inputStringstronglyasDatetype//System.out.println(conversionService.convert("2021-02-12",Date.class));//报错System.out.println(conversionService.convert(1613034123709L,Date.class));}运行程序,输出:1613034230018ThuFeb1117:02:03CST2021perfect。实现了Long类型<->Date类型的相互转换。可能有同学会问,为什么“2021-02-12”不能转成Date类型呢?为此,呃,嗯,嗯,如果你看了前面的文章,这个就不成问题了。当然,在实际使用中,更多的情况是String->Date转换的情况,如何破解呢?有两个方法:重温本系列之前的文章,因为前面的已经不止一次提到了,关注后面的文章。因为这种情况太常见了,后面还是会提到(尤其是在SpringMVC下使用的时候)。本文的重点是通过FormatterRegistry注册中心引用Spring常用的Registrar注册器设计思想。它是一种面向对象的编程思想。是不是比面向过程优雅多了?本文以DateTimeFormatterRegistrar为例做一个证明。可以看到Spring在API抽象方面真的很优秀,兼具可扩展性和便利性。这个度把握的好,或许这也算是设计美学吧。
