java bean 内省的基础

java bean 的内省,其实可以算是反射的一种基础应用,关于 java 的反射,无非就是获得对应的类、属性、方法、修饰符等的应用,对于 java 的反射探讨,可以点击参考 java 7 Reflection。在这里,我们关注一下 java 对 普通 bean 的使用和应用。

在 一个 普通 bean 里,我们会关注起属性与其对应的get/set 方法,如在一个类 User 中,关注属性 name 的同时,我们同样会关注一下 getName 和 setName 方法。在 java 里,专门提供了这么一种机制 Instropector(内省), 是 java 对 bean 属性、方法的一种缺省处理方法。通过 Introspector ,我们可以获得一个 bean 的基础信息 BeanInfo,进而获取这个 bean 的属性描述器 PropertyDescriptor,通过属性描述器,我们可以访问其对应的 get/set 方法。示例代码如下(为了演示方便,这里只是简单的抛出异常):

public class TestReflect {
    public static void main(String[] args) throws IntrospectionException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
        PropertyDescriptor props[] = userBeanInfo.getPropertyDescriptors();
        // 输出 user 里所有属性
        for (PropertyDescriptor prop : props) {
            System.out.print(prop.getName() + " ");
        }
        System.out.println();
        // 获取第一个属性 age ,并调用其 set 方法
        User user = User.class.newInstance();
        Method setter = props[0].getWriteMethod();
        setter.invoke(user, 18);
        System.out.println(user);
    }
}
 
public class User {
 
    private String name;
    private byte sex;
    private int age;
    private String email;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public byte getSex() {
        return sex;
    }
    public void setSex(byte sex) {
        this.sex = sex;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", sex=" + sex + ", age=" + age + ", email=" + email + "]";
    }
    
}

输出如下:

注意在输入里,其属性描述符包好一个 User 类不存在的属性 class。这里解释一下,通过 beanInfo 获取的属性描述符,主要是通过 bean 里 getXxx() 方法来获取的,换言之及时没有属性 password ,如果存在方法 getPassword(),那么在获取属性描述符的时候,同样也会获取到 password 属性。可在在这里为什么会获取到 class 属性呢??因为 User 默认继承了 Object 类(Object 有一个方法 getClass())。

通过属性 PropertyDescriptor#getReadMethod()/getWriteMethod() 可以对应的获取属性的 get/set 方法(Method),如果不存在对应属性的 get/set 方法,会返回 null。获取到对应 Method 之后,就可以调用 invoke 方法了。

dbutils 的里一个小应用

Apache dbutils 是一个很小巧的 jdbc 处理集,当中就提供了对 ResultSet 转换为 java bean,bean List 的机制,其中的原理其实就是 java bean 的内省机制的使用。关于 dbutils 的其他探讨可以参考 从 Apache dbutils 所学到的。在此我们探讨一下dbutils 是如何将一个 ResultSet 转换为 对应的 java bean 的。

在开始看代码之前,我们可以先思考一下,应该如何处理??

首先,我们应该获取指定 java bean 的属性描述符;

第二步:应该就是获取数据库对应的列属性名称;

第三步:应该就是创建指定的 java bean;

第四步:应该就是匹配属性名称与列属性名称,如果匹配,就调用java bean 对应的 set 方法。

大概的流程应该是这样,不过为了完善一点,我们应该考虑一下。如下情况:1)可能会存在 java bean 属性和列属性不一致的情况,可有需要将列的值赋给指定 java bean 的属性。2)对应数据库的 null 值,我们应该将其转换为 java 里对应的默认值,如 int 的默认值为 0 等。3)嗯,大概也就这些了。

现在来看一下 dbutils 的实现,整体函数如:

public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
    // 获取指定 java bean 的属性描述符
    PropertyDescriptor[] props = this.propertyDescriptors(type);
    // 通过 ResultSetMetaData 获取数据库列属性名称
    ResultSetMetaData rsmd = rs.getMetaData();
    // 处理 java bean 属性和列属性不一致的情况
    int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
    // 创建一个指定的 java bean
    return this.createBean(rs, type, props, columnToProperty);
}

接着就是分步实现上面的功能:

1)获取指定 java bean 的属性描述符,(这个应该很简单,直接就是 Instropector.getBeanInfo(class),只是需要额外处理一下异常就行了).

private PropertyDescriptor[] propertyDescriptors(Class<?> c)
    throws SQLException {
    BeanInfo beanInfo = null;
    try {
        beanInfo = Introspector.getBeanInfo(c);
    } catch (IntrospectionException e) {
        throw new SQLException(
            "Bean introspection failed: " + e.getMessage());
    }
 
    return beanInfo.getPropertyDescriptors();
}

