相關背景
上一篇介紹了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