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

时间:2022-05-19 19:05:12

【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 <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源代码
JavaBean部分参考张孝祥老师的Java基础强化资料