上一篇讲到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. public CustomNumberEditor(Class numberClass, boolean allowEmpty) throws IllegalArgumentException {
  10. this(numberClass, null, allowEmpty);
  11. }
  12.  
  13. public CustomNumberEditor(Class numberClass, NumberFormat numberFormat, boolean allowEmpty)
  14. throws IllegalArgumentException {
  15.  
  16. if (numberClass == null || !Number.class.isAssignableFrom(numberClass)) {
  17. throw new IllegalArgumentException("Property class must be a subclass of Number");
  18. }
  19. this.numberClass = numberClass;
  20. this.numberFormat = numberFormat;
  21. this.allowEmpty = allowEmpty;
  22. }
  23.  
  24. /**
  25. * Parse the Number from the given text, using the specified NumberFormat.
  26. */
  27. @Override
  28. @SuppressWarnings("unchecked")
  29. public void setAsText(String text) throws IllegalArgumentException {
  30. if (this.allowEmpty && !StringUtils.hasText(text)) {
  31. // Treat empty String as null value.
  32. setValue(null);
  33. }
  34. else if (this.numberFormat != null) {
  35. // Use given NumberFormat for parsing text.
  36. setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
  37. }
  38. else {
  39. // Use default valueOf methods for parsing text.
  40. setValue(NumberUtils.parseNumber(text, this.numberClass));
  41. }
  42. }
  43.  
  44. /**
  45. * Coerce a Number value into the required target class, if necessary.
  46. */
  47. @Override
  48. @SuppressWarnings("unchecked")
  49. public void setValue(Object value) {
  50. if (value instanceof Number) {
  51. super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass));
  52. }
  53. else {
  54. super.setValue(value);
  55. }
  56. }
  57.  
  58. /**
  59. * Format the Number as String, using the specified NumberFormat.
  60. */
  61. @Override
  62. public String getAsText() {
  63. Object value = getValue();
  64. if (value == null) {
  65. return "";
  66. }
  67. if (this.numberFormat != null) {
  68. // Use NumberFormat for rendering value.
  69. return this.numberFormat.format(value);
  70. }
  71. else {
  72. // Use toString method for rendering value.
  73. return value.toString();
  74. }
  75. }
  76.  
  77. }

真相大白,与上一节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. 省略gettersetter方法
  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. 。。。省略gettersetter方法。
  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=""/>
  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在内部实例化好了,当外部调用的时候直接吐出一个现成的对象来,所以,属性编辑属于工厂后处理器的任务。

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

  1. (spring-第6回【IoC基础篇】)BeanDefinition——实例化Bean之前的第一大利器。

    上节讲了Bean实例化的内部机制,这里再复述一遍: ResourceLoader从系统中加载XML配置信息,并由Resource来表示. BeanDefinitionReader从Resource中读 ...

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

    继承结构图如上.在加载XML,注册bean definition之后,在实例化bean definition之前,必要的时候要用到BeanFactoryPostProcessor.它负责把XML中有些 ...

  3. (spring-第5回【IoC基础篇】)spring容器从加载配置文件到实例化bean的内部工作机制

    前面讲过,spring的生命周期为:实例化前奏-->实例化-->实例化后期-->初始化前期-->初始化-->初始化后期-->bean的具体调用-->销毁前-- ...

  4. Spring+SpringMVC+MyBatis+easyUI整合基础篇(六)maven整合SSM

    写在前面的话   承接前文<Spring+SpringMVC+MyBatis+easyUI整合基础篇(五)讲一下maven>,本篇所讲述的是如何使用maven与原ssm项目整合,使得一个普 ...

  5. Spring+SpringMVC+MyBatis+easyUI整合基础篇(八)mysql中文查询bug修复

    写在前面的话 在测试搜索时出现的问题,mysql通过中文查询条件搜索不出数据,但是英文和数字可以搜索到记录,中文无返回记录.本文就是写一下发现问题的过程及解决方法.此bug在第一个项目中点这里还存在, ...

  6. Spring+SpringMVC+MyBatis+easyUI整合基础篇(十一)SVN服务器进阶

    日常啰嗦 上一篇文章<Spring+SpringMVC+MyBatis+easyUI整合基础篇(十)SVN搭建>简单的讲了一下SVN服务器的搭建,并没有详细的介绍配置文件及一些复杂的功能, ...

  7. Spring+SpringMVC+MyBatis+easyUI整合基础篇(十二)阶段总结

    不知不觉,已经到了基础篇的收尾阶段了,看着前面的十几篇文章,真的有点不敢相信,自己竟然真的坚持了下来,虽然过程中也有过懒散和焦虑,不过结果还是自己所希望的,克服了很多的问题,将自己的作品展现出来,也发 ...

  8. Spring+SpringMVC+MyBatis+easyUI整合基础篇

    基础篇 Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简介 Spring+SpringMVC+MyBatis+easyUI整合基础篇(二)牛刀小试 Spring+S ...

  9. (spring-第4回【IoC基础篇】)spring基于注解的配置

    基于XML的bean属性配置:bean的定义信息与bean的实现类是分离的. 基于注解的配置:bean的定义信息是通过在bean实现类上标注注解实现. 也就是说,加了注解,相当于在XML中配置了,一样 ...

