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

自定義Formatter格式化器?用它就對(duì)嘍

開(kāi)發(fā) 前端
本系列(Spring類(lèi)型轉(zhuǎn)換)到現(xiàn)在,大部分的理論基礎(chǔ)已經(jīng)搞定了,很抽象甚至很枯燥有木有。還好終于快到頭了,此處應(yīng)給跟著“學(xué)”過(guò)來(lái)的自己1秒鐘掌聲。接下來(lái)的內(nèi)容會(huì)更多的偏向于應(yīng)用,比如在Spring MVC中的應(yīng)用、在IoC容器里的應(yīng)用、在JPA里的應(yīng)用等。

[[387530]]

前言

你好,我是A哥(YourBatman)。

本系列(Spring類(lèi)型轉(zhuǎn)換)到現(xiàn)在,大部分的理論基礎(chǔ)已經(jīng)搞定了,很抽象甚至很枯燥有木有。還好終于快到頭了,此處應(yīng)給跟著“學(xué)”過(guò)來(lái)的自己1秒鐘掌聲。接下來(lái)的內(nèi)容會(huì)更多的偏向于應(yīng)用,比如在Spring MVC中的應(yīng)用、在IoC容器里的應(yīng)用、在JPA里的應(yīng)用等。

后續(xù)內(nèi)容相較于前面基礎(chǔ)孰輕孰重姑且不能一概而論,但相信大部分同學(xué)會(huì)更感興趣些。畢竟具象化的東西更易接受,更順應(yīng)人性,并且很多都是些工作中會(huì)用、考試中會(huì)考、面試中會(huì)問(wèn)的知識(shí)點(diǎn),自然積極性也會(huì)高上不少。

本文作為“二者”的承上啟下,將介紹自定義ConversionService類(lèi)型轉(zhuǎn)換服務(wù)的集大成者FormattingConversionServiceFactoryBean,以及較少人會(huì)關(guān)注但設(shè)計(jì)思路卻很重要的DateTimeContext和DateTimeContextHolder內(nèi)容,很值得你看它一看。

本文提綱


版本約定

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

正文

ConversionService是Spring自3.0提出的一個(gè)全新的、統(tǒng)一的類(lèi)型轉(zhuǎn)換服務(wù),在Spring Framework下它有兩大實(shí)現(xiàn)可用于生產(chǎn):

  • DefaultConversionService:默認(rèn)注冊(cè)了非常多常規(guī)的類(lèi)型轉(zhuǎn)換器,如Number -> String、String -> Collection ...,但是它并沒(méi)有關(guān)于日期/時(shí)間、數(shù)字格式化方面的組件
  • DefaultFormattingConversionService:它在DefaultConversionService基礎(chǔ)上增強(qiáng)(但不繼承于它),增加了格式化相關(guān)的內(nèi)容。如支持:Date、JSR 310、數(shù)字錢(qián)幣百分?jǐn)?shù)等格式化相關(guān)內(nèi)容

雖說(shuō)Spring內(nèi)置的轉(zhuǎn)換器/格式化器能“應(yīng)付”絕大部分場(chǎng)景,但不免有時(shí)候我們依舊需要DIY。通過(guò)前面的學(xué)習(xí)我們知道了,向注冊(cè)中心注冊(cè)格式化器/轉(zhuǎn)換器的方式多種多樣,能否降低使用者門(mén)檻提供一種較為統(tǒng)一的編程體驗(yàn)?zāi)?有,它就是今天的主角:FormattingConversionServiceFactoryBean。

FormattingConversionServiceFactoryBean

一個(gè)工廠類(lèi),用于產(chǎn)生FormattingConversionService實(shí)例,設(shè)計(jì)它的目的是方便的集中化配置它。

