成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

自定義Formatter格式化器?用它就對嘍

開發 前端
本系列(Spring類型轉換)到現在,大部分的理論基礎已經搞定了,很抽象甚至很枯燥有木有。還好終于快到頭了,此處應給跟著“學”過來的自己1秒鐘掌聲。接下來的內容會更多的偏向于應用,比如在Spring MVC中的應用、在IoC容器里的應用、在JPA里的應用等。

[[387530]]

前言

你好,我是A哥(YourBatman)。

本系列(Spring類型轉換)到現在,大部分的理論基礎已經搞定了,很抽象甚至很枯燥有木有。還好終于快到頭了,此處應給跟著“學”過來的自己1秒鐘掌聲。接下來的內容會更多的偏向于應用,比如在Spring MVC中的應用、在IoC容器里的應用、在JPA里的應用等。

后續內容相較于前面基礎孰輕孰重姑且不能一概而論,但相信大部分同學會更感興趣些。畢竟具象化的東西更易接受,更順應人性,并且很多都是些工作中會用、考試中會考、面試中會問的知識點,自然積極性也會高上不少。

本文作為“二者”的承上啟下,將介紹自定義ConversionService類型轉換服務的集大成者FormattingConversionServiceFactoryBean,以及較少人會關注但設計思路卻很重要的DateTimeContext和DateTimeContextHolder內容,很值得你看它一看。

本文提綱


版本約定

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

正文

ConversionService是Spring自3.0提出的一個全新的、統一的類型轉換服務,在Spring Framework下它有兩大實現可用于生產:

  • DefaultConversionService:默認注冊了非常多常規的類型轉換器,如Number -> String、String -> Collection ...,但是它并沒有關于日期/時間、數字格式化方面的組件
  • DefaultFormattingConversionService:它在DefaultConversionService基礎上增強(但不繼承于它),增加了格式化相關的內容。如支持:Date、JSR 310、數字錢幣百分數等格式化相關內容

雖說Spring內置的轉換器/格式化器能“應付”絕大部分場景,但不免有時候我們依舊需要DIY。通過前面的學習我們知道了,向注冊中心注冊格式化器/轉換器的方式多種多樣,能否降低使用者門檻提供一種較為統一的編程體驗呢?有,它就是今天的主角:FormattingConversionServiceFactoryBean。

FormattingConversionServiceFactoryBean

一個工廠類,用于產生FormattingConversionService實例,設計它的目的是方便的集中化配置它。

在這之前,小復習一下:FormattingConversionService實現了FormatterRegistry接口,并且繼承自GenericConversionService,所以功能上它是DefaultConversionService的超集。一般來講,我們常說的ConversionService轉換服務底層實現使用的就是它(的子類),區分如下case:

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

 

另外請切記,ConversionService作為基礎組件,并非全局只有一個。在Spring Framework和Spring Boot環境下有著不同表現,在本系列后半部分對此會再做詳細的使用分析。

為何需要?

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

  1. // ==========1、直接注冊Converter轉換器========== 
  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、注冊Formatter格式化器(底層適配為Converter轉換器)========== 
  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、通過注解工廠方式為某些標有制定注解的格式注冊格式化器/轉換器========== 
  14. void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); 

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

關于格式化器/轉換器的注冊方式,A哥嘗試畫張圖來表示:


由此清晰可見,注冊格式化器/轉換器的方式有很多很多。因此為了方便起見,Spring設計了FormattingConversionServiceFactoryBean來集中化的向容器提供一個ConversionService實例,盡量提供統一化編程體驗來屏蔽更多細節,對使用者友好。

如何實現?

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


從這些成員變量就能看到注冊轉換器的所有手段都被包含了進來。細心的你有可能會疑問:咋沒看到通過注解工廠AnnotationFormatterFactory的方式呀???

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


①:負責注冊所有的轉換器。包括Converter、ConverterFactory、GenericConverter三種類型,覆蓋1:1、N:1、N:N所有場景②:負責注冊格式化器Formatter和注解工廠方式。這里有兩點值得你特別注意:

  1. 并不支持單獨注冊Printer/Parser,因為Spring認為任何一個類型的格式化器應該是雙向的
  2. AnnotationFormatterFactory是放在Set formatters里的,和Formatter放在一起

