中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用@DateTimeFormat和@NumberFormat

發布時間:2021-10-19 11:26:55 來源:億速云 閱讀:355 作者:iii 欄目:web開發

本篇內容介紹了“如何使用@DateTimeFormat和@NumberFormat”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

本文提綱

如何使用@DateTimeFormat和@NumberFormat

版本約定

  • Spring Framework:5.3.x

  • Spring Boot:2.4.x

正文

據我了解,@DateTimeFormat是開發中出鏡率很高的一個注解,其作用見名之意很好理解:對日期時間格式化。但使用起來常常迷糊。比如:使用它還是com.fasterxml.jackson.annotation.JsonFormat注解呢?能否使用在Long類型上?能否使用在JSR  310日期時間類型上?

有這些問號其實蠻正常,但切忌囫圇吞棗,也不建議強記這些問題的答案,而是通過規律在原理層面去理解,不僅能更牢靠而且更輕松,這或許是學習編程最重要的必備技巧之一。

@DateTimeFormat和@NumberFormat

在類型轉換/格式化方面注解,Spring提供了兩個:

  • @DateTimeFormat:將Field/方法參數格式化為日期/時間類型

  • @NumberFormat:將Field/方法參數格式化為數字類型

值得關注的是:這里所說的日期/時間類型有很多,如最古老的java.util.Date類型、JSR  310的LocalDate類型甚至時間戳Long類型都可稱作日期時間類型;同樣的,數字類型也是個泛概念,如Number類型、百分數類型、錢幣類型也都屬此范疇。

  • ?話外音:這兩個注解能夠作用的類型很廣很廣?分別看看這兩個注解定義,不可謂不簡單:

  1. @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) 

  2. public @interface DateTimeFormat { 

  3.  

  4.  String style() default "SS"; 

  5.  ISO iso() default ISO.NONE; 

  6.  String pattern() default ""; 

  7.   


@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) public @interface NumberFormat {   Style style() default Style.DEFAULT;  String pattern() default ""; }

哥倆有兩個共通的點:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 都支持標注在方法Method、字段Field、方法參數Parameter上

  3. 均支持靈活性極大的pattern屬性,此屬性支持Spring占位符書寫形式

  4. 對于pattern屬性你既可以用字面量寫死,也可以用形如${xxx.xxx.pattern}占位符形式這種更富彈性的寫法

咱們在使用這兩個注解時,最最最常用的是pattern這個屬性沒有之一,理由是它非常的靈活強大,能滿足各式各樣格式化需求。從這一點也側面看出,咱們在日期/時間、數字方面的格式化,并不遵循國際標準(如ISO),而普遍使用的“中國標準”。

由于這兩個注解幾乎所有同學都在Spring MVC上使用過,那么本文就先原理再示例。在知曉了其背后原理后再去使用,別有一番體會。

AnnotationFormatterFactory

說到格式化注解,就不得不提該工廠類,它是實現原理的核心所在。

字面含義:注解格式化工廠。用大白話解釋:該工廠用于為標注在Field字段上的注解創建對應的格式化器進而對值進行格式化處理。從這句話里可提取出幾個關鍵因素:

如何使用@DateTimeFormat和@NumberFormat
  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 注解

  3. 字段Field

  4. 這里Field并不只表示java.lang.reflect.Field,像方法返回類型、參數類型都屬此范疇,下面使用示例會有所體會

  5. 格式化器Formatter

接口定義:

public interface AnnotationFormatterFactory<A extends Annotation> {    Set<Class<?>> getFieldTypes();    Printer<?> getPrinter(A annotation, Class<?> fieldType);  Parser<?> getParser(A annotation, Class<?> fieldType); }

接口共定義了三個方法:

  • getFieldTypes:支持的類型。注解標注在這些類型上時該工廠就能夠處理,否則不管

  • getPrinter:為標注有annotation注解的fieldType類型生成一個Printer

  • getParser:為標注有annotation注解的fieldType類型生成一個Parser

此接口Spring內建有如下實現:

如何使用@DateTimeFormat和@NumberFormat