随机推荐

  1. python 练习 9

    #!/usr/bin/python # -*- coding: UTF-8 -*- for i in range(1,5): for j in range(1,5): for k in range(1 ...

  2. PHP 启动 cURL模块以及启动失败的解决方案

    配置方法: php_curl.dll libeay32.dll ssleay32.dll php5ts.dll 复制到 %windir%/system32 以及php 目录的ext目录 下 并且找到p ...

  3. 发布完ArcGIS地图服务后,服务未启动成功

    今天下午更新地图服务后,服务未启动成功.出来的弹出框警告问题目前应该是ArcGIS Server出了问题,打开ArcCatalog目录,查看GIS服务器下localhost下的服务,只要是今天发布的服 ...

  4. 《javascript高级程序设计》第八章 The Browser Object Model

    8.1 window 对象 8.1.1 全局作用域 8.1.2 窗口关系及框架 8.1.3 窗口位置 8.1.4 窗口大小 8.1.5 导航和打开窗口 8.1.6 间歇调用和超时调用 8.1.7 系统 ...

  5. centos修改文件及文件夹权限

    查看文件权限的语句: 在终端输入:ls -l xxx.xxx (xxx.xxx是文件名) 那么就会出现相类似的信息,主要都是这些:-rw-rw-r-- 一共有10位数 其中: 最前面那个 - 代表的是 ...

  6. bzoj2458: [BeiJing2011]最小三角形(分治+几何)

    题目链接:bzoj2458: [BeiJing2011]最小三角形 学习推荐博客:分治法编程问题之最接近点对问题的算法分析 题解:先将所有点按x值排列,然后每次将当前区间[l,r]分成左右两半递归求解 ...

  7. Spring使用RowMapper将数据中的每一行封装成用户定义的类

    1.dao public interface MapperSelecteAllEmpDao { public List<Emp> all(); } 2.实现类 public class M ...

  8. C++实现对树的创建和前中后序遍历

    #include<iostream>#include<stdio.h> using namespace std; class BitNode{ public: char dat ...

  9. SSL证书请求文件(CSR)生成指南 - Tomcat

    SSL证书请求文件(CSR)生成指南 - Tomcat http://www.zhenssl.com/support/CSRgen/tomcat_CSR.htm   重要注意事项 An Importa ...

  10. 如何解决链入js,innerHTML中文乱码问题呢?

    描述:发生在做suhuotong网站的时候,添加在线客服代码的时候三个地方1.将js以UTF-8无BOM编码:VS修改或者使用NotePad++修改2.<meta http-equiv=&quo ...