自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring的Registrar倒排思想送給你

開發(fā) 前端
本文重點(diǎn)是想經(jīng)由FormatterRegistry注冊(cè)中心,引述出Spring常用的Registrar注冊(cè)員設(shè)計(jì)思想,它是一種面向?qū)ο缶幊趟枷氲捏w現(xiàn),是不是比面向過程優(yōu)雅很多呢?

 [[381742]]

本文轉(zhuǎn)載自微信公眾號(hào)「BAT的烏托邦」,作者YourBatman 。轉(zhuǎn)載本文請(qǐng)聯(lián)系BAT的烏托邦公眾號(hào)。  

請(qǐng)人吃飯不如請(qǐng)人出汗,請(qǐng)人出汗不如送人以漁。A哥春節(jié)繼續(xù)營(yíng)業(yè),這個(gè)時(shí)候還能看得下去這種技術(shù)文章的同學(xué)我猜有三類:

  • 要么孤獨(dú)了
  • 要么喝醉了
  • 要么喝醉后覺得孤獨(dú)了

現(xiàn)實(shí)情況往往挺扎心,所以牢記使命,砥礪前行是個(gè)好辦法。

上篇文章 把@DateTimeFormat和@NumberFormat注解的實(shí)現(xiàn)原理搞清楚了,通過面向元數(shù)據(jù)編程屏蔽了理解層面、實(shí)施層面上的差異化。同時(shí),通過手敲代碼案例,扎扎實(shí)實(shí)、徹徹底底搞明白了@DateTimeFormat等注解有何用以及如何用,從此不再虛。

像AnnotationFormatterFactory、xxxConverter這種均屬于low-level底層API,上手起來(lái)一般頗具難度。一個(gè)良好的、流行的框架最起碼應(yīng)該是上手簡(jiǎn)單的,所以開發(fā)者應(yīng)該是最多關(guān)心到FormattingConversionService/ConversionService層面即止。本文帶你看看Spring是如何做到醬紫的~

本文提綱

 

版本約定

  • Spring Framework:5.3.x
  • Spring Boot:2.4.x

正文

上文是通過手動(dòng)調(diào)用API的方式實(shí)現(xiàn)元數(shù)據(jù)的解析從而達(dá)到數(shù)據(jù)格式化(轉(zhuǎn)換)的目的,而在實(shí)際應(yīng)用場(chǎng)景中,作為業(yè)務(wù)開發(fā)者是不可能去直接去操縱API的,畢竟說(shuō)到底那對(duì)開發(fā)者太不友好,使用門檻過高。

因此,本文將介紹的是一種更為“高級(jí)”的使用方案,看看Spring是如何做到兼具高擴(kuò)展性的整合,從而對(duì)開發(fā)者十分友好,相信這便也是Spring最有魅力的地方,一起來(lái)學(xué)習(xí)學(xué)習(xí)吧。

FormatterRegistry:注冊(cè)中心

對(duì)于多組件的管理,注冊(cè)中心是個(gè)很好的解決方案。

FormatterRegistry其實(shí)在:9. 細(xì)節(jié)見真章,F(xiàn)ormatter注冊(cè)中心的設(shè)計(jì)很討巧 這篇文章已經(jīng)有過很詳細(xì)的分析,學(xué)到了它那非常巧妙的設(shè)計(jì),這里也順道推薦你花幾分鐘前往看看。在這篇文章的末尾,A哥故意留下了一個(gè)小尾巴沒講:注冊(cè)中心對(duì)注解工廠AnnotationFormatterFactory的支持,也就是這個(gè)接口方法:

  1. FormatterRegistry: 
  2.  
  3.  void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); 

現(xiàn)在時(shí)機(jī)成熟,本文就來(lái)重點(diǎn)關(guān)照它。

該接口方法的唯一實(shí)現(xiàn)在FormattingConversionService里:

 

①:從AnnotationFormatterFactory的泛型類型中提取到注解類型。注意:若沒有指定泛型(沒有指定注解類型)就拋出異常②:該工廠類支持的類型們③:對(duì)于支持的每個(gè)類型,均注冊(cè)一個(gè)Printer/Parser

重點(diǎn)在于步驟③,AnnotationPrinterConverter和AnnotationParserConverter均是一個(gè)ConditionalGenericConverter轉(zhuǎn)換器,底層實(shí)現(xiàn)實(shí)際委托給AnnotationFormatterFactory去完成,所以說(shuō)對(duì)AnnotationFormatterFactory的理解格外的重要,還好上篇文章對(duì)它已經(jīng)做了詳盡分析,點(diǎn)擊這里電梯直達(dá)。