在這之前,小復(fù)習(xí)一下:FormattingConversionService實(shí)現(xiàn)了FormatterRegistry接口,并且繼承自GenericConversionService,所以功能上它是DefaultConversionService的超集。一般來(lái)講,我們常說(shuō)的ConversionService轉(zhuǎn)換服務(wù)底層實(shí)現(xiàn)使用的就是它(的子類(lèi)),區(qū)分如下case:

  • 在Spring Framework環(huán)境下,其子類(lèi) 只有 DefaultFormattingConversionService(默認(rèn)有很多格式化器/轉(zhuǎn)換器,支持JSR 310、數(shù)字格式化、格式化注解等)
  • 在Spring Boot環(huán)境下,其子類(lèi)還有 ApplicationConversionService和WebConversionService
  1. ApplicationConversionService不繼承于DefaultFormattingConversionService但功能強(qiáng)于它:表現(xiàn)在額外增加了更多轉(zhuǎn)換器,且能夠從容器里自動(dòng)檢索出Converter/Formatter類(lèi)型的Bean然后注冊(cè)上去
  2. WebConversionService繼承自DefaultFormattingConversionService,并且增強(qiáng)了對(duì)JSR 310的更強(qiáng)支持。在Spring Boot的web環(huán)境下,該實(shí)例取代了通過(guò)注解 @EnableWebMvc/@EnableWebFlux默認(rèn)指定的轉(zhuǎn)換服務(wù)實(shí)例

 

另外請(qǐng)切記,ConversionService作為基礎(chǔ)組件,并非全局只有一個(gè)。在Spring Framework和Spring Boot環(huán)境下有著不同表現(xiàn),在本系列后半部分對(duì)此會(huì)再做詳細(xì)的使用分析。

為何需要?

根據(jù)本系列前面文章所講,雖然格式化器/轉(zhuǎn)換器的底層表現(xiàn)形式均為xxxConverter,但其“上層”的注冊(cè)方式卻不單一,提供了多種多樣的方式,表現(xiàn)出了極大的靈活性,便于使用和擴(kuò)展。就拿FormatterRegistry(繼承自ConverterRegistry)注冊(cè)中心來(lái)說(shuō),它提供了很多方法讓你可以向注冊(cè)中心注冊(cè)格式化器/轉(zhuǎn)換器,如下API:

  1. // ==========1、直接注冊(cè)Converter轉(zhuǎn)換器========== 
  2. void addConverter(Converter<?, ?> converter); 
  3. <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter); 
  4. void addConverter(GenericConverter converter); 
  5. void addConverterFactory(ConverterFactory<?, ?> factory); 
  6.  
  7. // ==========2、注冊(cè)Formatter格式化器(底層適配為Converter轉(zhuǎn)換器)========== 
  8. void addPrinter(Printer<?> printer); 
  9. void addParser(Parser<?> parser); 
  10. void addFormatter(Formatter<?> formatter); 
  11. void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); 
  12.  
  13. // ==========3、通過(guò)注解工廠方式為某些標(biāo)有制定注解的格式注冊(cè)格式化器/轉(zhuǎn)換器========== 
  14. void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); 

除了這些直接用于注冊(cè)的接口API能夠完成注冊(cè)外,Spring還提供了一些批量注冊(cè)方式。雖然底層依舊依賴(lài)于這些API接口,但這種聚合手段大大提高了其可治理性,簡(jiǎn)化了注冊(cè)流程。譬如前面用專(zhuān)門(mén)文章重點(diǎn)介紹過(guò)的FormatterRegistrar注冊(cè)員就是典型代表。

關(guān)于格式化器/轉(zhuǎn)換器的注冊(cè)方式,A哥嘗試畫(huà)張圖來(lái)表示:


由此清晰可見(jiàn),注冊(cè)格式化器/轉(zhuǎn)換器的方式有很多很多。因此為了方便起見(jiàn),Spring設(shè)計(jì)了FormattingConversionServiceFactoryBean來(lái)集中化的向容器提供一個(gè)ConversionService實(shí)例,盡量提供統(tǒng)一化編程體驗(yàn)來(lái)屏蔽更多細(xì)節(jié),對(duì)使用者友好。

如何實(shí)現(xiàn)?