2)第二步,处理java bean 属性与 列属性不一致的情况。这个也不难,首先我们一个java bean 属性到列属性覆盖的 HashMap<String,String>(),接着就是使用一个数组 columnToProperty, 用来记录列属性的到java bean属性的索引值(在 PropertyDescriptor[] 的索引,columnToProperty 默认是列属性没有与java bean 属性对应,值为 -1)。谈到这里,我们就会知道,在这个工具类里 BeanProcessor 的构造函数应该提供一个HashMap<String,String>(),默认构造函数,可以创建一个没有元素的空的 HashMap<String,String>()。参考代码如下:

// columnToProperty 的默认值(没有覆盖java bean 的指定属性)
protected static final int PROPERTY_NOT_FOUND = -1;
// 需要覆盖的 java bean 属性
private final Map<String, String> columnToPropertyOverrides;
// 默认的构造函数,创建一个元素为空的 HashMap
public BeanProcessor() {
this(new HashMap<String, String>());
}
public BeanProcessor(Map<String, String> columnToPropertyOverrides) {
    super();
    if (columnToPropertyOverrides == null) {
        throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
    }
    this.columnToPropertyOverrides = columnToPropertyOverrides;
}

那如何进行记录列属性的到java bean属性的索引值呢??参考代码如下:

protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
        PropertyDescriptor[] props) throws SQLException {
    
    int cols = rsmd.getColumnCount();//获数据库表的列的数目,注意其索引从 1 开始
    int[] columnToProperty = new int[cols + 1];
    Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);// 默认赋值为 -1
 
    for (int col = 1; col <= cols; col++) {
         // 获取列属性名的别名
        String columnName = rsmd.getColumnLabel(col);
        if (null == columnName || 0 == columnName.length()) {
          columnName = rsmd.getColumnName(col);// 获取列属性名
        }
        //查找该属性是否需要覆盖
        String propertyName = columnToPropertyOverrides.get(columnName);
        if (propertyName == null) {
            propertyName = columnName;
        }
        //记录其在 props 属性描述数组里的索引位置
        for (int i = 0; i < props.length; i++) {
 
            if (propertyName.equalsIgnoreCase(props[i].getName())) {
                columnToProperty[col] = i;
                break;
            }
        }
    }
 
    return columnToProperty;
}

3)就是根据指定列属性和java bean 的 set 方法,创建一个 java bean。如下:

创建一个 java bean 简单(额外处理一些异常),如下:

protected <T> T newInstance(Class<T> c) throws SQLException {
    try {
        return c.newInstance();
 
    } catch (InstantiationException e) {
        throw new SQLException(
            "Cannot create " + c.getName() + ": " + e.getMessage());
 
    } catch (IllegalAccessException e) {
        throw new SQLException(
            "Cannot create " + c.getName() + ": " + e.getMessage());
    }
}

2)调用相对应的 set 方法

private <T> T createBean(ResultSet rs, Class<T> type,
       PropertyDescriptor[] props, int[] columnToProperty)
       throws SQLException {
    //创建 bean
   T bean = this.newInstance(type);
 
   for (int i = 1; i < columnToProperty.length; i++) {
       //该列属性不需要赋值给 java bean 的属性
       if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
           continue;
       }
       //根据 columnToProperty 的索引获取对应的属性描述符
       PropertyDescriptor prop = props[columnToProperty[i]];
       Class<?> propType = prop.getPropertyType();//获取属性描述符的类型
        //获取列属性的值
       Object value = this.processColumn(rs, i, propType);
        // 当列属性为 null 时,根据其类型赋值其相对应的默认值
       if (propType != null && value == null && propType.isPrimitive()) {
           value = primitiveDefaults.get(propType);
       }
        // 调用bean 对一个的 set 方法,注意支持 8 种基本数据类型和String 的set 方法
       this.callSetter(bean, prop, value);
   }
 
   return bean;
}

4)默认值处理很简单,根据属性描述的类型,赋值对应默认值行了。为了性能上优化,往往也会使用静态变量来存储 java 8 种基本数据类型的默认值,这里采用 map 来存储,如下:

private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>();
static {
    primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
    primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0));
    primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0));
    primitiveDefaults.put(Float.TYPE, Float.valueOf(0f));
    primitiveDefaults.put(Double.TYPE, Double.valueOf(0d));
    primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
    primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
    primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
}

现在我们已经知道如何将将数据库表映射到一个bean了,那么处理 bean list 也就不难,就是创建一个 List<T>,接着就是将每一行映射得到的 bean ,添加到 List里就可以了。

