在Spring配置文件里,我们往往通过字面值为Bean各种类型的属性提供设置值:不管是double类型还是int类型,在配置文件中都对应字符串类型的字面值。BeanWrapper填充Bean属性时如何将这个字面值转换为对应的double或int等内部类型呢?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器。
“属性编辑器”这个名字可能会让人误以为是一个带用户界面的输入器,其实属性编辑器不一定非得有用户界面, 任何实现java.beans.PropertyEditor接口的类都是属性编辑器。属性编辑器的主要功能就是将外部的设置值转换为JVM内部的对应类型,所以属性编辑器其实就是一个类型转换器。
Sun所制定的JavaBean规范,很大程度上是为IDE准备的——它让IDE能够以可视化的方式设置JavaBean的属性。如果在IDE中开发一个可视化应用程序,我们需要通过属性设置的方式对组成应用的各种组件进行定制,IDE通过属性编辑器让开发人员使用可视化的方式设置组件的属性。
一般的IDE都支持JavaBean规范所定义的属性编辑器,当组件开发商发布一个组件时,它往往将组件对应的属性编辑器捆绑发行,这样开发者就可以在IDE环境下方便地利用属性编辑器对组件进行定制工作。由于JavaBean对应的属性编辑器等IDE环境相关的资源和组件需要动态加载,所以在纯Java的IDE中开发基于组件的应用时,总会感觉IDE反应很迟钝,不像Delphi、C++ Builder一样灵敏快捷。但在Eclipse开发环境中,设计包括可视化组件的应用时却很快捷,原因是Eclipse没有使用Java的标准用户界面组件库,当然也就没有按照JavaBean的规范开发设计GUI组件了。
Java为PropertyEditor提供了一个方便的实现类:PropertyEditorSupport,该类实现了PropertyEditor接口并提供默认实现,一般情况下,用户可以通过扩展这个方便类设计自己的属性编辑器。
BeanInfo主要描述了JavaBean哪些属性可以编辑以及对应的属性编辑器,每一个属性对应一个属性描述器PropertyDescriptor。PropertyDescriptor的构造函数有两个入参:PropertyDescriptor(String propertyName, Class beanClass) ,其中propertyName为属性名;而beanClass为JavaBean对应的Class。
BeanInfo接口有一个常用的实现类:SimpleBeanInfo,一般情况下,可以通过扩展SimpleBeanInfo实现自己的功能。
在本节中,我们来看一个具体属性编辑器的实例,该实例根据《Core Java Ⅱ》上的一个例子改编而成。
ChartBean是一个可定制图表组件,允许通过属性的设置定制图表的样式以得到满足各种不同使用场合要求的图表。我们忽略ChartBean的其他属性,仅关注其中的两个属性:import javax.swing.JPanel; import static javax.swing.border.TitledBorder.CENTER; public class ChartBean extends JPanel{ private int titlePosition = CENTER; private boolean inverse; //省略get/setter方法 }下面,我们为titlePosition属性提供一个属性编辑器。我们不去直接实现PropertyEditor,而是通过扩展PropertyEditorSupport这个方便类来定义我们的属性编辑器:
import java.beans.*; public class TitlePositionEditor extends PropertyEditorSupport { private final String[] options = {"Left", "Center", "Right"}; //1. 代表可选属性值的字符串标识数组 @Override public String[] getTags() { return options; } //2. 代表属性初始值的字符串 @Override public String getJavaInitializationString() { return "" + getValue(); } //3. 将内部属性值转换为对应的字符串表示形式,供属性编辑器显示之用 @Override public String getAsText() { int value = (Integer) getValue(); return options[value]; } //4. 将外部设置的字符串转换为内部属性的值 @Override public void setAsText(String s) { for (int i = 0; i < options.length; i++) { if (options[i].equals(s)) { setValue(i); return; } } } }1处通过getTags()方法返回一个字符串数组,因此在IDE中该属性对应的编辑器将自动提供一个下拉框,下拉框中包含3个可选项:“Left”、“Center”、“Right”。而3和4处的两个方法分别完成属性值到字符串的双向转换功能。CharBean的inverse属性也有一个相似的编辑器InverseEditor,我们忽略不讲。
import java.beans.*; public class ChartBeanBeanInfo extends SimpleBeanInfo { @Override public PropertyDescriptor[] getPropertyDescriptors() { try { //1. 将TitlePositionEditor绑定到ChartBean的titlePosition属性中 PropertyDescriptor titlePositionDescriptor = new PropertyDescriptor("titlePosition", ChartBean.class); titlePositionDescriptor.setPropertyEditorClass(TitlePositionEditor.class); //2. 将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的属性编辑器和传统的用于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 |
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类的简要代码:
package com.baobaotao.editor; public class Car { private int maxSpeed; public String brand; private double price; //省略get/setter }
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属性,有两种方案:
package com.baobaotao.editor; import java.beans.PropertyEditorSupport; public class CustomCarEditor extends PropertyEditorSupport { //1. 将字面值转换为属性类型对象 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])); //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:
<!--①配置自动注册属性编辑器的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,而不是属性编辑器的类名。
引用:
本文转自:http://stamen.iteye.com/blog/1525668