知曉了此FactoryBean的功能定位,實(shí)現(xiàn)其實(shí)就比較簡(jiǎn)單嘍,無(wú)非就是把各種“手段”整合到一起,可集中化定制和管理罷了。


從這些成員變量就能看到注冊(cè)轉(zhuǎn)換器的所有手段都被包含了進(jìn)來(lái)。細(xì)心的你有可能會(huì)疑問(wèn):咋沒(méi)看到通過(guò)注解工廠AnnotationFormatterFactory的方式呀???

其實(shí)它被歸類(lèi)到了Set formatters(Set的泛型類(lèi)型是?),如下源碼可“證明”:


①:負(fù)責(zé)注冊(cè)所有的轉(zhuǎn)換器。包括Converter、ConverterFactory、GenericConverter三種類(lèi)型,覆蓋1:1、N:1、N:N所有場(chǎng)景②:負(fù)責(zé)注冊(cè)格式化器Formatter和注解工廠方式。這里有兩點(diǎn)值得你特別注意:

  1. 并不支持單獨(dú)注冊(cè)Printer/Parser,因?yàn)镾pring認(rèn)為任何一個(gè)類(lèi)型的格式化器應(yīng)該是雙向的
  2. AnnotationFormatterFactory是放在Set formatters里的,和Formatter放在一起

③:負(fù)責(zé)處理注冊(cè)員xxxRegistrar的批量注冊(cè)動(dòng)作。如DateTimeFormatterRegistrar和DateFormatterRegistrar等,關(guān)于注冊(cè)員FormatterRegistrar詳細(xì)介紹可參見(jiàn)這篇文章:11. 春節(jié)禮物:Spring的Registrar倒排思想送給你

最后,從上面這張圖還有一點(diǎn)值得你關(guān)注:該工廠產(chǎn)生的ConversionService實(shí)例是固定的 DefaultFormattingConversionService,這就是我為何說(shuō)在Spring Framework環(huán)境下默認(rèn)使用的ConversionService實(shí)例都是它的原因,這不管是web還是非web場(chǎng)景。

使用場(chǎng)景

誠(chéng)然,直接使用FormattingConversionServiceFactoryBean的場(chǎng)景是不多的,除非你對(duì)此機(jī)制非常了解想進(jìn)行完全替換,那么推薦你使用它。

舉個(gè)例子:在Spring Framework環(huán)境下,若要啟用Spring MVC模塊的話會(huì)使用@EnableWebMvc注解來(lái)開(kāi)啟,此時(shí)Spring MVC默認(rèn)就向容器放入了一個(gè)ConversionService實(shí)例:

  1. WebMvcConfigurationSupport: 
  2.  
  3.  @Bean 
  4.  public FormattingConversionService mvcConversionService() { 
  5.   FormattingConversionService conversionService = new DefaultFormattingConversionService(); 
  6.   addFormatters(conversionService); 
  7.   return conversionService; 
  8.  } 
  9.  
  10.  protected void addFormatters(FormatterRegistry registry) { 
  11.  } 

暴露了addFormatters()這個(gè)擴(kuò)展點(diǎn),一般來(lái)講若你想自定義格式化器/轉(zhuǎn)換器的話,通過(guò)復(fù)寫(xiě)此方法添加是被推薦的方式。

  • ❝說(shuō)明:這里僅代表在Spring Framework環(huán)境下,若在Spring Boot下會(huì)有不同表現(xiàn)和不同的自定義方式❞

另外呢,從這部分源碼可以看到這里并沒(méi)有通過(guò)FormattingConversionServiceFactoryBean來(lái)構(gòu)建類(lèi)型轉(zhuǎn)換服務(wù)實(shí)例,而是通過(guò)直接new的方式。其實(shí)來(lái)講,這里若使用FormattingConversionServiceFactoryBean來(lái)構(gòu)建我認(rèn)為是能夠更方便的,而且也更方便留下擴(kuò)展點(diǎn),你覺(jué)得呢?

DateTimeContext:細(xì)粒度個(gè)性化定制