下面以AnnotationPrinterConverter為例觀其源碼:

 

①:該轉(zhuǎn)換器只負(fù)責(zé)將fieldType類型轉(zhuǎn)換為String類型②:只有fieldType上標(biāo)注有指定的這個(gè)注解,此轉(zhuǎn)換器才會(huì)生效③:轉(zhuǎn)換邏輯。這種緩存式處理邏輯很是常見,其實(shí)最核心的代碼往往只有一句,本處就是它:this.annotationFormatterFactory.getPrinter(...)。獲取到合適的Printer,然后適配為PrinterConverter從而完成最終的convert轉(zhuǎn)換動(dòng)作

❝說(shuō)明:PrinterConverter和ParserConverter在本系列前面文章已介紹,相關(guān)內(nèi)容可出門左拐在本系列內(nèi)很容易找到❞AnnotationParserConverter的實(shí)現(xiàn)邏輯如出一轍,這里就不再啰嗦了。

FormattingConversionService它實(shí)現(xiàn)了FormatterRegistry接口的所有接口方法,但是它并未提供一些默認(rèn)行為。換句話講:實(shí)現(xiàn)了所有的組件注冊(cè)/管理的能力,但并沒有“幫你”注冊(cè)任何組件,所以還不具備能夠直接提供服務(wù)的條件,若要使用還需“人工干預(yù)”放些組件進(jìn)去才行。

一般來(lái)講,對(duì)于這種情況一般在外部再包一層 DefaultXXX來(lái)提供默認(rèn)服務(wù)是一種對(duì)開發(fā)者十分友好的解決方案,Spring也是這么干的,下面來(lái)看看DefaultFormattingConversionService為我們默認(rèn)注冊(cè)了哪些基礎(chǔ)組件,提供了哪些能力呢。

DefaultFormattingConversionService

默認(rèn)的格式化器轉(zhuǎn)換服務(wù),該默認(rèn)行為適用于大多數(shù)應(yīng)用程序?qū)Ω袷交?、轉(zhuǎn)換器的需求。

繼承自FormattingConversionService,這個(gè)默認(rèn)行為是為該實(shí)例而設(shè)計(jì)的,但為了方便使用,它對(duì)外暴露了其static靜態(tài)方法addDefaultFormatters(),這個(gè)設(shè)計(jì)方式同DefaultConversionService暴露了靜態(tài)方法addDefaultConverters()如出一轍。

默認(rèn)注冊(cè)了哪些組件?

對(duì)于一個(gè)默認(rèn)的Service服務(wù),最關(guān)心的當(dāng)屬它提供了哪些能力。換句話講:它默認(rèn)幫我們注冊(cè)了哪些組件呢?

要回答這個(gè)問題可不能靠“背答案”,方式方法其實(shí)非常的簡(jiǎn)單,爬進(jìn)去它的源碼處一看便知:

 

①:雖然說(shuō)本類(其實(shí)是父類)實(shí)現(xiàn)了EmbeddedValueResolverAware接口,但構(gòu)造時(shí)依舊可以指定占位符處理器StringValueResolver,當(dāng)然一般情況下傳入null即可②:調(diào)用DefaultConversionService的靜態(tài)方法,把默認(rèn)的轉(zhuǎn)換器們都注冊(cè)進(jìn)來(lái)。那么,默認(rèn)到底注冊(cè)了哪些轉(zhuǎn)換器呢?DefaultConversionService.addDefaultConverters(this)該靜態(tài)方法其實(shí)是本系列前面文章所講的內(nèi)容,這里A哥順道也貼在這吧:

 

③:若registerDefaultFormatters為true就添加默認(rèn)的格式化器們,一般來(lái)講,此值都為true。那么,默認(rèn)到底注冊(cè)了哪些格式化器呢?

 

①:對(duì)@NumberFormat注解提供支持,格式化數(shù)字(Currency、數(shù)字、百分?jǐn)?shù)等)

②:對(duì)JSR 354錢幣類型javax.money.CurrencyUnit、Monetary等類型提供支持。一般情況下,用不著,所以此part不會(huì)被真的注冊(cè)

③:對(duì)JSR-310日期時(shí)間的格式化提供支持。這里使用到了其專用的注冊(cè)器DateTimeFormatterRegistrar統(tǒng)一操作

④、⑤:第4、5步是互斥操作,若有Jota-Time就提供對(duì)它的支持而不觸發(fā)java.util.Date的注冊(cè)器,否則使用后者注冊(cè)器。

