上一篇讲到javaBeans的属性编辑器,编写自己的属性编辑器,需要继承PRopertyEditorSupport,编写自己的BeanInfo,需要继承SimpleBeanInfo,然后在BeanInfo中把特定的属性编辑器和需要编辑的属性绑定起来(详情请查看上一篇)。
Spring的属性编辑器仅负责将配置文件中的字面值转换成Bean属性的对应值。(而JavaBean的属性编辑器能够通过界面来手动设置bean属性的值)。如果属性的类型不同,转换的方法就不同。正如javabean的属性编辑器一样,特定类型的属性对应着特定的属性编辑器。Spring在PropertyEditorSupport中提供了默认的属性编辑器。PropertyEditorSupport中有两个重要的变量:defaultEditors、customEditors,它们分别存放默认的属性编辑器和用户自定义的属性编辑器。 下面是PropertyEditorSupport的部分源码:
1privatevoidcreateDefaultEditors() {2this.defaultEditors =newHashMap<Class, PropertyEditor>(64);34//Simple editors, without parameterization capabilities.5//The JDK does not contain a default editor for any of these target types.6this.defaultEditors.put(Charset.class,newCharsetEditor());7this.defaultEditors.put(Class.class,newClassEditor());8this.defaultEditors.put(Class[].class,newClassArrayEditor());9this.defaultEditors.put(Currency.class,newCurrencyEditor());10this.defaultEditors.put(File.class,newFileEditor());11this.defaultEditors.put(InputStream.class,newInputStreamEditor());12this.defaultEditors.put(InputSource.class,newInputSourceEditor());13this.defaultEditors.put(Locale.class,newLocaleEditor());14this.defaultEditors.put(Pattern.class,newPatternEditor());15this.defaultEditors.put(Properties.class,newPropertiesEditor());16this.defaultEditors.put(Resource[].class,newResourceArrayPropertyEditor());17this.defaultEditors.put(TimeZone.class,newTimeZoneEditor());18this.defaultEditors.put(URI.class,newURIEditor());19this.defaultEditors.put(URL.class,newURLEditor());20this.defaultEditors.put(UUID.class,newUUIDEditor());2122//Default instances of collection editors.23//Can be overridden by registering custom instances of those as custom editors.24this.defaultEditors.put(Collection.class,newCustomCollectionEditor(Collection.class));25this.defaultEditors.put(Set.class,newCustomCollectionEditor(Set.class));26this.defaultEditors.put(SortedSet.class,newCustomCollectionEditor(SortedSet.class));27this.defaultEditors.put(List.class,newCustomCollectionEditor(List.class));28this.defaultEditors.put(SortedMap.class,newCustomMapEditor(SortedMap.class));2930//Default editors for primitive arrays.31this.defaultEditors.put(byte[].class,newByteArrayPropertyEditor());32this.defaultEditors.put(char[].class,newCharArrayPropertyEditor());3334//The JDK does not contain a default editor for char!35this.defaultEditors.put(char.class,newCharacterEditor(false));36this.defaultEditors.put(Character.class,newCharacterEditor(true));3738//Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.39this.defaultEditors.put(boolean.class,newCustomBooleanEditor(false));40this.defaultEditors.put(Boolean.class,newCustomBooleanEditor(true));4142//The JDK does not contain default editors for number wrapper types!43//Override JDK primitive number editors with our own CustomNumberEditor.44this.defaultEditors.put(byte.class,newCustomNumberEditor(Byte.class,false));45this.defaultEditors.put(Byte.class,newCustomNumberEditor(Byte.class,true));46this.defaultEditors.put(short.class,newCustomNumberEditor(Short.class,false));47this.defaultEditors.put(Short.class,newCustomNumberEditor(Short.class,true));48this.defaultEditors.put(int.class,newCustomNumberEditor(Integer.class,false));49this.defaultEditors.put(Integer.class,newCustomNumberEditor(Integer.class,true));50this.defaultEditors.put(long.class,newCustomNumberEditor(Long.class,false));51this.defaultEditors.put(Long.class,newCustomNumberEditor(Long.class,true));52this.defaultEditors.put(float.class,newCustomNumberEditor(Float.class,false));53this.defaultEditors.put(Float.class,newCustomNumberEditor(Float.class,true));54this.defaultEditors.put(double.class,newCustomNumberEditor(Double.class,false));55this.defaultEditors.put(Double.class,newCustomNumberEditor(Double.class,true));56this.defaultEditors.put(BigDecimal.class,newCustomNumberEditor(BigDecimal.class,true));57this.defaultEditors.put(BigInteger.class,newCustomNumberEditor(BigInteger.class,true));5859//Only register config value editors if explicitly requested.60if(this.configValueEditorsActive) {61StringArrayPropertyEditor sae =newStringArrayPropertyEditor();62this.defaultEditors.put(String[].class, sae);63this.defaultEditors.put(short[].class, sae);64this.defaultEditors.put(int[].class, sae);65this.defaultEditors.put(long[].class, sae);66}67}
可以看到,defaultEditors、customEditors是哈希Map类型的,以属性的类为键,以对应属性编辑器的对象为值。
以48行为例,我们看一下CustomNumberEditor是个什么鬼,下面是源码:
1publicclassCustomNumberEditorextendsPropertyEditorSupport{23privatefinalClass numberClass;45privatefinalNumberFormat numberFormat;67privatefinalbooleanallowEmpty;8910publicCustomNumberEditor(Class numberClass,booleanallowEmpty)throwsIllegalArgumentException {11this(numberClass,null, allowEmpty);12}131415publicCustomNumberEditor(Class numberClass, NumberFormat numberFormat,booleanallowEmpty)16throwsIllegalArgumentException {1718if(numberClass ==null|| !Number.class.isAssignableFrom(numberClass)) {19thrownewIllegalArgumentException("Property class must be a subclass of Number");20}21this.numberClass =numberClass;22this.numberFormat =numberFormat;23this.allowEmpty =allowEmpty;24}252627/**28* Parse the Number from the given text, using the specified NumberFormat.29*/30@Override31@SuppressWarnings("unchecked")32publicvoidsetAsText(String text)throwsIllegalArgumentException {33if(this.allowEmpty && !StringUtils.hasText(text)) {34//Treat empty String as null value.35setValue(null);36}37elseif(this.numberFormat !=null) {38//Use given NumberFormat for parsing text.39setValue(NumberUtils.parseNumber(text,this.numberClass,this.numberFormat));40}41else{42//Use default valueOf methods for parsing text.43setValue(NumberUtils.parseNumber(text,this.numberClass));44}45}4647/**48* Coerce a Number value into the required target class, if necessary.49*/50@Override51@SuppressWarnings("unchecked")52publicvoidsetValue(Object value) {53if(valueinstanceofNumber) {54super.setValue(NumberUtils.convertNumberToTargetClass((Number) value,this.numberClass));55}56else{57super.setValue(value);58}59}6061/**62* Format the Number as String, using the specified NumberFormat.63*/64@Override65publicString getAsText() {66Object value =getValue();67if(value ==null) {68return"";69}70if(this.numberFormat !=null) {71//Use NumberFormat for rendering value.72returnthis.numberFormat.format(value);73}74else{75//Use toString method for rendering value.76returnvalue.toString();77}78}7980}
真相大白,与上一节javabean的属性编辑器类似,CustomNumberEditor 是spring内置的属性编辑器,它也是继承了PropertyEditorSupport ,并且覆盖了setAsText、setValue、getAsText方法。所以,这是扩展javabean的属性编辑器的通用方法。那么我们编写自己的属性编辑器也应该这样做。(javabean的属性编辑器相关内容请查看上一节)。而且结合上一节我们知道在这里,getAsText表示把<bean>标签里的属性值拿到,而setAsText表示把拿到的标签字面值转换成bean属性的有变量类型的值。比如,下面是Car的xml属性配置:
1<beanid="car"class="com.mesopotamia.test1.Car"2p:name="汽车"3p:brand="宝马"4p:maxSpeed="200"/>
下面是Car的Bean类:
1publicclassCar {2privateString name;3privateString brand;4privatedoublemaxSpeed;
在XML属性配置中是没有类型之分的,经过属性编辑器的转换,就可以给Car的对应属性赋予对应的值。
spring提供的默认属性编辑器支持的类型是有限的,如果要自定义属性编辑器,就要扩展PropertyEditorSupport ,并且把自己的属性编辑器注册到spring容器中。下面我们一起来设计一个自定义的属性编辑器并把它注册到spring容器中使用。
现在有一个Car类:
1publicclassCar {2privateString name;3privateString brand;4privatedoublemaxSpeed;5。。。6省略getter、setter方法78publicString toString(){9return"名字:"+name+" 型号:"+brand+" 速度:"+maxSpeed;10}
有一个Store类,这个Store类拥有一个Car类型的属性:
1publicclassStore {23privateCar car;45。。。省略getter、setter方法。67publicString toString(){8returncar.toString();9}
下面是配置文件:
1<bean id="car"class="com.mesopotamia.test1.Car"2p:name="汽车"3p:brand="宝马"4p:maxSpeed="200"/>56<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:
1publicstaticvoidmain(String args[]){2applicationContext ctx =newClassPathXmlApplicationContext("com/mesopotamia/test1/*.xml");3//Car car1 = ctx.getBean("car1",Car.class);4Store store=ctx.getBean("store",Store.class);5log.info(store.toString());6}
运行结果:
12015-11-29 23:21:24,446 INFO [main] (Car.java:22) -调用了Car的构造函数,实例化了Car..22015-11-29 23:21:24,493 INFO [main] (Store.java:13) -调用了Store的构造函数,实例化了Store。。。32015-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"/>23<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()方法。代码如下:
1publicclassCustomCarEditorextendsPropertyEditorSupport {2publicvoidsetAsText(String text){3if(text ==null|| text.indexOf(",") == -1){4thrownewIllegalArgumentException("设置的字符串格式不正确");5}6String[] infos = text.split(",");7Car car =newCar();8car.setName(infos[0]);9car.setBrand(infos[1]);10car.setMaxSpeed(Double.parseDouble(infos[2]));11setValue(car);12}
取出文本值,分割逗号,新建Car对象,设置属性值,最后调用父类的setValue方法给Store的Car属性赋值。
那么setValue如何知道把括号里的对象参数赋予给谁呢?我们注册该属性编辑器时就会知道。
自定义的属性编辑器写好了,接下来要注册到spring容器中,注册方法如下:
1<beanid="store"class="com.mesopotamia.test1.Store">2<propertyname="car"value="汽车,宝马,200.00"/>3</bean>45<beanclass="org.springframework.beans.factory.config.CustomEditorConfigurer">6<propertyname="customEditors">7<map>8<entrykey="com.mesopotamia.test1.Car">9<beanclass="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在内部实例化好了,当外部调用的时候直接吐出一个现成的对象来,所以,属性编辑属于工厂后处理器的任务。