【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基础强化资料