雖然實現有5個之多,但其實只有兩類,也就是說面向使用者而言只需做兩種區分即可,分別對應上面所講的兩個注解。這里A哥把它繪制成圖所示:

如何使用@DateTimeFormat和@NumberFormat

紅色框框部分(以及其處理的Field類型)是咱們需要關注的重點,其它的留個印象即可。

關于日期時間類型,我在多篇文章里不再推薦使用java.util.Date類型(更不建議使用Long類型嘍),而是使用Java 8提供的JSR  310日期時間類型100%代替(包括代替joda-time)。但是呢,在當下階段java.util.Date類型依舊不可忽略(龐大存量市場,龐大“存量”程序員的存在),因此決定把DateTimeFormatAnnotationFormatterFactory依舊還是抬到桌面上來敘述敘述,但求做得更全面些。

?關于JDK的日期時間我寫了一個非常全的系列,詳情點擊這里直達:日期時間系列,建議先行了解?

DateTimeFormatAnnotationFormatterFactory

對應的格式化器API是:org.springframework.format.datetime.DateFormatter。

@since 3.2版本就已存在,專用于對java.util.Date體系 +  @DateTimeFormat的支持:創建出相應的Printer/Parser。下面解讀其源碼:

如何使用@DateTimeFormat和@NumberFormat

①:該工廠類專為@DateTimeFormat注解服務②:借助Spring的StringValueResolver對占位符(若存在)做替換

如何使用@DateTimeFormat和@NumberFormat

這部分源碼告訴我們:@DateTimeFormat注解標注在如圖的這些類型上時才有效,才能被該工廠處理從而完成相應創建工作。

  • ?注意:除了Date和Calendar類型外,還有Long類型哦,請不要忽略了?

如何使用@DateTimeFormat和@NumberFormat

核心處理邏輯也比較好理解:不管是Printer還是Parser最終均委托給DateFormatter去完成,而此API在本系列前面文章已做了詳細講解。電梯直達

值得注意的是:DateFormatter 只能  處理Date類型。換句話講getFormatter()方法的第二個參數fieldType在此方法里并沒有被使用,也就是說缺省情況下@DateTimeFormat注解并不能正常處理其標注在Calendar、Long類型的Case。若要得到支持,需自行重寫其getPrinter/getParser等方法。

使用示例

由于@DateTimeFormat可以標注在成員屬性、方法參數、方法(返回值)上,且當其標注在Date、Calendar、Long等類型上時方可交給本工廠類來處理生成相應的處理類,本文共用三個案例case進行覆蓋。

case1:成員屬性 + Date類型。輸入 + 輸出

準備一個標注有@DateTimeFormat注解的Field屬性,為Date類型