③:負責處理注冊員xxxRegistrar的批量注冊動作。如DateTimeFormatterRegistrar和DateFormatterRegistrar等,關于注冊員FormatterRegistrar詳細介紹可參見這篇文章:11. 春節禮物:Spring的Registrar倒排思想送給你

最后,從上面這張圖還有一點值得你關注:該工廠產生的ConversionService實例是固定的 DefaultFormattingConversionService,這就是我為何說在Spring Framework環境下默認使用的ConversionService實例都是它的原因,這不管是web還是非web場景。

使用場景

誠然,直接使用FormattingConversionServiceFactoryBean的場景是不多的,除非你對此機制非常了解想進行完全替換,那么推薦你使用它。

舉個例子:在Spring Framework環境下,若要啟用Spring MVC模塊的話會使用@EnableWebMvc注解來開啟,此時Spring MVC默認就向容器放入了一個ConversionService實例:

  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()這個擴展點,一般來講若你想自定義格式化器/轉換器的話,通過復寫此方法添加是被推薦的方式。

  • ❝說明:這里僅代表在Spring Framework環境下,若在Spring Boot下會有不同表現和不同的自定義方式❞

另外呢,從這部分源碼可以看到這里并沒有通過FormattingConversionServiceFactoryBean來構建類型轉換服務實例,而是通過直接new的方式。其實來講,這里若使用FormattingConversionServiceFactoryBean來構建我認為是能夠更方便的,而且也更方便留下擴展點,你覺得呢?

DateTimeContext:細粒度個性化定制

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

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

若有定制需要,可以向該上下文實例設置這兩個值(日歷和時區),當然最重要的當屬從上下文中獲取到一個格式化器,這也是最終目的:


①:若設置了timeZone時區,就以其為準。否則執行步驟②②:若沒設置時區,嘗試從LocaleContext上下文里獲取時區,有就有沒有就沒有

簡而言之,這個步驟就是根據上下文設置的參數(有就有沒有就沒有)得到一個DateTimeFormatter實例用于格式化,注意:此方法是實例方法 而非靜態方法,所以先得自己new一個DateTimeContext喲。

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

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

本類除了對DateTimeContext的維護外,提供了一個更直接的方法:根據當前上下文情況,直接獲取到DateTimeFormatter格式化器實例:


①:給調用者傳入的格式化器綁定上Locale屬性,若存在的話②:獲取到當前上下文對象DateTimeContext,進而根據當前上下文(若存在)得到加工后的DateTimeFormatter實例

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

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

使用場景

和其它xxxContext一樣,結合使用場景去了解它才能更深刻,畢竟一切的學習都是為了應用嘛。Context上下文的概念在程序的世界里已經非常多見了,不管是做業務開發、中間件開發、基礎架構開發我認為都有理由會應用。

