Apache Commons Beanutils 二 (动态Bean - DynaBeans)
相关背景
上一篇介绍了PropertyUtils的用法,PropertyUtils主要是在不修改bean结构的前提下,动态访问bean的属性;
但是有时候,我们会经常希望能够在不定义一个Java类的前提下,动态决定这个类中包含哪些属性,并动态访问它们的属性值,比较典型的使用场景是作为SQL查询的结果集的bean;
为了支持以上特性,Apache Commons Beanutils包为我们提供了DynaBean接口、
DynaClass接口;
举个简单例子如下:
DynaProperty[] props = new DynaProperty[]{ new DynaProperty("address", java.util.Map.class), new DynaProperty("subordinate", mypackage.Employee[].class), new DynaProperty("firstName", String.class), new DynaProperty("lastName", String.class) }; BasicDynaClass dynaClass = new BasicDynaClass("employee", null, props); DynaBean employee = dynaClass.newInstance(); employee.set("address", new HashMap()); employee.set("subordinate", new Employee[]{...}); employee.set("firstName", "Fred"); employee.set("lastName", "Flintstone"); DynaBean employee = ...; // 具体的DynaBean实现类 String firstName = (String) employee.get("firstName"); Address homeAddress = (Address) employee.get("address", "home"); Object subordinate = employee.get("subordinate", 2);
由于DynaBean和DynaClass都是接口,它们可以有多种实现形式,应用于多种场景。
接下来,会介绍在Apache Commons Beanutils包下,DynaBean和DynaClass接口不同的实现类;
当然,我们也可以自定义实现类来满足我们特定的需求;
基础实现类:BasicDynaBean和BasicDynaClass
先了解下这两个重要的实现,这两个类为DynaBean和DynaClass接口的基础实现类;
首先,我们可以这样创建一个DynaClass实例,其中类的成员属性是用DynaProperty类来描述的:
DynaProperty[] props = new DynaProperty[] { new DynaProperty("address", java.util.Map.class), new DynaProperty("subordinate", Employee[].class), new DynaProperty("firstName", String.class), new DynaProperty("lastName", String.class) }; BasicDynaClass dynaClass = new BasicDynaClass("employee", null, props);
注意这里的Class<?> dynaBeanClass参数为空,看下源码就发现,如果为null的话,默认会使用BasicDynaBean.class;
有了BasicDynaClass实例后,我们就可以开始创建DynaBean实例了,并且可以调用DynaBean接口中定义的方法,如get和set来读写属性值,如下所示:
DynaBean employee = dynaClass.newInstance(); employee.set("address", new HashMap<String, Object>()); employee.set("subordinate", new Employee[0]); employee.set("firstName", "Fred"); employee.set("lastName", "Flintstone");
System.out.println(employee.get("firstName"));
实现类:ResultSetDynaClass,处理数据库查询结果集
ResultSetDynaClass主要用于包装java.sql.ResultSet,即SQL查询时候返回的结果集;
不使用DynaBean的话,通常我们是这样处理的:
String sql = "SELECT id, name, address, state FROM user"; stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { Long id = rs.getLong("id"); String name = rs.getString("name"); String address = rs.getString("address"); boolean state = rs.getBoolean("state"); System.out.print("id: " + id); System.out.print(", name: " + name); System.out.print(", address: " + address); System.out.println(", state: " + state); }
使用ResultSetDynaClass的话,我们可以这样做:
String sql = "SELECT id, name, address, state FROM user"; stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(sql); Iterator<DynaBean> rows = (new ResultSetDynaClass(rs)).iterator(); while (rows.hasNext()) { DynaBean row = rows.next(); System.out.print("id: " + row.get("id")); System.out.print(", name: " + row.get("name")); System.out.print(", address: " + row.get("address")); System.out.println(", state: " + row.get("state")); }
完整示例:
/* * File Name: ResultSetDyna.java * Description: * Author: PiChen * Create Date: 2017年5月30日 */ package apache.commons.beanutils.example.dynabeans; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Iterator; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.ResultSetDynaClass; /** * * @author PiChen * @version 2017年5月30日 */ public class ResultSetDyna { static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost/demo"; static final String USER = "root"; static final String PASS = "root"; public static void main(String[] args) { Connection conn = null; PreparedStatement stmt = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(DB_URL, USER, PASS); String sql = "SELECT id, name, address, state FROM user"; stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(sql); // while (rs.next()) // { // Long id = rs.getLong("id"); // String name = rs.getString("name"); // String address = rs.getString("address"); // boolean state = rs.getBoolean("state"); // // System.out.print("id: " + id); // System.out.print(", name: " + name); // System.out.print(", address: " + address); // System.out.println(", state: " + state); // } Iterator<DynaBean> rows = (new ResultSetDynaClass(rs)).iterator(); while (rows.hasNext()) { DynaBean row = rows.next(); System.out.print("id: " + row.get("id")); System.out.print(", name: " + row.get("name")); System.out.print(", address: " + row.get("address")); System.out.println(", state: " + row.get("state")); } rs.close(); stmt.close(); conn.close(); } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); } catch (SQLException se2) { } try { if (conn != null) conn.close(); } catch (SQLException se) { se.printStackTrace(); } } } }
实现类:RowSetDynaClass,处理数据库查询结果集,连接关闭后仍可使用
ResultSetDynaClass作为SQL查询结果集中的一个动态bean非常实用,但是仍然有一个严重的缺陷,就是使用ResultSetDynaClass的前提是要保证
ResultSet一直处于打开状态,这对于分层结构的Web项目来说是非常不便的,因为我们经常需要将数据从dao层传到service层传到view层,而ResultSet在DAO层使用后往往会关闭掉;
为解决这个问题,引入了RowSetDynaClass实现类,与ResultSetDynaClass不同的是,它会自己在内存中拷贝一份数据,这样就保证了即使ResultSet关闭后,数据也能一直被访问到;不过同样也有缺点就是需要消耗性能用于拷贝数据以及占用堆内存空间;
如下是一个示例:
Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(DB_URL, USER, PASS); String sql = "SELECT id, name, address, state FROM user"; stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(sql); RowSetDynaClass rowSet = new RowSetDynaClass(rs); rs.close(); stmt.close(); conn.close(); List<DynaBean> rowlist = rowSet.getRows(); for (DynaBean row : rowlist) { System.out.print("id: " + row.get("id")); System.out.print(", name: " + row.get("name")); System.out.print(", address: " + row.get("address")); System.out.println(", state: " + row.get("state")); }
完整示例:
/* * File Name: ResultSetDyna.java * Description: * Author: PiChen * Create Date: 2017年5月30日 */ package apache.commons.beanutils.example.dynabeans; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.RowSetDynaClass; /** * * @author PiChen * @version 2017年5月30日 */ public class RowSetDyna { static final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost/demo"; static final String USER = "root"; static final String PASS = "root"; public static void main(String[] args) { Connection conn = null; PreparedStatement stmt = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(DB_URL, USER, PASS); String sql = "SELECT id, name, address, state FROM user"; stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(sql); RowSetDynaClass rowSet = new RowSetDynaClass(rs); rs.close(); stmt.close(); conn.close(); List<DynaBean> rowlist = rowSet.getRows(); for (DynaBean row : rowlist) { System.out.print("id: " + row.get("id")); System.out.print(", name: " + row.get("name")); System.out.print(", address: " + row.get("address")); System.out.println(", state: " + row.get("state")); } } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); } catch (SQLException se2) { } try { if (conn != null) conn.close(); } catch (SQLException se) { se.printStackTrace(); } } } }
实现类:WrapDynaBean和WrapDynaClass,包装普通bean
使用WrapDynaBean,我们可以将普通的javabean包装成DynaBean,并非常简便的使用DynaBean提供的API方法来访问bean成员属性
示例:
Employee e = new Employee(); e.setFirstName("hello"); DynaBean wrapper = new WrapDynaBean(e); String firstName = (String) wrapper.get("firstName"); System.out.println(firstName);
注意,以上代码中,会间接的创建了WrapDynaClass实例,我们不需要直接处理它;
实现类:Lazy DynaBeans,简单易用的DynaBean实现
Lazy DynaBeans,正如其名,可以让我们省去很多工作,更加人性化的去使用DynaBean,
Lazy DynaBeans有如下特性:
1、自动添加bean属性,当我们调用set(name, value)方法时,如果属性不存在,会自动添加该属性;
2、List、Array属性自动扩容,
3、List、Array属性里的内部元素可以自动创建,实例化
4、Map属性也可以自动创建,实例化
5、...
简单的说,使用Lazy DynaBeans的话,你可以大胆调用DynaBean的set、get方法,而不必担心没有属性不存在,集合数组空间不够等问题,Lazy DynaBeans会帮我们自动处理;
如下是一个LazyDynaBean
例子:
DynaBean dynaBean = new LazyDynaBean(); dynaBean.set("foo", "bar"); // simple dynaBean.set("customer", "title", "Mr"); // mapped dynaBean.set("customer", "surname", "Smith"); // mapped dynaBean.set("users", 0, new User()); // indexed dynaBean.set("users", 1, new User()); // indexed dynaBean.set("users", 2, new User()); // indexed System.out.println(dynaBean.get("customer", "title"));
如下是一个LazyDynaMap
例子:
DynaBean dynaBean = new LazyDynaMap(); dynaBean.set("foo", "bar"); // simple dynaBean.set("customer", "title", "Mr"); // mapped dynaBean.set("customer", "surname", "Smith"); // mapped dynaBean.set("users", 0, new User()); // indexed dynaBean.set("users", 1, new User()); // indexed dynaBean.set("users", 2, new User()); // indexed System.out.println(dynaBean.get("customer", "title")); //转成Map对象 Map<String, Object> myMap = ((LazyDynaBean) dynaBean).getMap(); System.out.println(myMap);
LazyDynaList例子,详见API文档
LazyDynaList dynaBean = new LazyDynaList(); dynaBean.setElementType(User.class); User u = new User(); u.setName("hello"); dynaBean.add(1, u); System.out.println(dynaBean.size()); User[] users = (User[])dynaBean.toArray();//转化为数组 System.out.println(users[1].getName()); WrapDynaBean w = (WrapDynaBean) dynaBean.get(1); System.out.println(w.get("name"));
Lazy DynaBeans可以让我们不受控制的添加任意类型的bean属性,但是有时候,我们还是希望能控制某个bean属性的数据类型,如下,是一个示例:
MutableDynaClass dynaClass = new LazyDynaClass(); // create DynaClass dynaClass.add("amount", java.lang.Integer.class); // add property dynaClass.add("users", User[].class); // add indexed property dynaClass.add("orders", TreeMap.class); // add mapped property DynaBean dynaBean = new LazyDynaBean(dynaClass); dynaBean.set("amount_", "s"); dynaBean.set("amount", "s");//报错,需要为整型 dynaBean.set("users", 1);//报错,需要维数组 System.out.println(dynaBean.get("amount"));
参考资料
源码
https://github.com/peterchenhdu/apache-commons-beanutils-example