在Spring配置文件裏,咱們每每經過字面值爲Bean各類類型的屬性提供設置值:不論是double類型仍是int類型,在配置文件中都對應字符串類型的字面值。BeanWrapper填充Bean屬性時如何將這個字面值轉換爲對應的double或int等內部類型呢?咱們能夠隱約地感受到必定有一個轉換器在其中起做用,這個轉換器就是屬性編輯器。
「屬性編輯器」這個名字可能會讓人誤覺得是一個帶用戶界面的輸入器,其實屬性編輯器不必定非得有用戶界面,任何實現java.beans.PropertyEditor接口的類都是屬性編輯器。屬性編輯器的主要功能就是將外部的設置值轉換爲JVM內部的對應類型,因此屬性編輯器其實就是一個類型轉換器。
PropertyEditor是JavaBean規範定義的接口,JavaBean規範中還有其餘一些PropertyEditor配置的接口。爲了完全理解屬性編輯器,必須對JavaBean中有關屬性編輯器的規範進行學習,相信這些知識對學習和掌握Spring中的屬性編輯器會大有幫助。
JavaBean的編輯器
Sun所制定的JavaBean規範,很大程度上是爲IDE準備的——它讓IDE可以以可視化的方式設置JavaBean的屬性。若是在IDE中開發一個可視化應用程序,咱們須要經過屬性設置的方式對組成應用的各類組件進行定製,IDE經過屬性編輯器讓開發人員使用可視化的方式設置組件的屬性。
通常的IDE都支持JavaBean規範所定義的屬性編輯器,當組件開發商發佈一個組件時,它每每將組件對應的屬性編輯器捆綁發行,這樣開發者就能夠在IDE環境下方便地利用屬性編輯器對組件進行定製工做。
JavaBean規範經過java.beans.PropertyEditor定義了設置JavaBean屬性的方法,經過BeanInfo描述了JavaBean哪些屬性是可定製的,此外還描述了可定製屬性與PropertyEditor的對應關係。
BeanInfo與JavaBean之間的對應關係,經過二者之間規範的命名確立:對應JavaBean的BeanInfo採用以下的命名規範:<Bean>BeanInfo。如ChartBean對應的BeanInfo爲ChartBeanBeanInfo;Car對應的BeanInfo爲CarBeanInfo。當JavaBean連同其屬性編輯器相同的組件註冊到IDE中後,當在開發界面中對JavaBean進行定製時,IDE就會根據JavaBean規範找到對應的BeanInfo,再根據BeanInfo中的描述信息找到JavaBean屬性描述(是否開放、使用哪一個屬性編輯器),進而爲JavaBean生成特定開發編輯界面。
JavaBean規範提供了一個管理默認屬性編輯器的管理器:PropertyEditorManager,該管理器內保存着一些常見類型的屬性編輯器,若是某個JavaBean的常見類型屬性沒有經過BeanInfo顯式指定屬性編輯器,IDE將自動使用PropertyEditorManager中註冊的對應默認屬性編輯器。
因爲JavaBean對應的屬性編輯器等IDE環境相關的資源和組件須要動態加載,因此在純Java的IDE中開發基於組件的應用時,總會感受IDE反應很遲鈍,不像Delphi、C++Builder同樣靈敏快捷。但在Eclipse開發環境中,設計包括可視化組件的應用時卻很快捷,緣由是Eclipse沒有使用Java的標準用戶界面組件庫,固然也就沒有按照JavaBean的規範開發設計GUI組件了。
PropertyEditor
PropertyEditor是屬性編輯器的接口,它規定了將外部設置值轉換爲內部JavaBean屬性值的轉換接口方法。PropertyEditor主要的接口方法說明以下:
- Object getValue():返回屬性的當前值。基本類型被封裝成對應的封裝類實例;
- void setValue(Object newValue):設置屬性的值,基本類型以封裝類傳入;
- String getAsText():將屬性對象用一個字符串表示,以便外部的屬性編輯器能以可視化的方式顯示。缺省返回null,表示該屬性不能以字符串表示;
- void setAsText(String text):用一個字符串去更新屬性的內部值,這個字符串通常從外部屬性編輯器傳入;
- String[] getTags():返回表示有效屬性值的字符串數組(如boolean屬性對應的有效Tag爲true和false),以便屬性編輯器能如下拉框的方式顯示出來。缺省返回null,表示屬性沒有匹配的字符值有限集合;
- String getJavaInitializationString():爲屬性提供一個表示初始值的字符串,屬性編輯器以此值做爲屬性的默認值。
能夠看出PropertyEditor接口方法是內部屬性值和外部設置值的溝通橋樑。此外,咱們能夠很容易地發現該接口的不少方法是專爲IDE中的可視化屬性編輯器提供的:如getTags()、getJavaInitializationString()以及另一些咱們未此介紹的接口方法。
Java爲PropertyEditor提供了一個方便類:PropertyEditorSupport,該類實現了PropertyEditor接口並提供默認實現,通常狀況下,用戶能夠經過擴展這個方便類設計本身的屬性編輯器。
BeanInfo
BeanInfo主要描述了JavaBean哪些屬性能夠編輯以及對應的屬性編輯器,每個屬性對應一個屬性描述器PropertyDescriptor。PropertyDescriptor的構造函數有兩個入參:
PropertyDescriptor(String propertyName, Class beanClass) ,其中propertyName爲屬性名;而beanClass爲JavaBean對應的Class。
此外PropertyDescriptor還有一個setPropertyEditorClass(Class propertyEditorClass)方法,爲JavaBean屬性指定編輯器。BeanInfo接口最重要的方法就是:PropertyDescriptor[] getPropertyDescriptors() ,該方法返回JavaBean的屬性描述器數組。
BeanInfo接口有一個經常使用的實現類:SimpleBeanInfo,通常狀況下,能夠經過擴展SimpleBeanInfo實現本身的功能。
一個實例
在本節中,咱們來看一個具體屬性編輯器的實例,該實例根據《Core Java Ⅱ》上的一個例子改編而成。
ChartBean是一個可定製圖表組件,容許經過屬性的設置定製圖表的樣式以獲得知足各類不一樣使用場合要求的圖表。咱們忽略ChartBean的其餘屬性,僅關注其中的兩個屬性:
代碼清單5-2 CharBean
- public class ChartBean extends JPanel{
- private int titlePosition = CENTER;
- private boolean inverse;
- //省略get/setter方法
- }
下面,咱們爲titlePosition屬性提供一個屬性編輯器。咱們不去直接實現PropertyEditor,而是經過擴展PropertyEditorSupport這個方便類來定義咱們的屬性編輯器:
代碼清單5-3 TitlePositionEditor
Java代碼
- import java.beans.*;
- public class TitlePositionEditor extends PropertyEditorSupport{
- private String[] options = { "Left", "Center", "Right" };
-
- //①表明可選屬性值的字符串標識數組
- public String[] getTags() { return options; }
-
- //②表明屬性初始值的字符串
- public String getJavaInitializationString() { return "" + getValue(); }
-
- //③將內部屬性值轉換爲對應的字符串表示形式,供屬性編輯器顯示之用
- public String getAsText(){
- int value = (Integer) getValue();
- return options[value];
- }
-
- //④將外部設置的字符串轉換爲內部屬性的值
- public void setAsText(String s){
- for (int i = 0; i < options.length; i++){
- if (options[i].equals(s)){
- setValue(i);
- return;
- }
- }
- }
- }
①處經過getTags()方法返回一個字符串數組,所以在IDE中該屬性對應的編輯器將自動提供一個下拉框,下拉框中包含3個可選項:「Left」、「Center」、「Right」。而③和④處的兩個方法分別完成屬性值到字符串的雙向轉換功能。CharBean的inverse屬性也有一個類似的編輯器InverseEditor,咱們忽略不講。
下面編寫ChartBean對應的BeanInfo,根據JavaBean的命名規範,這個BeanInfo應該命名爲ChartBeanBeanInfo,它負責將屬性編輯器和ChartBean的屬性掛鉤起來:
代碼清單5-4 ChartBeanBeanInfo
Java代碼
- import java.beans.*;
- public class ChartBeanBeanInfo extends SimpleBeanInfo{
- public PropertyDescriptor[] getPropertyDescriptors() {
- try{
-
- //①將TitlePositionEditor綁定到ChartBean的titlePosition屬性中
- PropertyDescriptor titlePositionDescriptor
- = new PropertyDescriptor("titlePosition", ChartBean.class);
- titlePositionDescriptor.setPropertyEditorClass(TitlePositionEditor.class);
-
-
- //②將InverseEditor綁定到ChartBean的inverse屬性中
- PropertyDescriptor inverseDescriptor
- = new PropertyDescriptor("inverse", ChartBean.class);
- inverseDescriptor.setPropertyEditorClass(InverseEditor.class);
- return new PropertyDescriptor[]{titlePositionDescriptor, inverseDescriptor};
- }
- catch (IntrospectionException e){
- e.printStackTrace();
- return null;
- }
- }
- }
在ChartBeanBeanInfo中,咱們分別爲ChartBean和titlePosition和inverse屬性指定對應的屬性編輯器。將ChartBean連同屬性編輯器以及ChartBeanBeanInfo打成JAR包,使用IDE組件擴展管理功能註冊到IDE中。這樣,咱們就能夠像使用TextField、Checkbox等這些組對ChartBean進行可視化的開發設計工做了。下面是ChartBean在NetBeans IDE中的屬性編輯器效果圖,如圖5-5所示。
ChartBean可設置的屬性都列在屬性查看器中,當單擊titlePosition屬性時,下拉框中列出了咱們提供的3個選項。
Spring默認屬性編輯器
Spring的屬性編輯器和傳統的用於IDE開發時的屬性編輯器不一樣,它們沒有UI界面,僅負責將配置文件中的文本配置值轉換爲Bean屬性的對應值,因此Spring的屬性編輯器並不是傳統意義上的JavaBean屬性編輯器。
Spring爲常見的屬性類型提供了默認的屬性編輯器。從圖5-4中,咱們能夠看出BeanWrapperImpl類擴展了PropertyEditorRegistrySupport類,Spring在PropertyEditor RegistrySupport中爲常見屬性類型提供了默認的屬性編輯器,這些「常見的類型」共32個,可分爲3大類,總結以下:
表5-1 Spring提供的默認屬性編輯器
類 別 |
說 明 |
基礎數據類型 |
分爲幾個小類: 1)基本數據類型,如:boolean、byte、short、int等; 2)基本數據類型封裝類,如:Long、Character、Integer等; 3)兩個基本數據類型的數組,char[]和byte[]; 4)大數類,BigDecimal和BigInteger |
集合類 |
爲5種類型的集合類Collection、Set、SortedSet、List和SortedMap提供了編輯器 |
資源類 |
用於訪問外部資源的8個常見類Class、Class[]、File、InputStream、Locale、Properties、Resource[]和URL |
|
PropertyEditorRegistrySupport中有兩個用於保存屬性編輯器的Map類型變量:
- defaultEditors:用於保存默認屬性類型的編輯器,元素的鍵爲屬性類型,值爲對應的屬性編輯器實例;
- customEditors:用於保存用戶自定義的屬性編輯器,元素的鍵值和defaultEditors相同。
PropertyEditorRegistrySupport經過相似如下的代碼定義默認屬性編輯器:
Java代碼
- this.defaultEditors.put(char.class, new CharacterEditor(false));
- this.defaultEditors.put(Character.class, new CharacterEditor(true));
- this.defaultEditors.put(Locale.class, new LocaleEditor());
- this.defaultEditors.put(Properties.class, new PropertiesEditor());
這些默認的屬性編輯器解決常見屬性類型的註冊問題,若是用戶的應用包括一些特殊類型的屬性,且但願在配置文件中以字面值提供配置值,那麼就須要編寫自定義屬性編輯器並註冊到Spring容器中。這樣,Spring才能將配置文件中的屬性配置值轉換爲對應的屬性類型值。
自定義屬性編輯器
Spring大部分默認屬性編輯器都直接擴展於java.beans.PropertyEditorSupport類,用戶也能夠經過擴展PropertyEditorSupport實現本身的屬性編輯器。比起用於IDE環境的屬性編輯器來講,Spring環境下使用的屬性編輯器的功能很是單一:僅須要將配置文件中字面值轉換爲屬性類型的對象便可,並不須要提供UI界面,所以僅須要簡單覆蓋PropertyEditorSupport的setAsText()方法就能夠了。
一個實例
咱們繼續使用第4章中Boss和Car的例子,假設咱們如今但願在配置Boss時,不經過引用Bean的方式注入Boss的car屬性,而但願直接經過字符串字面值提供配置。爲了方便閱讀,這裏再次列出Boss和Car類的簡要代碼:
代碼清單5-5 Car
Java代碼
- package com.baobaotao.editor;
- public class Car {
- private int maxSpeed;
- public String brand;
- private double price;
- //省略get/setter
- }
代碼清單5-6 Boss
Java代碼
- package com.baobaotao.editor;
- public class Boss {
- private String name;
- private Car car = new Car();
- //省略get/setter
- }
Boss有兩個屬性:name和car,分別對應String類型和Car類型。Spring擁有String類型的默認屬性編輯器,所以對於String類型的屬性咱們不用操心。但Car類型是咱們自定義的類型,要配置Boss的car屬性,有兩種方案:
- 1)在配置文件中爲car專門配置一個<bean>,而後在boss的<bean>中經過ref引用car Bean,這正是咱們上一章中所用的方法;
- 2)爲Car類型提供一個自定義的屬性編輯器,這樣,咱們就經過字面值爲Boss的car屬性提供配置值。
第一種方案是經常使用的方法,可是在有些狀況下,這種方式須要將屬性對象一步步肢解爲最終能夠用基本類型表示的Bean,使配置文件變得不夠清晰,直接爲屬性類提供一個對應的自定義屬性編輯器可能會是更好的替代方案。
如今,咱們來爲Car編寫一個自定義的屬性編輯器,其代碼以下所示:
代碼清單5-7 CustomCarEditor
Java代碼
- package com.baobaotao.editor;
- import java.beans.PropertyEditorSupport;
-
- public class CustomCarEditor extends PropertyEditorSupport {
-
- //①將字面值轉換爲屬性類型對象
- public void setAsText(String text){
- if(text == null || text.indexOf(",") == -1){
- throw new IllegalArgumentException("設置的字符串格式不正確");
- }
- String[] infos = text.split(",");
- Car car = new Car();
- car.setBrand(infos[0]);
- car.setMaxSpeed(Integer.parseInt(infos[1]));
- car.setPrice(Double.parseDouble(infos[2]));
-
- //②調用父類的setValue()方法設置轉換後的屬性對象
- setValue(car);
- }
- }
CustomCarEditor很簡單,它僅覆蓋PropertyEditorSupport便利類的setAsText(String text)方法,該方法負責將配置文件以字符串提供的字面值轉換爲Car對象。字面值採用逗號分隔的格式同時爲brand、maxSpeed和price屬性值提供設置值,setAsText()方法解析這個字面值並生成對應的Car對象。因爲咱們並不須要將Boss內部的car屬性反顯到屬性編輯器中,所以不須要覆蓋getAsText()方法。
註冊自定義的屬性編輯器
在IDE環境下,自定義屬性編輯器在使用以前必須經過擴展組件功能進行註冊,在Spring環境中也須要經過必定的方法註冊自定義的屬性編輯器。
若是使用BeanFactory,用戶須要手工調用registerCustomEditor(Class requiredType, PropertyEditor propertyEditor)方法註冊自定義屬性編輯器;若是使用ApplicationContext,則只須要在配置文件經過CustomEditorConfigurer註冊就能夠了。CustomEditorConfigurer實現BeanFactoryPostProcessor接口,所以是一個Bean工廠後處理器。咱們知道Bean工廠後處理器在Spring容器加載配置文件並生成BeanDefinition半成品後就會被自動執行。所以CustomEditorConfigurer有容器啓動時有機會注入自定義的屬性編輯器。下面的配置片段定義了一個CustomEditorConfigurer:
Xml代碼
- <!--①配置自動註冊屬性編輯器的CustomEditorConfigurer -->
- <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
- <property name="customEditors">
- <map>
- <!--②-1屬性編輯器對應的屬性類型-->
- <entry key="com.baobaotao.editor.Car">
-
- <!--②-2對應的屬性編輯器Bean -->
- <bean class="com.baobaotao.editor.CustomCarEditor" />
- </entry>
- </map>
- </property>
- </bean>
- <bean id="boss" class="com.baobaotao.editor.Boss">
- <property name="name" value="John"/>
- <!--③該屬性將使用②處的屬性編輯器完成屬性填充操做-->
- <property name="car" value="紅旗CA72,200,20000.00"/>
- </bean>
在①處,咱們定義了用於註冊自定義屬性編輯器的CustomEditorConfigurer,Spring容器將經過反射機制自動調用這個Bean。CustomEditorConfigurer經過一個Map屬性定義須要自動註冊的自定義屬性編輯器。在②處,咱們爲Car類型指定了對應屬性編輯器CustomCarEditor,注意鍵是屬性類型,而值是對應的屬性編輯器Bean,而不是屬性編輯器的類名。
最精彩的部分固然是③處的配置,咱們原來經過一個<bean>元素標籤配置好car Bean,而後在boss的<bean>中經過ref引用car Bean,可是如今咱們直接經過value爲car屬性提供配置。BeanWrapper在設置boss的car屬性時,它將檢索自定義屬性編輯器的註冊表,當發現Car屬性類型擁有對應的屬性編輯器CustomCarEditor時,它就會利用CustomCarEditor將「紅旗CA72,200,20000.00」轉換爲Car對象。
引用
按照JavaBeans的規範,JavaBeans的基礎設施會在JavaBean相同類包下查找是否存在<JavaBean>Editor的類,若是存在,自動使用<JavaBean>Editor做爲該JavaBean的PropertyEditor。
如com.baobaotao.domain.UserEditor會自動成爲com.baobaotao.domain.User對應的PropertyEditor。Spring也支持這個規範,也即若是採用這種規約命令PropertyEditor,就無須顯式在CustomEditorConfigurer中註冊了,Spring將自動查找並註冊這個PropertyEditor。
另:Spring 3.0除支持PropertyEditor外,還在覈心包中引入了自建的ConversionService,它提供了更爲強大的類型轉換的能力,能夠完成任意類型之間的轉換,還能夠在轉換過程當中參考目標對象所在宿主類的上下文信息。Spring的類型轉換同時支持PropertyEdito和ConversionService。