@Data @AllArgsConstructor class Person {      @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")     private Date birthday;      }

書寫測試程序:

@Test public void test1() throws Exception {     AnnotationFormatterFactory annotationFormatterFactory = new DateTimeFormatAnnotationFormatterFactory();      // 找到該field     Field field = Person.class.getDeclaredField("birthday");     DateTimeFormat annotation = field.getAnnotation(DateTimeFormat.class);     Class<?> type = field.getType();      // 輸出:     System.out.println("輸出:Date -> String====================");     Printer printer = annotationFormatterFactory.getPrinter(annotation, type);     Person person = new Person(new Date());     System.out.println(printer.print(person.getBirthday(), Locale.US));      // 輸入:     System.out.println("輸入:String -> Date====================");     Parser parser = annotationFormatterFactory.getParser(annotation, type);     Object output = parser.parse("2021-02-06 19:00:00", Locale.US);     person = new Person((Date) output);     System.out.println(person); }

運行程序,輸出:

輸出:Date -> String==================== 2021-02-06 22:21:56 輸入:String -> Date==================== Person(birthday=Sat Feb 06 19:00:00 CST 2021)

完美。

case2:方法參數 + Calendar。輸入

@Test public void test2() throws NoSuchMethodException, ParseException {     AnnotationFormatterFactory annotationFormatterFactory = new DateTimeFormatAnnotationFormatterFactory();      // 拿到方法入參     Method method = this.getClass().getDeclaredMethod("method", Calendar.class);     Parameter parameter = method.getParameters()[0];     DateTimeFormat annotation = parameter.getAnnotation(DateTimeFormat.class);     Class<?> type = parameter.getType();      // 輸入:     System.out.println("輸入:String -> Calendar====================");     Parser parser = annotationFormatterFactory.getParser(annotation, type);     Object output = parser.parse("2021-02-06 19:00:00", Locale.US);  // 給該方法傳入“轉換好的”參數,表示輸入     method((Calendar) output); }  public void method(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Calendar calendar) {     System.out.println(calendar);     System.out.println(calendar.getTime()); }

運行程序,報錯:

輸入:String -> Calendar====================  java.lang.ClassCastException: java.util.Date cannot be cast to java.util.Calendar  ...

通過文上的闡述,這個錯誤是在意料之中的。下面通過自定義一個增強實現來達到目的:

class MyDateTimeFormatAnnotationFormatterFactory extends DateTimeFormatAnnotationFormatterFactory {      @Override     public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {         if (fieldType.isAssignableFrom(Calendar.class)) {             return new Parser<Calendar>() {                 @Override                 public Calendar parse(String text, Locale locale) throws ParseException {                     // 先翻譯為Date                     Formatter<Date> formatter = getFormatter(annotation, fieldType);                     Date date = formatter.parse(text, locale);                      // 再翻譯為Calendar                     Calendar calendar = Calendar.getInstance(TimeZone.getDefault());                     calendar.setTime(date);                     return calendar;                 }              };         }         return super.getParser(annotation, fieldType);     } }

將測試程序中的工廠類換為自定義的增強實現:

AnnotationFormatterFactory annotationFormatterFactory = new MyDateTimeFormatAnnotationFormatterFactory();

再次運行程序,輸出:

輸入:String -> Calendar==================== java.util.GregorianCalendar[time=1612609200000, ... Sat Feb 06 19:00:00 CST 2021

完美。

case3:方法返回值 + Long。輸出 建議自行實現,略

?時間戳被經常用來做時間傳遞,那么傳輸中的Long類型如何被自動封裝  為Date類型(輸入)呢?動動手鞏固下吧~?

Jsr310DateTimeFormatAnnotationFormatterFactory

對應的格式化器API是:Spring的org.springframework.format.datetime.standard.DateTimeFormatterFactory以及JDK的java.time.format.DateTimeFormatter。

@since 4.0。JSR 310時間是伴隨著Java 8的出現而出現的,Spring自4.0 開始支持 Java 8,自5.0至少基于 Java  8,因此此類since 4.0就不好奇嘍。

從類名能讀出它用于處理JSR 310日期時間。下面解讀一下它的部分源碼,透過現象看其本質:

如何使用@DateTimeFormat和@NumberFormat

①:該工廠專為@DateTimeFormat注解服務②:借助Spring的StringValueResolver對占位符(若存在)做替換

如何使用@DateTimeFormat和@NumberFormat

@DateTimeFormat注解標注在這些類型上時,就會交給此工廠類來負責其格式化器的創建工作。

如何使用@DateTimeFormat和@NumberFormat

①:得到一個JDK的java.time.format.DateTimeFormatter,由它負責將 日期/時間 ->  String類型的格式化。由于JSR  310日期/時間的格式化JDK自己實現得已經非常完善,Spring只需要將它整合進來就成。但是呢,DateTimeFormatter它是線程安全的無法同時設置iso、pattern等個性化參數,于是Spring就造了DateTimeFormatterFactory工廠類,用它用來抹平使用上的差異,達到(和java.util.Date)一致的使用體驗。當然嘍,這個知識點屬于上篇文章的內容,欲回顧詳情可點擊這里電梯直達。

回到本處,getFormatter()方法得到格式化器實例是關鍵,具體代碼如下:

如何使用@DateTimeFormat和@NumberFormat

使用Spring的工廠類DateTimeFormatterFactory構建出一個JSR  310的日期時間格式化器DateTimeFormatter來處理。有了上篇文章的鋪墊,相信這個邏輯無需再多費一言解釋了哈。

②:這一大塊是對LocalXXX(含LocalDate/Time)標準格式化器做的特殊處理:將ISO_XXX格式化模版適配為更加適用的ISO_Local_XXX格式化模版,更加精確。③:TemporalAccessorPrinter它就是個Printer,實際的格式化器依舊是DateTimeFormatter,只不過它的作用是兼容到了上下文級別(和當前線程綁定)的格式化器,從而有能力用到上下文級別的格式化參數,具有更好的可定制性,如下圖所示(源碼來自TemporalAccessorPrinter):

如何使用@DateTimeFormat和@NumberFormat

強調:別看這個特性很小,但非常有用,有四兩撥千斤的功效。因為它和我們業務系統息息相關,掌握這個點可輕松實現事半功倍的效果,別人加班你加薪。關于此知識點的應用,A哥覺得值得專門寫篇文章來描述,敬請期待下文。

接下來再看看getParser()部分的實現:

如何使用@DateTimeFormat和@NumberFormat

①:TemporalAccessorParser是個Parser,同樣的也是利用了具有Context上下文特性的DateTimeFormatter來完成String  ->  TemporalAccessor工作的。熟悉這個方向的轉換邏輯的同學就知道,因為都是靜態方法調用,所以必須用“枚舉”的方式一一處理,截圖如下(源碼來自TemporalAccessorParser):

如何使用@DateTimeFormat和@NumberFormat

到此,整個Jsr310DateTimeFormatAnnotationFormatterFactory的源碼就分析完了,總結一下:

  1. 鴻蒙官方戰略合作共建——HarmonyOS技術社區

  2. 此工廠專為標注在JSR 310日期/時間類型的@DateTimeFormat注解服務

  3. 底層格式化器雙向均使用的是和上下文相關的的DateTimeFormatter,具有高度可定制化的特性。此特性雖小卻有四兩撥千斤的效果,后面會專文給出使用場景

  4. @DateTimeFormat注解的style和pattern屬性都是支持占位符形式書寫的,更富彈性

使用示例

它不像DateTimeFormatAnnotationFormatterFactory只提供了部分支持,而是提供了全部功能,感受一下。

case1:成員屬性 + LocalDate類型。輸入 + 輸出

@Data @AllArgsConstructor class Father {     @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)     private LocalDate birthday; }

測試代碼:

@Test public void test4() throws NoSuchFieldException, ParseException {     AnnotationFormatterFactory annotationFormatterFactory = new Jsr310DateTimeFormatAnnotationFormatterFactory();      // 找到該field     Field field = Father.class.getDeclaredField("birthday");     DateTimeFormat annotation = field.getAnnotation(DateTimeFormat.class);     Class<?> type = field.getType();      // 輸出:     System.out.println("輸出:LocalDate -> String====================");     Printer printer = annotationFormatterFactory.getPrinter(annotation, type);     Father father = new Father(LocalDate.now());     System.out.println(printer.print(father.getBirthday(), Locale.US));      // 輸入:     System.out.println("輸入:String -> Date====================");     Parser parser = annotationFormatterFactory.getParser(annotation, type);     Object output = parser.parse("2021-02-07", Locale.US);     father = new Father((LocalDate) output);     System.out.println(father); }

運行程序,輸出:

輸出:LocalDate -> String==================== 2021-02-07 輸入:String -> Date==================== Father(birthday=2021-02-07)

完美。

case2:方法參數 + LocalDate類型。輸入

@Test public void test5() throws ParseException, NoSuchMethodException {     AnnotationFormatterFactory annotationFormatterFactory = new Jsr310DateTimeFormatAnnotationFormatterFactory();      // 拿到方法入參     Method method = this.getClass().getDeclaredMethod("methodJSR310", LocalDate.class);     Parameter parameter = method.getParameters()[0];     DateTimeFormat annotation = parameter.getAnnotation(DateTimeFormat.class);     Class<?> type = parameter.getType();       // 輸入:     System.out.println("輸入:String -> LocalDate====================");     Parser parser = annotationFormatterFactory.getParser(annotation, type);     Object output = parser.parse("2021-02-06", Locale.US);     // 給該方法傳入“轉換好的”參數,表示輸入     methodJSR310((LocalDate) output); }  public void methodJSR310(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate localDate) {     System.out.println(localDate); }

運行程序,輸出:

輸入:String -> LocalDate==================== 2021-02-06

case3:方法返回值 + LocalDate類型。輸入

@Test public void test6() throws NoSuchMethodException {     AnnotationFormatterFactory annotationFormatterFactory = new Jsr310DateTimeFormatAnnotationFormatterFactory();      // 拿到方法返回值類型     Method method = this.getClass().getDeclaredMethod("method1JSR310");     DateTimeFormat annotation = method.getAnnotation(DateTimeFormat.class);     Class<?> type = method.getReturnType();       // 輸出:     System.out.println("輸出:LocalDate -> 時間格式的String====================");     Printer printer = annotationFormatterFactory.getPrinter(annotation, type);      LocalDate returnValue = method1JSR310();     System.out.println(printer.print(returnValue, Locale.US)); }  @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) public LocalDate method1JSR310() {     return LocalDate.now(); }

完美。

NumberFormatAnnotationFormatterFactory

對應的格式化器API是:org.springframework.format.number.AbstractNumberFormatter的三個子類

如何使用@DateTimeFormat和@NumberFormat

@since 3.0。直奔主題,源碼嘍幾眼:

如何使用@DateTimeFormat和@NumberFormat

有了上面的“經驗”,此part不用解釋了吧。

如何使用@DateTimeFormat和@NumberFormat

如何使用@DateTimeFormat和@NumberFormat

①:@NumberFormat可以標注在Number的子類型上,并生成對應的格式化器處理。

底層實現:實際的格式化動作Printer/Parser如下圖所示,全權委托給前面已介紹過的格式化器來完成,就不做過多介紹啦。有知識盲區的可乘坐電梯前往本系列前面文章查看~

如何使用@DateTimeFormat和@NumberFormat

使用示例

@NumberFormat支持標注在多種類型上,如小數、百分數、錢幣等等,由于文上已做好了鋪墊,所以這里只給出個簡單使用案例即可,舉一反三。

@Test public void test2() throws NoSuchMethodException, ParseException {     AnnotationFormatterFactory annotationFormatterFactory = new NumberFormatAnnotationFormatterFactory();      // 獲取待處理的目標類型(方法參數、字段屬性、方法返回值等等)     Method method1 = this.getClass().getMethod("method2", double.class);     Parameter parameter = method1.getParameters()[0];     NumberFormat annotation = parameter.getAnnotation(NumberFormat.class);     Class<?> fieldType = parameter.getType();      // 1、根據注解和field類型生成一個解析器,完成String -> LocalDateTime     Parser parser = annotationFormatterFactory.getParser(annotation, fieldType);     // 2、模擬轉換動作,并輸出結果     Object result = parser.parse("11%", Locale.US);     System.out.println(result.getClass());     System.out.println(result);  }  public void method2(@NumberFormat(style = NumberFormat.Style.PERCENT) double d) { }

運行程序,輸出:

class java.math.BigDecimal 0.11

完美的將11%這種百分數數字轉換為BigDecimal了。至于為何是BigDecimal類型而不是double,那都在PercentStyleFormatter里了。

“如何使用@DateTimeFormat和@NumberFormat”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

榆社县| 垦利县| 荃湾区| 乐昌市| 景东| 安宁市| 龙口市| 奎屯市| 蓝山县| 阜康市| 常熟市| 兰考县| 马边| 涞水县| 孝感市| 泌阳县| 永修县| 鄢陵县| 芜湖市| 资溪县| 临江市| 揭西县| 安丘市| 自贡市| 湟源县| 新乐市| 读书| 同德县| 会昌县| 鄂托克前旗| 蓬安县| 友谊县| 嘉峪关市| 沂南县| 慈利县| 调兵山市| 大丰市| 西和县| 无为县| 开封市| 新兴县|