(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,它就会去寻找这个属性编辑器进行后续操作。

自定义属性编辑器步骤总结:

  1. 继承PropertyEditorSupport类,覆盖setAsText方法;
  2. 注册自定义的属性编辑器。

CustomEditorConfigurer是BeanFactoryPostProcessor的实现类,因此它也是一个工厂后处理器。所谓的工厂后处理器,就是在实例化bean的过程中对bean进行处理,工厂模式本身就是把用户要使用的bean在内部实例化好了,当外部调用的时候直接吐出一个现成的对象来,所以,属性编辑属于工厂后处理器的任务。

时间: 2024-10-18 00:34:06

(spring-第13回【IoC基础篇】)PropertyEditor(属性编辑器)--实例化Bean的第五大利器的相关文章

(spring-第9回【IoC基础篇】)BeanFactoryPostProcessor,实例化Bean之前的第二大利器

继承结构图如上.在加载XML,注册bean definition之后,在实例化bean definition之前,必要的时候要用到BeanFactoryPostProcessor.它负责把XML中有些占位符式的属性还原成真实值.意思是说,有时候,XML中<bean>的属性值不固定,会随着外界因素变化,这时候,在<bean>中配置占位符,而另外定义一个属性文件来控制<bean>的属性.比如下面是一个数据库连接的XML配置: 1 <bean id="data

(spring-第12回【IoC基础篇】)JavaBean的属性编辑器

在spring实例化bean的最后阶段,spring利用属性编辑器将配置文件中的文本配置值转换为bean属性的对应值,例如: 代码0011 <bean id="car" class="com.mesopotamia.test1.Car" 2 p:name="汽车" 3 p:brand="宝马" 4 p:maxSpeed="200"/> 上面是文本配置, 再看bean: 代码0021 public

(spring-第14回【IoC基础篇】)国际化信息 (转)

国际化又称为本地化. 当你把手机的language由中文切换到英文时,你的微信也相应改用英语,这就是i18n国际化.一般来说,应用软件提供一套不同语言的资源文件,放到特定目录中,应用根据不同语言的操作系统决定使用哪一种语言. 一般由两个条件限定一个国际化类型:语言类型和国家/地区类型.比如: 中文:语言类型:zh,国家/地区类型:CN(中国大陆)/HK(中国香港)/TW(中国台湾). 英语:语言类型:en,国家类型:EN. ------------------------------------

(spring-第14回【IoC基础篇】)国际化信息

国际化又称为本地化. 当你把手机的language由中文切换到英文时,你的微信也相应改用英语,这就是i18n国际化.一般来说,应用软件提供一套不同语言的资源文件,放到特定目录中,应用根据不同语言的操作系统决定使用哪一种语言. 一般由两个条件限定一个国际化类型:语言类型和国家/地区类型.比如: 中文:语言类型:zh,国家/地区类型:CN(中国大陆)/HK(中国香港)/TW(中国台湾). 英语:语言类型:en,国家类型:EN. ------------------------------------

(spring-第10回【IoC基础篇】)InstantiationStrategy--实例化Bean的第三大利器

Bean的实例化整个过程如下图: : 其中,BeanDefinition加入到注册表中,并由BeanFactoryPostProcessor的实现类处理后,需要由InstantiationStrategy负责实例化.实例化仅仅是调用构造函数,相当于new了一个对象而已,bean的具体的属性在此时并未赋值(当然,一开始在XML中配置了Bean属性的值,或者在构造函数中有赋值语句的话,相关属性才会在实例化的时候便有了值.).InstantiationStrategy负责由Bean类的默认构造函数.带

(spring-第7回【IoC基础篇】)BeanDefinition的载入与解析&amp;&amp;spring.schemas、spring.handlers的使用

报错信息:Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/p], 一桩事故引发的连锁思考...开幕—— ----------------------------------------------------------------------------------------------

(spring-第15回【IoC基础篇】)容器事件

五个人在报社订阅了报纸.报社一旦有了新报纸,就派员工分别送到这五个人手里.在这个例子中,“报纸”就是事件,“报社”就是广播器,五个订阅者就是监听器.广播器收到事件,把事件传给监听器,监听器对事件做一些事情.这样的例子属于观察者模式. 观察者模式:A类负责接收新信息,B.C.D类一直关注着A.A类一旦有了新信息,就发送给B.C.D类,B.C.D类收到信息作出不同的操作.这就是观察者模式.具体到代码中,A中设置一个变量message(这就是信息),当message改变时,调用B.C.D的perfor

(spring-第8回【IoC基础篇】)BeanDefinition在IoC容器中的注册

在spring中,所有的bean都是由BeanFactory进行管理的.下面是BeanFactory的类体系结构: 我们清楚的看到,DefaultListableBeanFactory继承了BeanFactory的优良传统,同时又实现了BeanDefinitionRegistry这个注册器,那么无疑,BeanDefinition在容器中的注册任务,非他莫属.事实上,DefaultListableBeanFactory拥有一个私有的BeanDefinitonMap属性,这个属性是个哈希Map,通过

Spring+SpringMVC+MyBatis+easyUI整合基础篇(二)牛刀小试

承接上文,该篇即为项目整合的介绍了. 废话不多说,先把源码和项目地址放上来,重点要写在前面. github地址为ssm-demo 你也可以先体验一下实际效果,点击这里就行啦 账号:admin 密码:123456 从构思这个博客,一直到最终确定以这个项目为切入点,中间也是各种问题出现,毕竟是新人,所以也是十分的小心,修改代码以及搬上github其实花了不少时间,但也特别的认真,不知道是怎么回事,感觉这几天的过程比逼死产品经理还要精彩和享受.或许是博客路上的第一站吧,有压力也有新奇,希望自己能坚持下