【Java进阶】内省IntroSpector操作JavaBean和Apache-commons-dbutils对内省的使用

来源:互联网 发布:广电网络缴费 编辑:程序博客网 时间:2024/06/06 01:27

【Java进阶】内省IntroSpector操作JavaBean和Apache-commons-dbutils对内省的使用

内省IntroSpector操作JavaBean

介绍JavaBean

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。

这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?

怎么确定JavaBean的属性?
JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。
如果方法名为setId,就是设置id,至于你把它存到哪个变量上,用管吗?
如果方法名为getId,就是获取id,至于你从哪个变量上取,用管吗?
去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小写的。

setId的属性名,id
isLast的属性名,last
setCPU的属性名,CPU
getUPS的属性名,UPS

总之,一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java内部的成员变量。

 public class Person{     private int x;     public int getAge(){ return x; }     public void setAge(int age) { this.x = age; } }

一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean。
好处如下:

  • 在JavaEE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就遵守大家的约定。
  • JDK中提供了对JavaBean进行操作的API,这套API称为内省。如果要你自己去通过getX方法来访问私有的x,则怎么做,有一定难度吧?用这套内省api操作JavaBean比用普通类的方式更方便。

IntroSpector示例

对JavaBean操作常用的类有PropertyDescriptor, IntroSpector, BeanInfo等。

定义JavaBean

package javaenhance.part02;import org.junit.Test;import java.beans.IntrospectionException;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * description: * * @author liyazhou * @since 2017-08-12 12:39 */// 定义JavaBeanclass Person{    private int age;    public Person(){}    public Person(int age){        this.age = age;    }    public int getAge(){        return age;    }    public void setAge(int age){        this.age = age;    }}

使用PropertyDescriptor操作JavaBean

public class IntroSepctorTest {    // 不使用getter方法情况下,通过JavaBean对象、属性名称和新的属性值,为JavaBean设置新的属性    public void setProperty(Object obj, String propertyName, Object newValue) throws Exception {        // 属性描述符,根据属性名称和字节码获取到该属性的描述符        PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());        // 通过属性描述符,获取到该属性的写方法,也就是setter方法        Method setter = pd.getWriteMethod();        // invoke该属性的写方法Method        setter.invoke(obj, newValue);    }    // 不使用getter方法情况下,通过JavaBean对象、属性名称,获得JavaBean的属性值    public Object getProperty(Object obj, String propertyName) throws Exception {        // 属性描述符,根据属性名称和字节码获取到该属性的描述符        PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());        // 通过属性描述符,获取到该属性的读方法,也就是getter方法        Method getter = pd.getReadMethod();        // invoke该属性的读方法Method        return getter.invoke(obj);    }    @Test    public void propertyTest() throws Exception {        Person p = new Person(12);        String propertyName = "age";        Object retVal = getProperty(p, propertyName);        System.out.println(propertyName + " = " + retVal);        setProperty(p, propertyName, 22);        System.out.println(propertyName + " = " + p.getAge());    }}

执行结果如下:

age = 12age = 22

此处,仅仅是抛砖引玉,大家可以通过查看java.beans.PropertyDescriptor学习更多的关于内省的知识。

DbUtils对内省的使用

之前看过Apache commons DbUtils的源码,记得有一段关于内省的源码,现在贴出一段,看看大神们怎么运用内省技术的。

源码分析

下面的这段代码在 org.apache.commons.dbutils.AbstractQueryRunner 这个类中。其中的中文部分,是自己的解释,如有问题,欢迎留言指正。

    /**     * Fill the <code>PreparedStatement</code> replacement parameters with the     * given object's bean property values.     *     * @param stmt     *            PreparedStatement to fill     * @param bean     *            A JavaBean object     * @param propertyNames     *            An ordered array of property names (these should match the     *            getters/setters); this gives the order to insert values in the     *            statement     * @throws SQLException     *             If a database access error occurs     */    public void fillStatementWithBean(PreparedStatement stmt, Object bean,            String... propertyNames) throws SQLException {        PropertyDescriptor[] descriptors;          try {            // 通过bean的字节码获取到所有属性的描述符,是以数组形式返回的            descriptors = Introspector.getBeanInfo(bean.getClass())                    .getPropertyDescriptors();        } catch (IntrospectionException e) {            throw new RuntimeException("Couldn't introspect bean "                    + bean.getClass().toString(), e);        }        // 下面操作为了对属性描述符排序,以实现属性描述和属性的名称相对应        // 两层for循环,可见通用类的程序是有性能高代价的        // 总之,要权衡代码的复用性和性能的问题了        PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];        for (int i = 0; i < propertyNames.length; i++) {            // 获取一个属性名称            String propertyName = propertyNames[i];            if (propertyName == null) {                throw new NullPointerException("propertyName can't be null: "                        + i);            }            boolean found = false;            // 查找上面属性名称对应的属性描述符            for (int j = 0; j < descriptors.length; j++) {                PropertyDescriptor descriptor = descriptors[j];                if (propertyName.equals(descriptor.getName())) {                    sorted[i] = descriptor;                    found = true;                    break;                }            }            if (!found) {  // 如果没有发现该属性对应的属性描述符,则抛出异常                throw new RuntimeException("Couldn't find bean property: "                        + bean.getClass() + " " + propertyName);            }        }        fillStatementWithBean(stmt, bean, sorted);    }

另一个方法

    public void fillStatementWithBean(PreparedStatement stmt, Object bean,            PropertyDescriptor[] properties) throws SQLException {        Object[] params = new Object[properties.length];        for (int i = 0; i < properties.length; i++) {            PropertyDescriptor property = properties[i];            Object value = null;            Method method = property.getReadMethod();            if (method == null) {                throw new RuntimeException("No read method for bean property "                        + bean.getClass() + " " + property.getName());            }            try {                // 为了获取属性的值                value = method.invoke(bean, new Object[0]);            } catch (InvocationTargetException e) {                throw new RuntimeException("Couldn't invoke method: " + method,                        e);            } catch (IllegalArgumentException e) {                throw new RuntimeException(                        "Couldn't invoke method with 0 arguments: " + method, e);            } catch (IllegalAccessException e) {                throw new RuntimeException("Couldn't invoke method: " + method,                        e);            }            params[i] = value;        }        fillStatement(stmt, params);    }

能不能优化以上的程序

以上两个函数的目的就是,根据属性的名称和JavaBean获取到对应的属性值,它要求属性值跟属性名称的顺序性。

为了对属性描述符排序,以实现属性描述和属性的名称相对应,所以源代码中使用了两层for循环实现的。

个人认为这部分是存在优化空间的,可以使用下面的这种方式获取到属性名称对应的属性的值,而不需要循环操作。

 // 不使用getter方法情况下,通过JavaBean对象、属性名称,获得JavaBean的属性值 public Object getProperty(Object obj, String propertyName) throws Exception {     PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());     Method getter = pd.getReadMethod();     return getter.invoke(obj); }

参考

Apache commons dbutils源代码
《Java基础强化教程-张孝祥》

阅读全文
1 0
原创粉丝点击