注意:你以為④、⑤是真的互斥嗎?難道導(dǎo)入了joda-time的包后java.util.Date相關(guān)模塊就失效了?很明顯不是這樣的,讓你“放心”的地方在于JodaTimeFormatterRegistrar注冊(cè)器內(nèi)部包含了java.util.Date格式化器的注冊(cè)關(guān)系,因此一切都還得到xxxRegistrar里去看才能揭曉。

總之,DefaultFormattingConversionService作為默認(rèn)的格式化轉(zhuǎn)換服務(wù),它是DefaultConversionService的超集,在其基礎(chǔ)上擴(kuò)展了格式化器,格式化注解支持等相關(guān)能力。在Spring環(huán)境下,大多數(shù)情況使用都是它而非DefaultConversionService。

 

現(xiàn)在,對(duì)FormatterRegistry類一個(gè)籠統(tǒng)的認(rèn)識(shí),知道它默認(rèn)給注冊(cè)了哪些組件,支持哪些功能,但是細(xì)節(jié)部分還不清晰。比如說(shuō):支持哪些數(shù)據(jù)類型?支持哪些格式?這些都藏在相應(yīng)的xxxRegistrar里~

FormatterRegistrar:注冊(cè)員

registrar:登記員;注冊(cè)主任。

xxxRegistrar它是一種“倒排”思想的設(shè)計(jì)體現(xiàn),能達(dá)到高內(nèi)聚的效果。Spring、Spring Boot慣用的“伎倆”,譬如你隨便一搜就能看能看到很多很多:

 