Spring自4.0起提供了DateTimeContextHolder,其用于線程綁定DateTimeContext。而DateTimeContext提供了:Chronology(Java中的日歷系統(tǒng))、ZoneId(JSR 310中的時(shí)區(qū))、DateTimeFormatter(JSR 310格式化器)等上下文數(shù)據(jù),如果需要這種上下文信息的話,可以使用這個(gè)API進(jìn)行綁定。

  1. public class DateTimeContext { 
  2.  
  3.  @Nullable 
  4.  private Chronology chronology; 
  5.  @Nullable 
  6.  private ZoneId timeZone; 
  7.  
  8.  ... // 省略get/set 

若有定制需要,可以向該上下文實(shí)例設(shè)置這兩個(gè)值(日歷和時(shí)區(qū)),當(dāng)然最重要的當(dāng)屬?gòu)纳舷挛闹蝎@取到一個(gè)格式化器,這也是最終目的:


①:若設(shè)置了timeZone時(shí)區(qū),就以其為準(zhǔn)。否則執(zhí)行步驟②②:若沒(méi)設(shè)置時(shí)區(qū),嘗試從LocaleContext上下文里獲取時(shí)區(qū),有就有沒(méi)有就沒(méi)有

簡(jiǎn)而言之,這個(gè)步驟就是根據(jù)上下文設(shè)置的參數(shù)(有就有沒(méi)有就沒(méi)有)得到一個(gè)DateTimeFormatter實(shí)例用于格式化,注意:此方法是實(shí)例方法 而非靜態(tài)方法,所以先得自己new一個(gè)DateTimeContext喲。

再看DateTimeContextHolder,它用ThreadLocal把DateTimeContext和線程綁定,方便使用者獲取上下文數(shù)據(jù):

  1. private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext"); 

本類(lèi)除了對(duì)DateTimeContext的維護(hù)外,提供了一個(gè)更直接的方法:根據(jù)當(dāng)前上下文情況,直接獲取到DateTimeFormatter格式化器實(shí)例:


①:給調(diào)用者傳入的格式化器綁定上Locale屬性,若存在的話②:獲取到當(dāng)前上下文對(duì)象DateTimeContext,進(jìn)而根據(jù)當(dāng)前上下文(若存在)得到加工后的DateTimeFormatter實(shí)例

該靜態(tài)方法可認(rèn)為是對(duì)DateTimeContext#getFormatter()的封裝并擴(kuò)展出Locale參數(shù)也可自定義,使用者可以一步到位獲取到和上下文相關(guān)的DateTimeFormatter實(shí)例,大多數(shù)時(shí)候我們直接使用此方法更為方便。

  • ❝提問(wèn):為何Locale參數(shù)不一起放到LocalDateContext上下文屬性里呢?你能猜到Spring是如何設(shè)計(jì)如何考慮的嗎?❞

使用場(chǎng)景

和其它xxxContext一樣,結(jié)合使用場(chǎng)景去了解它才能更深刻,畢竟一切的學(xué)習(xí)都是為了應(yīng)用嘛。Context上下文的概念在程序的世界里已經(jīng)非常多見(jiàn)了,不管是做業(yè)務(wù)開(kāi)發(fā)、中間件開(kāi)發(fā)、基礎(chǔ)架構(gòu)開(kāi)發(fā)我認(rèn)為都有理由會(huì)應(yīng)用。