由于DateTimeFormatter是線程安全的,因此為了開發方便,通常會定一個(已經配置好的)全局通用的實例,形如這樣:

  1. /** 
  2.  * 全局通用的日期-時間格式化器(當然還可以有日期專用的、時間專用的...) 
  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); 

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

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

聽到不同接口,敏感的就能發現這是一個典型的可以用Context解決的場景:既不影響全局,又能實現線程級別的個性化定制。下面針對此場景,我用代碼示例模擬Demo。

代碼示例

  1. @Test 
  2. public void test1() throws InterruptedException { 
  3.     // 模擬請求參數(同一個參數,在不同接口里的不同表現) 
  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.         // 基于全局的格式化器 + 自己的上下文自定義一個本接口專用的格式化器 
  12.         DateTimeFormatter primaryFormatter = DateTimeContextHolder.getFormatter(GLOBAL_DATETIME_FORMATTER, null); 
  13.  
  14.         System.out.printf("北京時間%s 接口1時間%s \n"
  15.                 GLOBAL_DATETIME_FORMATTER.format(start), 
  16.                 primaryFormatter.format(start)); 
  17.     }).start(); 
  18.  
  19.     // 模擬Controller的接口2:Locale不一樣 
  20.     new Thread(() -> { 
  21.         // 基于全局的格式化器 + 自己的上下文自定義一個本接口專用的格式化器 
  22.         DateTimeFormatter primaryFormatter = DateTimeContextHolder.getFormatter(GLOBAL_DATETIME_FORMATTER, Locale.US); 
  23.  
  24.         System.out.printf("北京時間%s 接口2時間%s \n"
  25.                 GLOBAL_DATETIME_FORMATTER.format(start), 
  26.                 primaryFormatter.format(start)); 
  27.     }).start(); 
  28.  
  29.     TimeUnit.SECONDS.sleep(2); 

運行程序,輸出:

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

完美。通過這種操作上下文的方式達到了既復用又個性化的目的:

  1. 復用了全局格式化器的配置
  2. 個性化只局部個性化,對全局的格式化器沒有任何影響,風險可控,卻又實現了非常自由的個性化需求

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

  • ❝說明:一般情況對一個項目而言,Pattern是不太可能需要個性化的。若真有此情況,那么請完整的自定義一個DateTimeFormatter處理吧❞

總結

本文介紹了Spring兩個組件:

  • FormattingConversionServiceFactoryBean:類型轉換服務工廠,注冊管理格式化器/轉換器的推薦方案
  • DateTimeContext:因為自定義日期時間格式化器屬比較常見的需求,因此Spring在4.0推出這套API方便使用者實現更細粒度的控制。還是那句話,使用好了事半功倍且代碼優雅更易維護

關于Spring轉換器/格式化器的基礎內容基本就到這了,希望這打破了很多同學以為的:類型轉換就等于Spring MVC Controller自動封裝的思維定式,要知道它的應用空間還大著哩。

本系列接下來會更偏向于應用層面的case分析,Spring MVC場景的使用更是”首當其沖“嘍,歡迎關注一起探討、交流和學習。

本文思考題

本文所屬專欄:Spring類型轉換,后臺回復專欄名即可獲取全部內容,已被https://yourbatman.cn收錄。

看完了不一定懂,看懂了不一定會。來,文末3個思考題幫你復盤:

如何使用FormattingConversionServiceFactoryBean自定義類型轉換服務?

Spring設計出DateTimeContext和DateTimeContextHolder旨在解決什么問題?

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

系列推薦

12. 查漏補缺@DateTimeFormat到底干了些啥

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

10. 原來是這么玩的,@DateTimeFormat和@NumberFormat

 

責任編輯:姜華 來源: BAT的烏托邦
相關推薦

2009-07-07 14:32:47

JDK日志Formatter

2025-03-05 10:49:32

2025-05-08 08:30:00

Redis自定義序列化數據庫

2021-12-06 06:36:23

fabricPython遠程連接

2018-12-03 09:10:07

Linux驅動器命令

2009-06-17 16:00:03

Hibernate自定

2011-04-11 13:14:58

AjaxWEB服務

2015-02-12 15:33:43

微信SDK

2020-11-03 10:21:33

MySQL

2010-03-01 11:10:41

WCF綁定元素

2010-02-25 11:23:29

WCF返回自定義格式

2023-08-26 19:04:40

配置write轉換器

2015-02-12 15:38:26

微信SDK

2009-08-03 14:25:59

C#日期格式化

2011-04-27 10:31:38

Java

2010-01-15 15:21:35

C++

2009-02-10 12:55:39

自定義控件AJAX.NET

2020-12-31 05:29:25

數據庫Powerdesign建模

2016-12-26 15:25:59

Android自定義View

2024-01-08 22:03:22

python代碼開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久国产一区二区三区 | av中文字幕网 | 成人在线中文字幕 | 日韩欧美大片在线观看 | 精产国产伦理一二三区 | 羞羞的视频在线看 | 久久久久九九九女人毛片 | 亚洲免费毛片 | 欧美精品影院 | 国产精品九九九 | 久久99精品久久久久久 | 日韩欧美专区 | 97超碰站 | 日韩av免费看 | 精品综合| 欧美日韩中文字幕在线播放 | 91av在线免费观看 | 色婷婷一区二区三区四区 | 亚洲精品久久久一区二区三区 | 国产大片黄色 | 免费天天干 | 国产伦精品一区二区三区精品视频 | 日本视频中文字幕 | 亚洲理论在线观看电影 | 欧美在线a | 天天操综合网站 | 免费黄色片在线观看 | 搞av.com| 久热国产在线 | 日韩亚洲视频 | 亚洲色图综合 | 欧美一区免费 | 国产精品视频999 | a a毛片| 91麻豆蜜桃一区二区三区 | 中文字幕 在线观看 | 亚洲九色| 盗摄精品av一区二区三区 | 亚洲一二三区精品 | 极品销魂美女一区二区 | 日韩成人 |