FormatterRegistrar代表的是格式化器注冊(cè)員接口,接口定義:

  1. public interface FormatterRegistrar { 
  2.  void registerFormatters(FormatterRegistry registry); 

接口方法含義:將Converter和Formatter注冊(cè)進(jìn)FormatterRegistry注冊(cè)中心里,至于注冊(cè)哪些組件由各子類自行管理和負(fù)責(zé),而非Registry注冊(cè)中心主動(dòng)去編排。這是一種倒排設(shè)計(jì)思想,能夠很好的達(dá)到高內(nèi)聚的目的。

❝注意:雖然存在ConverterRegistry和FormatterRegistry兩個(gè)接口,但只有FormatterRegistrar而 沒有 ConverterRegistrar哦❞該接口有三個(gè)實(shí)現(xiàn)類:

 

見名之意,每個(gè)實(shí)現(xiàn)子類都維護(hù)著自己分內(nèi)之事,邊界十分清晰。

DateFormatterRegistrar:Date注冊(cè)員

提供對(duì)java.util.Date、java.util.Calendar、long類型的日期時(shí)間的注冊(cè)支持。

接口方法實(shí)現(xiàn)如下:

 

①:添加常規(guī)轉(zhuǎn)換器,支持DateToLong、DateToCalendar、LongToCalendar等基礎(chǔ)轉(zhuǎn)換能力②:若有個(gè)性化指定格式化器,那就給Calendar專門使用。當(dāng)然,大多數(shù)情況下并不會(huì)這么做,這步邏輯是為了向后兼容性而考慮而已,一般可忽略③:添加@DateTimeFormat注解的解析支持

代碼示例

下面介紹DateFormatterRegistrar注冊(cè)員的使用示例。

普通使用方式

最常規(guī)的轉(zhuǎn)換,Date、Long、Calendar等日期時(shí)間類型似乎是可以互轉(zhuǎn)的。

  1. @Test 
  2. public void test1() { 
  3.     FormattingConversionService conversionService = new FormattingConversionService(); 
  4.     // 注冊(cè)員負(fù)責(zé)添加格式化器以支持Date系列的轉(zhuǎn)換 
  5.     new DateFormatterRegistrar().registerFormatters((FormatterRegistry) conversionService); 
  6.  
  7.     // 1、普通使用 
  8.     long currMills = System.currentTimeMillis(); 
  9.     System.out.println("當(dāng)前時(shí)間戳:" + currMills); 
  10.     // Date -> Calendar 
  11.     System.out.println(conversionService.convert(new Date(currMills), Calendar.class)); 
  12.     // Long ->  Date 
  13.     System.out.println(conversionService.convert(currMills, Date.class)); 
  14.     // Calendar -> Long 
  15.     Calendar calendar = Calendar.getInstance(TimeZone.getDefault()); 
  16.     calendar.setTimeInMillis(currMills); 
  17.     System.out.println(conversionService.convert(calendar, Long.class)); 

運(yùn)行程序,輸出:

  1. 當(dāng)前時(shí)間戳:1612741385457 
  2. java.util.GregorianCalendar[time=1612741385457 ... 
  3. Mon Feb 08 07:43:05 CST 2021 
  4. 1612741385457 

完美。

注解使用方式

使用更高級(jí)的注解方式,如@DateTimeFormat

  1. // 準(zhǔn)備一個(gè)Java Bean: 
  2. @Data 
  3. @AllArgsConstructor 
  4. class Son { 
  5.  
  6.     @DateTimeFormat(iso = DateTimeFormat.ISO.DATE
  7.     private Date birthday; 
  8.  

測(cè)試代碼:

  1. @Test 
  2. public void test1() { 
  3.     FormattingConversionService conversionService = new FormattingConversionService(); 
  4.     // 重要:重要:重要:注冊(cè)基礎(chǔ)的轉(zhuǎn)換能力 
  5.     DefaultConversionService.addDefaultConverters((ConverterRegistry) conversionService); 
  6.     // 注冊(cè)員負(fù)責(zé)添加格式化器以支持Date系列的轉(zhuǎn)換 
  7.     new DateFormatterRegistrar().registerFormatters((FormatterRegistry) conversionService); 
  8.  
  9.     // 1、注解使用 
  10.     Son son = new Son(new Date()); 
  11.     // 輸出:將Date類型輸出為L(zhǎng)ong類型 
  12.     System.out.println(conversionService.convert(son.getBirthday(), Long.class)); 
  13.     // 輸出:將String烈性輸入為Date類型 
  14.     // System.out.println(conversionService.convert("2021-02-12"Date.class)); // 報(bào)錯(cuò) 
  15.     System.out.println(conversionService.convert(1613034123709L, Date.class)); 

運(yùn)行程序,輸出:

  1. 1613034230018 
  2. Thu Feb 11 17:02:03 CST 2021 

完美。實(shí)現(xiàn)了Long類型 <-> Date類型的互轉(zhuǎn)。

可能有同學(xué)會(huì)問了,為毛"2021-02-12"就不能convert到Date類型呢?這個(gè)原因,額,嗯,哼,若你看了上篇文章 的話,這將不會(huì)是個(gè)問題。

當(dāng)然,在實(shí)際使用中,更多的情況是String -> Date的轉(zhuǎn)換case,怎么破?有兩個(gè)辦法:

回味本系列前面文章,因?yàn)榍懊嬗兄v了不止一次

關(guān)注后面文章。因?yàn)榇薱ase過于常見,后面(特別是在Spring MVC下使用)依舊會(huì)重點(diǎn)提及

總結(jié)

本文重點(diǎn)是想經(jīng)由FormatterRegistry注冊(cè)中心,引述出Spring常用的Registrar注冊(cè)員設(shè)計(jì)思想,它是一種面向?qū)ο缶幊趟枷氲捏w現(xiàn),是不是比面向過程優(yōu)雅很多呢?本文以DateTimeFormatterRegistrar為示例進(jìn)行了打樣,可以看到Spring在API抽象這塊著實(shí)是非常優(yōu)秀的,擴(kuò)展性和方便性兼具,這個(gè)度把握得絕佳,或許這也算是設(shè)計(jì)美學(xué)吧。

責(zé)任編輯:武曉燕 來(lái)源: BAT的烏托邦
相關(guān)推薦

2019-02-11 11:16:13

2018-06-16 08:35:57

UnixLinux命令

2020-11-19 15:14:30

APP網(wǎng)站軟件

2018-05-04 09:14:09

Git技巧shell命令

2021-04-15 11:37:47

NumpyPython代碼

2021-09-12 17:23:57

canvas動(dòng)畫函數(shù)

2017-06-05 12:06:00

2017-12-22 09:59:43

2020-06-23 11:49:08

神經(jīng)網(wǎng)絡(luò)數(shù)據(jù)圖形

2017-10-23 16:28:33

2020-08-06 07:54:24

SpringBoot 圖片識(shí)別

2020-07-08 15:30:29

Java面試題代碼

2017-08-28 21:02:55

深度學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)

2020-11-24 11:30:51

SpringJava代碼

2019-05-06 10:19:31

服務(wù)高可用部署

2020-12-24 08:07:18

SpringBootSpring SecuWeb

2024-04-15 08:17:21

Spring依賴注入循環(huán)依賴

2025-04-29 08:21:30

ANRAndroidUI

2021-06-07 12:06:19

SpringCloud Sleuth微服務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)