(spring-第13回【IoC基础篇】)PropertyEditor(属性编辑器)--实例化Bean的第五大利器
上一篇讲到JavaBeans的属性编辑器,编写自己的属性编辑器,需要继承PropertyEditorSupport,编写自己的BeanInfo,需要继承SimpleBeanInfo,然后在BeanInfo中把特定的属性编辑器和需要编辑的属性绑定起来(详情请查看上一篇)。
Spring的属性编辑器仅负责将配置文件中的字面值转换成Bean属性的对应值。(而JavaBean的属性编辑器能够通过界面来手动设置bean属性的值)。如果属性的类型不同,转换的方法就不同。正如javabean的属性编辑器一样,特定类型的属性对应着特定的属性编辑器。Spring在PropertyEditorSupport中提供了默认的属性编辑器。PropertyEditorSupport中有两个重要的变量:defaultEditors、customEditors,它们分别存放默认的属性编辑器和用户自定义的属性编辑器。 下面是PropertyEditorSupport的部分源码:
1 private void createDefaultEditors() { 2 this.defaultEditors = new HashMap<Class, PropertyEditor>(64); 3 4 // Simple editors, without parameterization capabilities. 5 // The JDK does not contain a default editor for any of these target types. 6 this.defaultEditors.put(Charset.class, new CharsetEditor()); 7 this.defaultEditors.put(Class.class, new ClassEditor()); 8 this.defaultEditors.put(Class[].class, new ClassArrayEditor()); 9 this.defaultEditors.put(Currency.class, new CurrencyEditor()); 10 this.defaultEditors.put(File.class, new FileEditor()); 11 this.defaultEditors.put(InputStream.class, new InputStreamEditor()); 12 this.defaultEditors.put(InputSource.class, new InputSourceEditor()); 13 this.defaultEditors.put(Locale.class, new LocaleEditor()); 14 this.defaultEditors.put(Pattern.class, new PatternEditor()); 15 this.defaultEditors.put(Properties.class, new PropertiesEditor()); 16 this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor()); 17 this.defaultEditors.put(TimeZone.class, new TimeZoneEditor()); 18 this.defaultEditors.put(URI.class, new URIEditor()); 19 this.defaultEditors.put(URL.class, new URLEditor()); 20 this.defaultEditors.put(UUID.class, new UUIDEditor()); 21 22 // Default instances of collection editors. 23 // Can be overridden by registering custom instances of those as custom editors. 24 this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class)); 25 this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class)); 26 this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class)); 27 this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class)); 28 this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class)); 29 30 // Default editors for primitive arrays. 31 this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor()); 32 this.defaultEditors.put(char[].class, new CharArrayPropertyEditor()); 33 34 // The JDK does not contain a default editor for char! 35 this.defaultEditors.put(char.class, new CharacterEditor(false)); 36 this.defaultEditors.put(Character.class, new CharacterEditor(true)); 37 38 // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor. 39 this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false)); 40 this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true)); 41 42 // The JDK does not contain default editors for number wrapper types! 43 // Override JDK primitive number editors with our own CustomNumberEditor. 44 this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false)); 45 this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true)); 46 this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false)); 47 this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true)); 48 this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false)); 49 this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true)); 50 this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false)); 51 this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true)); 52 this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false)); 53 this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true)); 54 this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false)); 55 this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true)); 56 this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true)); 57 this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true)); 58 59 // Only register config value editors if explicitly requested. 60 if (this.configValueEditorsActive) { 61 StringArrayPropertyEditor sae = new StringArrayPropertyEditor(); 62 this.defaultEditors.put(String[].class, sae); 63 this.defaultEditors.put(short[].class, sae); 64 this.defaultEditors.put(int[].class, sae); 65 this.defaultEditors.put(long[].class, sae); 66 } 67 }
可以看到,defaultEditors、customEditors是哈希Map类型的,以属性的类为键,以对应属性编辑器的对象为值。
以48行为例,我们看一下CustomNumberEditor是个什么鬼,下面是源码:
1 public class CustomNumberEditor extends PropertyEditorSupport { 2 3 private final Class numberClass; 4 5 private final NumberFormat numberFormat; 6 7 private final boolean allowEmpty; 8 9 10 public CustomNumberEditor(Class numberClass, boolean allowEmpty) throws IllegalArgumentException { 11 this(numberClass, null, allowEmpty); 12 } 13 14 15 public CustomNumberEditor(Class numberClass, NumberFormat numberFormat, boolean allowEmpty) 16 throws IllegalArgumentException { 17 18 if (numberClass == null || !Number.class.isAssignableFrom(numberClass)) { 19 throw new IllegalArgumentException("Property class must be a subclass of Number"); 20 } 21 this.numberClass = numberClass; 22 this.numberFormat = numberFormat; 23 this.allowEmpty = allowEmpty; 24 } 25 26 27 /** 28 * Parse the Number from the given text, using the specified NumberFormat. 29 */ 30 @Override 31 @SuppressWarnings("unchecked") 32 public void setAsText(String text) throws IllegalArgumentException { 33 if (this.allowEmpty && !StringUtils.hasText(text)) { 34 // Treat empty String as null value. 35 setValue(null); 36 } 37 else if (this.numberFormat != null) { 38 // Use given NumberFormat for parsing text. 39 setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat)); 40 } 41 else { 42 // Use default valueOf methods for parsing text. 43 setValue(NumberUtils.parseNumber(text, this.numberClass)); 44 } 45 } 46 47 /** 48 * Coerce a Number value into the required target class, if necessary. 49 */ 50 @Override 51 @SuppressWarnings("unchecked") 52 public void setValue(Object value) { 53 if (value instanceof Number) { 54 super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass)); 55 } 56 else { 57 super.setValue(value); 58 } 59 } 60 61 /** 62 * Format the Number as String, using the specified NumberFormat. 63 */ 64 @Override 65 public String getAsText() { 66 Object value = getValue(); 67 if (value == null) { 68 return ""; 69 } 70 if (this.numberFormat != null) { 71 // Use NumberFormat for rendering value. 72 return this.numberFormat.format(value); 73 } 74 else { 75 // Use toString method for rendering value. 76 return value.toString(); 77 } 78 } 79 80 }
真相大白,与上一节javabean的属性编辑器类似,CustomNumberEditor 是spring内置的属性编辑器,它也是继承了PropertyEditorSupport ,并且覆盖了setAsText、setValue、getAsText方法。所以,这是扩展javabean的属性编辑器的通用方法。那么我们编写自己的属性编辑器也应该这样做。(javabean的属性编辑器相关内容请查看上一节)。而且结合上一节我们知道在这里,getAsText表示把<bean>标签里的属性值拿到,而setAsText表示把拿到的标签字面值转换成bean属性的有变量类型的值。比如,下面是Car的XML属性配置:
1 <bean id="car" class="com.mesopotamia.test1.Car" 2 p:name="汽车" 3 p:brand="宝马" 4 p:maxSpeed="200"/>
下面是Car的Bean类:
1 public class Car { 2 private String name; 3 private String brand; 4 private double maxSpeed;
在XML属性配置中是没有类型之分的,经过属性编辑器的转换,就可以给Car的对应属性赋予对应的值。
spring提供的默认属性编辑器支持的类型是有限的,如果要自定义属性编辑器,就要扩展PropertyEditorSupport ,并且把自己的属性编辑器注册到spring容器中。下面我们一起来设计一个自定义的属性编辑器并把它注册到spring容器中使用。
现在有一个Car类:
1 public class Car { 2 private String name; 3 private String brand; 4 private double maxSpeed; 5 。。。 6 省略getter、setter方法 7 8 public String toString(){ 9 return "名字:"+name+" 型号:"+brand+" 速度:"+maxSpeed; 10 }
有一个Store类,这个Store类拥有一个Car类型的属性:
1 public class Store { 2 3 private Car car; 4 5 。。。省略getter、setter方法。 6 7 public String toString(){ 8 return car.toString(); 9 }
下面是配置文件:
1 <bean id="car" class="com.mesopotamia.test1.Car" 2 p:name="汽车" 3 p:brand="宝马" 4 p:maxSpeed="200"/> 5 6 <bean id="store" class="com.mesopotamia.test1.Store"> 7 <property name="car"> 8 <ref bean="car"/> 9 </property> 10 </bean>
这是典型的bean引用方式。那么,当我加载spring容器,调用Store类的对象,该Store的car属性就自动拥有了name、brand、maxSpeed值了。下面是Main:
1 public static void main(String args[]){ 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/mesopotamia/test1/*.xml"); 3 //Car car1 = ctx.getBean("car1",Car.class); 4 Store store=ctx.getBean("store",Store.class); 5 log.info(store.toString()); 6 }
运行结果:
1 2015-11-29 23:21:24,446 INFO [main] (Car.java:22) - 调用了Car的构造函数,实例化了Car.. 2 2015-11-29 23:21:24,493 INFO [main] (Store.java:13) - 调用了Store的构造函数,实例化了Store。。。 3 2015-11-29 23:21:24,505 INFO [main] (Main.java:16) - 名字:汽车 型号:宝马 速度:200.0
第1、2行的打印语句我分别写在Car、Store的构造函数里,所以,一经实例化必须打印。而第3行,我打印的是store 的toString()方法,而该方法又调用的是Car的toString()方法,然后打印出了Car的属性值。
完美。然而,我们现在不这样干,换个玩儿法,我把配置文件改为如下的方式:
1 <bean id="car" class="com.mesopotamia.test1.Car"/> 2 3 <bean id="store" class="com.mesopotamia.test1.Store"> 4 <property name="car" value="汽车,宝马,200.00"/> 5 </bean>
规则变了,我要求在实例化Store后,把汽车,宝马,200.00分别赋值给Store的Car属性的对应变量。
而属性编辑器就是为了把这个字面值转换成具体属性的值的,因此,需要使用属性编辑器。
而本例中这种转换方式spring的默认属性编辑器并不支持,所以,我们要自定义属性编辑器。
自定义属性编辑器,首先,继承PropertyEditorSupport ,然后,覆盖setAsText()、getAsText()方法。
由于我们不需要跟javabean一样,用getAsText()获取属性值然后放到下拉框中,在当前属性编辑器中也不需要获取它,
所以,我们只需要覆盖setAsText()方法。代码如下:
1 public class CustomCarEditor extends PropertyEditorSupport { 2 public void setAsText(String text){ 3 if(text == null || text.indexOf(",") == -1){ 4 throw new IllegalArgumentException("设置的字符串格式不正确"); 5 } 6 String[] infos = text.split(","); 7 Car car = new Car(); 8 car.setName(infos[0]); 9 car.setBrand(infos[1]); 10 car.setMaxSpeed(Double.parseDouble(infos[2])); 11 setValue(car); 12 }
取出文本值,分割逗号,新建Car对象,设置属性值,最后调用父类的setValue方法给Store的Car属性赋值。
那么setValue如何知道把括号里的对象参数赋予给谁呢?我们注册该属性编辑器时就会知道。
自定义的属性编辑器写好了,接下来要注册到spring容器中,注册方法如下:
1 <bean id="store" class="com.mesopotamia.test1.Store"> 2 <property name="car" value="汽车,宝马,200.00"/> 3 </bean> 4 5 <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> 6 <property name="customEditors"> 7 <map> 8 <entry key="com.mesopotamia.test1.Car"> 9 <bean class="com.mesopotamia.test1.CustomCarEditor" /> 10 </entry> 11 </map> 12 </property> 13 </bean>
实际上是注册加载了CustomEditorConfigurer,这个类是专门负责注册自定义属性编辑器的。我们一开始讲到,PropertyEditorSupport里面有个变量叫customEditors,用来存放自定义属性编辑器,它是一个HashMap类型,其中Key是属性类,Value是属性对应的属性编辑器。而上面配置文件中的6-9行恰好是customEditors变量以及存放的map。CustomEditorConfigurer就负责把6-9行转换成哈希Map交给PropertyEditorSupport。
当BeanWrapper在设置store的car属性时(BeanWrapper负责在实例化后期设置属性值),它会检索自定义属性编辑器的注册表,然后发现Car属性类型对应着CustomCarEditor,它就会去寻找这个属性编辑器进行后续操作。
自定义属性编辑器步骤总结:
- 继承PropertyEditorSupport类,覆盖setAsText方法;
- 注册自定义的属性编辑器。
CustomEditorConfigurer是BeanFactoryPostProcessor的实现类,因此它也是一个工厂后处理器。所谓的工厂后处理器,就是在实例化bean的过程中对bean进行处理,工厂模式本身就是把用户要使用的bean在内部实例化好了,当外部调用的时候直接吐出一个现成的对象来,所以,属性编辑属于工厂后处理器的任务。