由于DateTimeFormatter是線程安全的,因此為了開(kāi)發(fā)方便,通常會(huì)定一個(gè)(已經(jīng)配置好的)全局通用的實(shí)例,形如這樣:

  1. /** 
  2.  * 全局通用的日期-時(shí)間格式化器(當(dāng)然還可以有日期專(zhuān)用的、時(shí)間專(zhuān)用的...) 
  3.  */ 
  4. public static final DateTimeFormatter GLOBAL_DATETIME_FORMATTER = DateTimeFormatter 
  5.         .ofPattern("yyyy-MM-dd HH:mm:ss"
  6.         .withLocale(Locale.CHINA) 
  7.         .withZone(ZoneId.of("Asia/Shanghai")) 
  8.         .withChronology(IsoChronology.INSTANCE); 

這樣子項(xiàng)目中所有需要使用到格式化器DateTimeFormatter的地方從這里獲取即可,即便利又得到了統(tǒng)一管理,可謂一舉兩得。

但是,但是,但是,避免不了有時(shí)候會(huì)有個(gè)性化的的格式化需求,并且個(gè)性化的粒度還很細(xì)。如在Spring MVC場(chǎng)景下,不同的接口的返回值想自定義Locale、自定義ZoneId時(shí)區(qū)等從而返回不同的數(shù)據(jù)格式,但是又想復(fù)用全局的設(shè)置以盡量保持統(tǒng)一(畢竟個(gè)性化的參數(shù)一般僅1~2個(gè)而已)。

聽(tīng)到不同接口,敏感的就能發(fā)現(xiàn)這是一個(gè)典型的可以用Context解決的場(chǎng)景:既不影響全局,又能實(shí)現(xiàn)線程級(jí)別的個(gè)性化定制。下面針對(duì)此場(chǎng)景,我用代碼示例模擬Demo。

代碼示例

  1. @Test 
  2. public void test1() throws InterruptedException { 
  3.     // 模擬請(qǐng)求參數(shù)(同一個(gè)參數(shù),在不同接口里的不同表現(xiàn)) 
  4.     Instant start = Instant.now(); 
  5.  
  6.     // 模擬Controller的接口1:zoneId不一樣 
  7.     new Thread(() -> { 
  8.         DateTimeContext context = new DateTimeContext(); 
  9.         context.setTimeZone(ZoneId.of("America/New_York")); 
  10.         DateTimeContextHolder.setDateTimeContext(context); 
  11.         // 基于全局的格式化器 + 自己的上下文自定義一個(gè)本接口專(zhuān)用的格式化器 
  12.         DateTimeFormatter primaryFormatter = DateTimeContextHolder.getFormatter(GLOBAL_DATETIME_FORMATTER, null); 
  13.  
  14.         System.out.printf("北京時(shí)間%s 接口1時(shí)間%s \n"
  15.                 GLOBAL_DATETIME_FORMATTER.format(start), 
  16.                 primaryFormatter.format(start)); 
  17.     }).start(); 
  18.  
  19.     // 模擬Controller的接口2:Locale不一樣 
  20.     new Thread(() -> { 
  21.         // 基于全局的格式化器 + 自己的上下文自定義一個(gè)本接口專(zhuān)用的格式化器 
  22.         DateTimeFormatter primaryFormatter = DateTimeContextHolder.getFormatter(GLOBAL_DATETIME_FORMATTER, Locale.US); 
  23.  
  24.         System.out.printf("北京時(shí)間%s 接口2時(shí)間%s \n"
  25.                 GLOBAL_DATETIME_FORMATTER.format(start), 
  26.                 primaryFormatter.format(start)); 
  27.     }).start(); 
  28.  
  29.     TimeUnit.SECONDS.sleep(2); 

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

  1. 北京時(shí)間2021-03-15T07:29:37.8+08:00[Asia/Shanghai] 接口1時(shí)間2021-03-14T19:29:37.8-04:00[America/New_York] 
  2. 北京時(shí)間2021-03-15T07:29:37.8+08:00[Asia/Shanghai] 接口2時(shí)間2021-03-15T07:29:37.8+08:00[Asia/Shanghai] 

完美。通過(guò)這種操作上下文的方式達(dá)到了既復(fù)用又個(gè)性化的目的:

  1. 復(fù)用了全局格式化器的配置
  2. 個(gè)性化只局部個(gè)性化,對(duì)全局的格式化器沒(méi)有任何影響,風(fēng)險(xiǎn)可控,卻又實(shí)現(xiàn)了非常自由的個(gè)性化需求

可能有同學(xué)會(huì)問(wèn),若想自定義Pattern怎么辦呢?答案是:做不到。Java的DateTimeFormatter和Pattern屬于強(qiáng)綁定關(guān)系,Pattern改了就得用個(gè)全新的DateTimeFormatter實(shí)例,其它屬性無(wú)法(內(nèi)部)拷貝。至于什么原因,A哥在講解JDK日期時(shí)間時(shí)有提及,具體可關(guān)注我參考JDK日期時(shí)間系列。

  • ❝說(shuō)明:一般情況對(duì)一個(gè)項(xiàng)目而言,Pattern是不太可能需要個(gè)性化的。若真有此情況,那么請(qǐng)完整的自定義一個(gè)DateTimeFormatter處理吧❞

總結(jié)

本文介紹了Spring兩個(gè)組件:

  • FormattingConversionServiceFactoryBean:類(lèi)型轉(zhuǎn)換服務(wù)工廠,注冊(cè)管理格式化器/轉(zhuǎn)換器的推薦方案
  • DateTimeContext:因?yàn)樽远x日期時(shí)間格式化器屬比較常見(jiàn)的需求,因此Spring在4.0推出這套API方便使用者實(shí)現(xiàn)更細(xì)粒度的控制。還是那句話,使用好了事半功倍且代碼優(yōu)雅更易維護(hù)

關(guān)于Spring轉(zhuǎn)換器/格式化器的基礎(chǔ)內(nèi)容基本就到這了,希望這打破了很多同學(xué)以為的:類(lèi)型轉(zhuǎn)換就等于Spring MVC Controller自動(dòng)封裝的思維定式,要知道它的應(yīng)用空間還大著哩。

本系列接下來(lái)會(huì)更偏向于應(yīng)用層面的case分析,Spring MVC場(chǎng)景的使用更是”首當(dāng)其沖“嘍,歡迎關(guān)注一起探討、交流和學(xué)習(xí)。

本文思考題

本文所屬專(zhuān)欄:Spring類(lèi)型轉(zhuǎn)換,后臺(tái)回復(fù)專(zhuān)欄名即可獲取全部?jī)?nèi)容,已被https://yourbatman.cn收錄。

