1. 程式人生 > >2. Spring早期型別轉換,基於PropertyEditor實現

2. Spring早期型別轉換,基於PropertyEditor實現

> 青年時種下什麼,老年時就收穫什麼。關注公眾號【**BAT的烏托邦**】,有Spring技術棧、MyBatis、JVM、中介軟體等小而美的**原創專欄**供以免費學習。分享、成長,拒絕淺嘗輒止。本文已被 [**https://www.yourbatman.cn**](https://www.yourbatman.cn) 收錄。 [TOC] ![](https://img-blog.csdnimg.cn/20201203061043610.jpg#pic_center) # ✍前言 你好,我是YourBatman。 Spring早在1.0(2004年釋出,2003年孵化中)的時候,就有了型別轉換功能模組。此模組存在的必要性不必多說,相信每個同學都可理解。最初,Spring做型別轉換器是基於Java標準的`java.beans.PropertyEditor`這個API去擴充套件實現的,直到Spring 3.0後才得以出現更好替代方案(Spring 3.0釋出於2009 年12月)。 > 提示:文章末尾附有Spring主要版本的釋出時間和以及主要特性,感興趣者可文末檢視 雖說Spring自3.0就提出了更為靈活、優秀的型別轉換介面/服務,但是早期基於`PropertyEditor`實現的轉換器並未廢棄且還在發揮餘熱中,因此本文就針對其早期型別轉換實現做出專文講解。 ## 版本約定 - Spring Framework:5.3.1 - Spring Boot:2.4.0 > 說明:版本均於2020-11釋出,且版本號均不帶有`.RELEASE`字尾,這和之前是不一樣的。具體原因請參考:[Spring改變版本號命名規則:此舉對非英語國家很友好](https://mp.weixin.qq.com/s/ZoUG9h1TndW2QpnPyGeIQA) # ✍正文 若你用當下的眼光去看Spring基於`PropertyEditor`的型別轉換實現,會發現這麼搞是存在一些設計缺陷的。當然並不能這麼去看,畢竟現在都2020年了,那會才哪跟哪呢。既然Spring裡的`PropertyEditor`現如今依然健在,那咱就會會它唄。 ## PropertyEditor是什麼? `PropertyEditor`位於java.beans包中,要知道這個包裡面的類都是設計為Java GUI程式(AWT)服務的,所以你看官方javadoc對`PropertyEditor`的介紹也無出其右: ```java A PropertyEditor class provides support for GUIs that want to allow users to edit a property value of a given type. ``` 為GUI程式提供支援,允許你對給定的value進行編輯,作用類似於一個轉換器:GUI上你可以輸入、編輯某個屬性然後經過它轉換成合適的型別。 ![Java GUI程式](https://img-blog.csdnimg.cn/20201129232545656.png#pic_center) 此介面提供的方法挺多的,和本文型別轉換有關的最多隻有4個: 1. `void setValue(Object value)`:設定屬性值 2. `Object getValue()`:獲取屬性值 3. `String getAsText()`:輸出。把屬性值轉換成String輸出 4. `void setAsText(String text)`:輸入。將String轉換為屬性值型別輸入 JDK對PropertyEditor介面提供了一個預設實現`java.beans.PropertyEditorSupport`,因此我們若需擴充套件此介面,僅需繼承此類,根據需要複寫`getAsText/setAsText`這兩個方法即可,Spring無一例外都是這麼做的。 PropertyEditor作為一個JDK原生介面,內建了一些基本實現來服務於GUI程式,如: - `BooleanEditor`:將true/false字串轉換為Boolean型別 - `IntegerEditor`:將字串轉換為Integer型別 - 同類別的還有LongEditor、FloatEditor... JDK內建的實現比較少(如上),功能簡陋,但對於服務GUI程式來說已經夠用,畢竟介面輸入的只可能是字串,並且還均是基礎型別。但這對於複雜的Spring環境、以及富文字的web環境來說就不夠用了,所以Spring在此基礎上有所擴充套件,因此才有了本文來討論。 ### 注意:PropertyEditorSupport執行緒不安全 `PropertyEditor`實現的是雙向型別轉換:String和Object互轉。呼叫`setValue()`方法後,需要先“快取”起來後續才能夠使用(輸出)。`PropertyEditorSupport`為此提供了一個成員屬性來做: ```java PropertyEditorSupport: // 呼叫setValue()方法對此屬性賦值 getValue()方法取值 private Object value; ``` 這麼一來`PropertyEditorSupport`就是有狀態的了,因此是執行緒不安全的。在使用過程中需要特別注意,避免出現併發風險。 > 說明:Support類裡還看到屬性監聽器`PropertyChangeListener`,因它屬於GUI程式使用的元件,與我們無關,所以後續絲毫不會提及哦 Spring內建的**所有擴充套件**均是基於PropertyEditorSupport來實現的,因此也**都是**執行緒不安全的哦~ ## Spring為何基於它擴充套件? 官方的javadoc都說得很清楚:PropertyEditor設計是為GUI程式服務的,那麼Spring為何看上它了呢? 試想一下:那會的Spring只能支援xml方式配置,而XML屬於文字型別配置,因此在給某個屬性設定值的時候,書寫上去的**100%**是個字串,但是此屬性對應的型別卻不一定是字串,可能是任意型別。你思考下,這種場景是不是跟GUI程式(AWT)一毛一樣:輸入字串,對應任意型別。 為了實現這種需求,在`PropertyEditorSupport`的基礎上只需要複寫`setAsText`和`getAsText`這兩個方法就行,然後Spring就這麼幹了。我`個人yy`一下,當初Spring選擇這麼幹而沒自己另起爐灶的原因可能有這麼幾個: 1. 本著不重複發明輪子的原則,有得用就直接用唄,況且是100%滿足要求的 2. 示好Java EE技術。畢竟那會Spring地位還並不穩,有大腿就先榜上 3. 2003年左右,Java GUI程式還並未退出歷史舞臺,Spring為了通用性就選擇基於它擴充套件嘍 1. 說明:那會的通用性可能和現在通用性含義上是不一樣的,需要稍作區別 ## Spring內建擴充套件實現有哪些? Spring為了擴充套件自身功能,提高配置靈活性,擴展出了非常非常多的`PropertyEditor`實現,共計40餘個,部分截圖如下: ![](https://img-blog.csdnimg.cn/20201129232906258.png#pic_center) PropertyEditor | 功能 | 舉例 -------- | ----- | ----- ZoneIdEditor | 轉為java.time.ZoneId | Asia/Shanghai URLEditor | 轉為URL,支援傳統方式`file:,http:`,也支援Spring風格:`classpath:,context上下文相對路徑`等等 | http://www.baidu.com StringTrimmerEditor | trim()字串,也可刪除指定字元char | 任意字串 StringArrayPropertyEditor | 轉為字串陣列 | A,B,C PropertiesEditor | 轉為Properties | name = YourBatman PatternEditor | 轉為Pattern | (\\D*)(\\d+)(.*) PathEditor | 轉為java.nio.file.Path。支援傳統URL和Spring風格的url | classpath:xxx ClassEditor | 轉為Class | 全類名 CustomBooleanEditor | 轉為Boolean | 見示例 CharsetEditor | 轉為Charset | 見示例 CustomDateEditor | 轉為java.util.Date | 見示例 Spring把實現基本(大多數)都放在`org.springframework.beans.propertyeditors`包下,接下來我挑選幾個代表性API舉例說明。 ### 標準實現示例 - **CustomBooleanEditor**: ```java @Test public void test1() { PropertyEditor editor = new CustomBooleanEditor(true); // 這些都是true,不區分大小寫 editor.setAsText("trUe"); System.out.println(editor.getAsText()); editor.setAsText("on"); System.out.println(editor.getAsText()); editor.setAsText("yes"); System.out.println(editor.getAsText()); editor.setAsText("1"); System.out.println(editor.getAsText()); // 這些都是false(注意:null並不會輸出為false,而是輸出空串) editor.setAsText(null); System.out.println(editor.getAsText()); editor.setAsText("fAlse"); System.out.println(editor.getAsText()); editor.setAsText("off"); System.out.println(editor.getAsText()); editor.setAsText("no"); System.out.println(editor.getAsText()); editor.setAsText("0"); System.out.println(editor.getAsText()); // 報錯 editor.setAsText("2"); System.out.println(editor.getAsText()); } ``` 關注點:對於Spring來說,傳入的**true、on、yes、1**等都會被“翻譯”成true(字母不區分大小寫),大大提高相容性。 > 現在知道為啥你用Postman傳個1,用Bool值也能正常封裝進去了吧,就是它的功勞。此效果等同於轉換器`StringToBooleanConverter`,將在後面進行講述對比 - **CharsetEditor**: ```java @Test public void test2() { // 雖然都行,但建議你規範書寫:UTF-8 PropertyEditor editor = new CharsetEditor(); editor.setAsText("UtF-8"); System.out.println(editor.getAsText()); // UTF-8 editor.setAsText("utF8"); System.out.println(editor.getAsText()); // UTF-8 } ``` 關注點:utf-8中間的橫槓可要可不要,但建議加上使用標準寫法,另外字母也是不區分大小寫的。 - **CustomDateEditor**: ```java @Test public void test3() { PropertyEditor editor = new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),true); editor.setAsText("2020-11-30 09:10:10"); System.out.println(editor.getAsText()); // 2020-11-30 09:10:10 // null輸出空串 editor.setAsText(null); System.out.println(editor.getAsText()); // 報錯 editor.setAsText("2020-11-30"); System.out.println(editor.getAsText()); } ``` 關注點:這個時間/日期轉換器很不好用,構造時就必須指定一個`SimpleDateFormat`格式化器。在實際應用中,Spring並沒有使用到它,而是用後面會說到的替代方案。 ### 特殊實現 把沒有放在`org.springframework.beans.propertyeditors`包下的實現稱作特殊實現(前者稱為標準實現)。 - **MediaTypeEditor**:位於org.springframework.http。依賴的核心API是`MediaType.parseMediaType(text)`,可以把諸如text/html、application/json轉為MediaType物件 - 顯然它屬於spring-web包,使用在web環境下 - **FormatterPropertyEditorAdapter**:位於org.springframework.format.support。將3.0新增的Formatter介面適配為一個PropertyEditor:setAsText這種轉換操作委託給`formatter.parse()`去完成,反向委託給`formatter.print()`去完成。 - 此介面卡在`DataBinder#addCustomFormatter()`得到應用 - **PropertyValuesEditor**:位於org.springframework.beans。將k-v字串(Properties格式)轉換為一個PropertyValues,從而方便放進Environment裡 - **ResourceEditor**:位於org.springframework.core.io。此轉換器將String轉換為`Resource`資源,特別實用。作為基礎設施,廣泛被用到Spring的很多地方 - 像什麼標準的`FileEditor、InputSourceEditor、InputStreamEditor、URLEditor`等等與資源相關的轉換器,均依賴它而實現 ```java @Test public void test4() { // 支援標準URL如file:C:/myfile.txt,也支援classpath:myfile.txt // 同時還支援佔位符形式 PropertyEditor editor = new ResourceEditor(new DefaultResourceLoader(), new StandardEnvironment(), true); // file:形式本處略 // editor.setAsText("file:..."); // System.out.println(editor.getAsText()); // classpath形式(注意:若檔案不存在不會報錯,而是輸出null) editor.setAsText("classpath:app.properties"); System.out.println(editor.getAsText()); // 輸出帶碟符的全路徑 System.setProperty("myFile.name", "app.properties"); editor.setAsText("classpath:${myFile.name}"); System.out.println(editor.getAsText()); // 結果同上 } ``` 關注點:Spring擴展出來的Resource不僅自持常規`file:`資源協議,還支援平時使用最多的`classpath:`協議,可謂非常好用。 - **ConvertingPropertyEditorAdapter**:位於org.springframework.core.convert.support。將3.0新增的`ConversionService`轉換服務適配為一個`PropertyEditor`,內部轉換動作都委託給前者去完成。 - `AbstractPropertyBindingResult#findEditor()`為屬性尋找合適PropertyEditor的時候,若ConversionService能支援就包裝為ConvertingPropertyEditorAdapter供以使用,這是介面卡模式的典型應用場景。 ## “誰”在使用ProertyEditor ## PropertyEditor自動發現機制 ## PropertyEditor存在的缺陷 考慮到閱讀的舒適性,單篇文章不宜太長,因此涉及到`PropertyEditor`的這幾個問題,放在下篇文章單獨列出。這個幾個問題會明顯比本文更深入,歡迎保持持續關注,peace! # ✍總結 本文主要介紹了三點內容: 1. PropertyEditor是什麼? 2. Spring為何選擇基於PropertyEditor? 3. Spring內建的那些PropertyEditor都有哪些,各自什麼作用? PropertyEditor雖然已經很古老,不適合當下複雜環境。但不可否認它依舊有存在的價值,Spring內部也大量的仍在使用,因此**不容忽視**。下篇文章將深度探討Spring內部是如何使用PropertyEditor的,賦予了它哪些機制,以及最終為何還是決定自己另起爐灶搞一套呢?歡迎對本系列保持持續關注~ --- ## 附:Spring主要版本釋出時間和特性 - **2002-02**,開始開發孵化此專案,專案名叫:`interface21`,它便就是Spring的前身 - **2004-03**,1.0版釋出。進入迅速發展期 - **2006-10**,2.0版釋出。支援可擴充套件的xml配置功能、支援Java5、支援動態語言、支援更多擴充套件點 - **2007-11**,2.5版釋出。專案名從此改為Spring Source,支援Java 6,支援註解配置(部分) - **2009-12**,3.0版釋出。這是非常非常重要的一個版本,支援了模組驅動、支援SpEL、支援Java Bean配置、支援嵌入式資料庫...... - **2011和2012**,這兩年釋出了非常多的3.x系列小版本,帶來了很多驚喜,同時也讓Spring更加紮實 - **2013-12**,4.0版釋出。這是有一次進步,提供了對Java 8的全面支援,支援Java EE 7、支援websocket、泛型依賴注入...... - **2017-09**,5.0版釋出。最低JDK版本要求是Java 8,同時支援Servlet 3.1。當然最為重要的是引入了全新模組:WebFlux 截止到當前,Spring Framework的最新版本是`5.3.x`版本,此版本是5.x的最後一個主要功能分支,下個版本將是6.x嘍,咱們拭目以待。 --- ##### ✔推薦閱讀: - [1. 揭祕Spring型別轉換 - 框架設計的基石](https://mp.weixin.qq.com/s/5daOOdhIFqrGbpgtnuQMNw) - - [螞蟻金服上市了,我不想努力了](https://mp.weixin.qq.com/s/oEShE0fiHSGG8D89NRQYGw) - [如果程式設計師和產品經理都用凡爾賽文學對話......](https://mp.weixin.qq.com/s/SZUJ0sy4vM7UH10pk6NM3g) - [程式人生 | 春風得意馬蹄疾,一日看盡長安花](https://mp.weixin.qq.com/s/PGIFtpI7aZaxY7es0F6C6Q)