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

【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
 */

// 定义JavaBean
class 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 = 12
age = 22

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

DbUtils对内省的使用

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

源码分析

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

    /**
     * Fill the PreparedStatement 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基础强化教程-张孝祥》

你可能感兴趣的:(Java,Java进阶)