zoukankan      html  css  js  c++  java
  • JavaBean详解

    java内省(Introspector)

    内省(Introspector) 是Java 语言对JavaBean类属性、事件的一种缺省处理方法。
      JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。
      例如类UserInfo :
      

    package com.peidasoft.instrospector;
    
    public class UserInfo {
    
        private long userId;
        private String userName;
        private int age;
        private String emailAddress;
    
        public long getUserId() {
            return userId;
        }
    
        public void setUserId(long userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getEmailAddress() {
            return emailAddress;
        }
    
        public void setEmailAddress(String emailAddress) {
            this.emailAddress = emailAddress;
        }
    }
    

    在类UserInfo中有属性userName,那我们可以通过getUserName, setUserName来得到其值或者设置新的值。通过getUserName/setUserName来访问userName属性,这就是默认的规则。Java JDK中提供了一套API用来访问某个属性的getter/setter方法,这就是内省。
    JDK内省类库:


    PropertyDescriptor类:
      PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:
      1. getPropertyType(),获得属性的Class对象;
      2. getReadMethod(),获得用于读取属性值的方法;
       3. getWriteMethod(),获得用于写入属性值的方法;
      4. hashCode(),获取对象的哈希值;
      5. setReadMethod(Method readMethod),设置用于读取属性值的方法;
      6. setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
      实例代码如下:
      

    package com.peidasoft.instrospector;  
      
    import java.beans.BeanInfo;  
    import java.beans.Introspector;  
    import java.beans.PropertyDescriptor;  
    import java.lang.reflect.Method;  
      
    public class BeanInfoUtil {  
      
        // 设置bean的某个属性值  
        public static void setProperty(UserInfo userInfo, String userName) throws Exception {  
            // 获取bean的某个属性的描述符  
            PropertyDescriptor propDesc = new PropertyDescriptor(userName, UserInfo.class);  
            // 获得用于写入属性值的方法  
            Method methodSetUserName = propDesc.getWriteMethod();  
            // 写入属性值  
            methodSetUserName.invoke(userInfo, "wong");  
            System.out.println("set userName:" + userInfo.getUserName());  
        }  
      
        // 获取bean的某个属性值  
        public static void getProperty(UserInfo userInfo, String userName) throws Exception {  
            // 获取Bean的某个属性的描述符  
            PropertyDescriptor proDescriptor = new PropertyDescriptor(userName, UserInfo.class);  
            // 获得用于读取属性值的方法  
            Method methodGetUserName = proDescriptor.getReadMethod();  
            // 读取属性值  
            Object objUserName = methodGetUserName.invoke(userInfo);  
            System.out.println("get userName:" + objUserName.toString());  
        }  
    }  
    

    Introspector类:
      将JavaBean中的属性封装起来进行操作。在程序把一个类当做JavaBean来看,就是调用Introspector.getBeanInfo()方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。
      getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。具体代码如下:
      

    import java.beans.BeanInfo;
    import java.beans.Introspector;
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Method;
    
    public class BeanInfoUtil {
        
        // 通过内省设置bean的某个属性值
        public static void setPropertyByIntrospector(UserInfo userInfo, String userName) throws Exception {
            // 获取bean信息
            BeanInfo beanInfo = Introspector.getBeanInfo(UserInfo.class);
            // 获取bean的所有属性列表
            PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors();
            // 遍历属性列表,查找指定的属性
            if (proDescrtptors != null && proDescrtptors.length > 0) {
                for (PropertyDescriptor propDesc : proDescrtptors) {
                    // 找到则写入属性值
                    if (propDesc.getName().equals(userName)) {
                        Method methodSetUserName = propDesc.getWriteMethod();
                        methodSetUserName.invoke(userInfo, "alan");  // 写入属性值
                        System.out.println("set userName:" + userInfo.getUserName());
                        break;
                    }
                }
            }
        }
    
        // 通过内省获取bean的某个属性值
        public static void getPropertyByIntrospector(UserInfo userInfo, String userName) throws Exception {
            BeanInfo beanInfo = Introspector.getBeanInfo(UserInfo.class);
            PropertyDescriptor[] proDescrtptors = beanInfo.getPropertyDescriptors();
            if (proDescrtptors != null && proDescrtptors.length > 0) {
                for (PropertyDescriptor propDesc : proDescrtptors) {
                    if (propDesc.getName().equals(userName)) {
                        Method methodGetUserName = propDesc.getReadMethod();
                        Object objUserName = methodGetUserName.invoke(userInfo);
                        System.out.println("get userName:" + objUserName.toString());
                        break;
                    }
                }
            }
        }
    }
    

    通过这两个类的比较可以看出,都是需要获得PropertyDescriptor,只是方式不一样:前者通过创建对象直接获得,后者需要遍历,所以使用PropertyDescriptor类更加方便。
      使用实例:
      

    package com.peidasoft.instrospector;
    
    public class BeanInfoTest {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("peida");
            try {
                BeanInfoUtil.getProperty(userInfo, "userName");
                BeanInfoUtil.setProperty(userInfo, "userName");
                BeanInfoUtil.getProperty(userInfo, "userName");
                BeanInfoUtil.setPropertyByIntrospector(userInfo, "userName");
                BeanInfoUtil.getPropertyByIntrospector(userInfo, "userName");
                BeanInfoUtil.setProperty(userInfo, "age");  // IllegalArgumentException
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    BeanUtils工具包

    由上述可看出,内省操作非常的繁琐,所以所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。
      BeanUtils工具包:下载:http://commons.apache.org/beanutils/,注意:应用的时候还需要一个logging包http://commons.apache.org/logging/
      使用BeanUtils工具包完成上面的测试代码:
      

    package com.peidasoft.instrospector;
    
    import java.lang.reflect.InvocationTargetException;
    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.commons.beanutils.PropertyUtils;
    
    public class BeanInfoTest {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("peida");
            try {
                BeanUtils.setProperty(userInfo, "userName", "peida");
                System.out.println("set userName:" + userInfo.getUserName());
                System.out.println("get userName:" + BeanUtils.getProperty(userInfo, "userName"));
                BeanUtils.setProperty(userInfo, "age", 18);
                System.out.println("set age:" + userInfo.getAge());
                System.out.println("get age:" + BeanUtils.getProperty(userInfo, "age"));
                System.out.println("get userName type:" + BeanUtils.getProperty(userInfo, "userName").getClass().getName());
                System.out.println("get age type:" + BeanUtils.getProperty(userInfo, "age").getClass().getName());
                PropertyUtils.setProperty(userInfo, "age", 8);
                System.out.println(PropertyUtils.getProperty(userInfo, "age"));
                System.out.println(PropertyUtils.getProperty(userInfo, "age").getClass().getName());
                PropertyUtils.setProperty(userInfo, "age", "8");  // IllegalArgumentException
            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
    

    说明:
      1. 获得属性的值,例如,BeanUtils.getProperty(userInfo, "userName"),返回字符串。
      2. 设置属性的值,例如,BeanUtils.setProperty(userInfo, "age", 8),参数是字符串或基本类型自动包装。设置属性的值是字符串,获得的值也是字符串,不是基本类型。   3. BeanUtils的特点:
      1). 对基本数据类型的属性的操作:在WEB开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。
      2). 对引用数据类型的属性的操作:首先在类中必须有对象,不能是null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(userInfo, "birthday.time", 111111);

    package com.peidasoft.Introspector;
    import java.util.Date;
    
    public class UserInfo {
    
        private Date birthday = new Date(); // 引用类型的属性,不能为null
        
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
        public Date getBirthday() {
            return birthday;
        }      
    }
    
    package com.peidasoft.Beanutil;
    
    import java.lang.reflect.InvocationTargetException;
    import org.apache.commons.beanutils.BeanUtils;
    import com.peidasoft.Introspector.UserInfo;
    
    public class BeanUtilTest {
        public static void main(String[] args) {
            UserInfo userInfo=new UserInfo();
             try {
                BeanUtils.setProperty(userInfo, "birthday.time","111111");  // 操作对象的属性,而不是整个对象
                Object obj = BeanUtils.getProperty(userInfo, "birthday.time");  
                System.out.println(obj);          
            } 
             catch (IllegalAccessException e) {
                e.printStackTrace();
            } 
             catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }
    

    PropertyUtils类和BeanUtils不同在于,运行getProperty、setProperty操作时,没有类型转换,使用属性的原有类型或者包装类。由于age属性的数据类型是int,所以方法PropertyUtils.setProperty(userInfo,"age", "8")会爆出数据类型不匹配,无法将值赋给属性。

    JavaBean的编辑器

    在Spring配置文件里,我们往往通过字面值为Bean各种类型的属性提供设置值:不管是double类型还是int类型,在配置文件中都对应字符串类型的字面值。BeanWrapper填充Bean属性时如何将这个字面值转换为对应的double或int等内部类型呢?我们可以隐约地感觉到一定有一个转换器在其中起作用,这个转换器就是属性编辑器。
      “属性编辑器”这个名字可能会让人误以为是一个带用户界面的输入器,其实属性编辑器不一定非得有用户界面,任何实现java.beans.PropertyEditor接口的类都是属性编辑器。属性编辑器的主要功能就是将外部的设置值转换为JVM内部的对应类型,所以属性编辑器其实就是一个类型转换器。
      PropertyEditor是JavaBean规范定义的接口,JavaBean规范中还有其他一些PropertyEditor配置的接口。为了彻底理解属性编辑器,必须对JavaBean中有关属性编辑器的规范进行学习,相信这些知识对学习和掌握Spring中的属性编辑器会大有帮助。
      Sun所制定的JavaBean规范,很大程度上是为IDE准备的——它让IDE能够以可视化的方式设置JavaBean的属性。如果在IDE中开发一个可视化应用程序,我们需要通过属性设置的方式对组成应用的各种组件进行定制,IDE通过属性编辑器让开发人员使用可视化的方式设置组件的属性。
      一般的IDE都支持JavaBean规范所定义的属性编辑器,当组件开发商发布一个组件时,它往往将组件对应的属性编辑器捆绑发行,这样开发者就可以在IDE环境下方便地利用属性编辑器对组件进行定制工作。
      JavaBean规范通过java.beans.PropertyEditor定义了设置JavaBean属性的方法,通过BeanInfo描述了JavaBean哪些属性是可定制的,此外还描述了可定制属性与PropertyEditor的对应关系。
      BeanInfo与JavaBean之间的对应关系,通过两者之间规范的命名确立,对应JavaBean的BeanInfo采用如下的命名规范:BeanInfo。如ChartBean对应的BeanInfo为ChartBeanBeanInfo,Car对应的BeanInfo为CarBeanInfo。当JavaBean、其各属性的编辑器、和BeanInfo信息一起注册到IDE中后,当在开发界面中对JavaBean进行定制时,IDE就会根据JavaBean规范找到对应的BeanInfo,再根据BeanInfo中的描述信息找到JavaBean属性描述(是否开放、使用哪个属性编辑器),进而为JavaBean生成特定开发编辑界面。
      JavaBean规范提供了一个管理默认属性编辑器的管理器:PropertyEditorManager,该管理器内保存着一些常见类型的属性编辑器,如果某个JavaBean的常见类型属性没有通过BeanInfo显式指定属性编辑器,IDE将自动使用PropertyEditorManager中注册的对应默认属性编辑器。
      由于JavaBean对应的属性编辑器等IDE环境相关的资源和组件需要动态加载,所以在纯Java的IDE中开发基于组件的应用时,总会感觉IDE反应很迟钝,不像Delphi、C++ Builder一样灵敏快捷。但在Eclipse开发环境中,设计包括可视化组件的应用时却很快捷,原因是Eclipse没有使用Java的标准用户界面组件库,当然也就没有按照JavaBean的规范开发设计GUI组件了。

    PropertyEditor

    PropertyEditor是属性编辑器的接口,它规定了将外部设置值转换为内部JavaBean属性值的转换接口方法。PropertyEditor主要的接口方法说明如下:

    • Object getValue():返回属性的当前值。基本类型被封装成对应的包装类实例;
    • void setValue(Object newValue):设置属性的值,基本类型以包装类传入(自动装箱);
    • String getAsText():将属性对象用一个字符串表示,以便外部的属性编辑器能以可视化的方式显示。缺省返回null,表示该属性不能以字符串表示;
    • void setAsText(String text):用一个字符串去更新属性的内部值,这个字符串一般从外部属性编辑器传入;
    • String[] getTags():返回表示有效属性值的字符串数组(如boolean属性对应的有效Tag为true和false),以便属性编辑器能以下拉框的方式显示出来。缺省返回null,表示属性没有匹配的字符值有限集合;
    • String getJavaInitializationString():为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值。
        可以看出PropertyEditor接口方法是内部属性值和外部设置值的沟通桥梁。此外,我们可以很容易地发现该接口的很多方法是专为IDE中的可视化属性编辑器提供的:如getTags()、getJavaInitializationString()以及另外一些我们未此介绍的接口方法。
        Java为PropertyEditor提供了一个方便的实现类:PropertyEditorSupport,该类实现了PropertyEditor接口并提供默认实现,一般情况下,用户可以通过扩展这个方便类设计自己的属性编辑器。

    BeanInfo

    BeanInfo主要描述了JavaBean哪些属性可以编辑以及对应的属性编辑器,每一个属性对应一个属性描述器PropertyDescriptor。PropertyDescriptor的构造函数有两个入参:PropertyDescriptor(String propertyName, Class beanClass) ,其中propertyName为属性名;而beanClass为JavaBean对应的Class。
      此外PropertyDescriptor还有一个setPropertyEditorClass(Class propertyEditorClass)方法,为JavaBean属性指定编辑器。BeanInfo接口最重要的方法就是:PropertyDescriptor[] getPropertyDescriptors() ,该方法返回JavaBean的属性描述器数组。
      BeanInfo接口有一个常用的实现类:SimpleBeanInfo,一般情况下,可以通过扩展SimpleBeanInfo实现自己的功能。

    Spring默认属性编辑器

    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

    PropertyEditorRegistrySupport中有两个用于保存属性编辑器的Map类型变量:
    defaultEditors:用于保存默认属性类型的编辑器,元素的键为属性类型,值为对应的属性编辑器实例;
    customEditors:用于保存用户自定义的属性编辑器,元素的键值和defaultEditors相同。
      PropertyEditorRegistrySupport通过类似以下的代码定义默认属性编辑器:

    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类的简要代码:
      

    public class Car {    
        private int maxSpeed;    
        public String brand;    
        private double price;    
        //省略get/setter    
    }    
    
    public class Boss {    
        private String name;    
        private Car car = new Car();    
        //省略get/setter    
    }   
    

    Boss有两个属性:name和car,分别对应String类型和Car类型。Spring拥有String类型的默认属性编辑器,因此对于String类型的属性我们不用操心。但Car类型是我们自定义的类型,要配置Boss的car属性,有两种方案:
    1)在配置文件中为car专门配置一个,然后在boss的中通过ref引用car Bean,这正是我们上一章中所用的方法;
    2)为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,而不是属性编辑器的类名。
      最精彩的部分当然是③处的配置,我们原来通过一个元素标签配置好car Bean,然后在boss的中通过ref引用car Bean,但是现在我们直接通过value为car属性提供配置。BeanWrapper在设置boss的car属性时,它将检索自定义属性编辑器的注册表,当发现Car属性类型拥有对应的属性编辑器CustomCarEditor时,它就会利用CustomCarEditor将“红旗CA72,200,20000.00”转换为Car对象。
      
    引用:
      按照JavaBeans的规范,JavaBeans的基础设施会在JavaBean相同类包下查找是否存在Editor的类,如果存在,自动使用Editor作为该JavaBean的PropertyEditor。
      如com.baobaotao.domain.UserEditor会自动成为com.baobaotao.domain.User对应的PropertyEditor。Spring也支持这个规范,也即如果采用这种规约命令PropertyEditor,就无须显式在CustomEditorConfigurer中注册了,Spring将自动查找并注册这个PropertyEditor。
      另:Spring 3.0除支持PropertyEditor外,还在核心包中引入了自建的ConversionService,它提供了更为强大的类型转换的能力,可以完成任意类型之间的转换,还可以在转换过程中参考目标对象所在宿主类的上下文信息。Spring的类型转换同时支持PropertyEditor和ConversionService。

    引用块内容
    http://stamen.iteye.com/blog/1525668
    http://www.cnblogs.com/peida/archive/2013/06/03/3090842.html

  • 相关阅读:
    解决linux下打开windows下压缩文件乱码的问题
    vim & emacs 强制修改 root 权限的文件
    在ubuntu下,给 svn diff 一点颜色
    sql优化(一)
    sql优化(二) 索引(一)
    Java反射与思想!
    JDK5.0枚举 泛型 注释
    忘羡的Day9!
    来博客的第二天!
    来博客第一天
  • 原文地址:https://www.cnblogs.com/csuwater/p/5419930.html
Copyright © 2011-2022 走看看