看完了不一定懂,看懂了不一定會(huì)。來(lái),文末3個(gè)思考題幫你復(fù)盤(pán):

如何使用FormattingConversionServiceFactoryBean自定義類(lèi)型轉(zhuǎn)換服務(wù)?

Spring設(shè)計(jì)出DateTimeContext和DateTimeContextHolder旨在解決什么問(wèn)題?

為何DateTimeContextHolder#getFormatter方法的第二個(gè)參數(shù)Locale不放到DateTimeContext里?明明可以這么干的呀

系列推薦

12. 查漏補(bǔ)缺@DateTimeFormat到底干了些啥

11. 春節(jié)禮物:Spring的Registrar倒排思想送給你

10. 原來(lái)是這么玩的,@DateTimeFormat和@NumberFormat

 

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

2009-07-07 14:32:47

JDK日志Formatter

2025-03-05 10:49:32

2021-12-06 06:36:23

fabricPython遠(yuǎn)程連接

2018-12-03 09:10:07

Linux驅(qū)動(dòng)器命令

2011-04-11 13:14:58

AjaxWEB服務(wù)

2009-06-17 16:00:03

Hibernate自定

2015-02-12 15:33:43

微信SDK

2010-02-25 11:23:29

WCF返回自定義格式

2023-08-26 19:04:40

配置write轉(zhuǎn)換器

2010-03-01 11:10:41

WCF綁定元素

2020-11-03 10:21:33

MySQL

2011-04-27 10:31:38

Java

2015-02-12 15:38:26

微信SDK

2009-08-03 14:25:59

C#日期格式化

2024-11-18 09:18:21

Gin框架驗(yàn)證器

2011-03-17 09:45:01

Spring

2022-07-12 16:56:48

自定義組件鴻蒙

2024-01-08 08:30:05

光標(biāo)圖形編輯器開(kāi)發(fā)游標(biāo)

2022-06-23 07:23:34

自定義組件計(jì)時(shí)器

2009-02-10 12:55:39

自定義控件AJAX.NET
點(diǎn)贊
收藏

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