public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
    List<T> results = new ArrayList<T>();
 
    if (!rs.next()) {
        return results;
    }
 
    PropertyDescriptor[] props = this.propertyDescriptors(type);
    ResultSetMetaData rsmd = rs.getMetaData();
    int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
 
    do {
        results.add(this.createBean(rs, type, props, columnToProperty));
    } while (rs.next());
 
    return results;
}

最后感谢你的浏览,谢谢。

转载于:https://blog.51cto.com/peiquan/1427588

从 java bean 的内省到 dbutils 的应用相关推荐

  1. ejb java bean的区别_关于ejb:Bean,Java Bean和Enterprise Java Bean之间的区别

    本问题已经有最佳答案,请猛点这里访问. 我已经阅读了关于EJB,JavaBean的文章,但是我仍然怀疑"bean"这个词本身是什么意思呢? a)纯术语"bean" ...

  2. java对象转json字符串日期格式_fastJSON字符串类型数据中的日期转换为Java bean的日期对象...

    fastJSON字符串类型数据中的日期转换为Java bean的日期对象 Person.java import java.io.Serializable; import java.util.Date; ...

  3. java bean与xml转换_Java Bean与xml互相转换的方法分析

    本文实例讲述了Java Bean与xml互相转换的方法.分享给大家供大家参考,具体如下: XML和Java Bean互相转换是一个很有用的功能,因为两者有着前后合作的关系,但解析的过程比较痛苦.下面介 ...

  4. JAVA Bean和XML之间的相互转换 - XStream简单入门

    JAVA Bean和XML之间的相互转换 - XStream简单入门 背景介绍 XStream的简介 注解简介 应用实例 背景介绍 我们在工作中经常 遇到文件解析为数据或者数据转化为xml文件的情况, ...

  5. 【建议收藏】MD5 算法的Java Bean

    MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输 ...

  6. Java+MyEclipse+Tomcat (五)DAO和Java Bean实现数据库和界面分开操作

    正如前面一篇文章的介绍,当使用Servlet提交表单和JSP数据库查询时,总是相互交叉着的处理,要么在JSP中通过<%...%>内嵌Java代码操作数据库,要么 JSP中通过Post方法提 ...

  7. Java Bean Validation 最佳实践

    <h1 class="postTitle"><a id="cb_post_title_url" class="postTitle2& ...

  8. webservice xsd.exe根据xml生成xsd。然后根据xsd生成java bean

    2019独角兽企业重金招聘Python工程师标准>>> webservice xsd.exe根据xml生成xsd.然后根据xsd生成java bean 转载于:https://my. ...

  9. EJB(Enterprise Java Bean)存在的问题

    EJB(Enterprise Java Bean)存在的问题:EJB 是重量级的框架. 运行环境苛刻 代码移植性差

最新文章

  1. 云计算带来企业管理的革命
  2. 主成分分析(PCA) Java
  3. Ladda – 把加载提示效果集成到按钮中,提升用户体验
  4. PaddlePaddle实现波士顿房价预测
  5. storm java开发环境搭建,看这里!Storm【单机版】环境搭建
  6. bigdecimal 保留两位小数_不要以为你用了BigDecimal后,计算结果就一定精确了
  7. C#语句之while语句
  8. postfix和dovecot架设邮件服务器的一些记录1
  9. ConcurrentHashMap1.7到1.8变化
  10. python脚本模拟浏览器实现学习通自动刷网课
  11. JAVA实现10种排序
  12. 手机如何在线图片识别?3大教程,一键轻松图片转文字
  13. unity实现透视相机与正交相机的平滑切换
  14. 利用Photoshop制作毛玻璃效果
  15. 如何设置条码标签的打印数量
  16. 我是如何入行嵌入式开发的
  17. java批量打印标签_java批量打印
  18. (java)密码加密。某系统的数字密码,比如1983,采用加密方式进行传输,规则如下:先得到每位数,然后每位数都加上5,再对10求余,最后将所有数字反转,得到一串新数。
  19. XML注入:实战篇--MCIR的XMLmao模块0~1
  20. 1-3年开发的java学习规划

热门文章

  1. TestBird烧烤趴,几维安全专属报名通道
  2. chrome出现adobe flash playe 不是最新版本
  3. Nginx的配置文件
  4. Apache Rewrite url重定向功能的简单配置
  5. LinkedIn 开源成功的秘密
  6. Makefile写法入门心得
  7. REM——适合移动开发的自适应方案
  8. 141. Linked List Cycle
  9. 基于LODOP的打印
  10. DS实验题 Floyd最短路径 Prim最小生成树