Jakarta-Common-BeanUtils研究心得

 一、概述
第一次看到BeanUtils包,是在Struts项目中,作为Struts一个工具来使用的,
估计功能越弄越强,就移到Common项目中了吧。

BeanUtils一共有四个package:
org.apache.commons.beanutils
org.apache.commons.beanutils.converters
org.apache.commons.beanutils.locale
org.apache.commons.beanutils.locale.converters
后三个包主要是用于数据的转换,围绕着一个Converter接口,该接口只有一个方法:
java.lang.Object convert(java.lang.Class type, java.lang.Object value) ,
用于将一个value转换成另一个类型为type的Object。在一些自动化的应用中应该会有用。
这里不作评论,以后有兴趣了,或者觉得有用了,再行研究。
这里只讲第一个包。

二、测试用的Bean
在开始所有的测试之前,我写了一个简单的Bean,以便于测试,代码如下:
java 代码
 
  1. package test.jakarta.commons.beanutils;  
  2.   
  3. /** 
  4. * @author SonyMusic 
  5. * 
  6. */  
  7. public class Month {  
  8. private int value;  
  9. private String name;  
  10. private int[] days={11,22,33,44,55};  
  11.   
  12. public Month(int v, String n){  
  13. value=v;  
  14. name=n;  
  15. }  
  16.   
  17. /** 
  18. * Returns the name. 
  19. * @return String 
  20. */  
  21. public String getName() {  
  22. return name;  
  23. }  
  24.   
  25. /** 
  26. * Returns the value. 
  27. * @return int 
  28. */  
  29. public int getValue() {  
  30. return value;  
  31. }  
  32.   
  33. /** 
  34. * Sets the name. 
  35. * @param name The name to set 
  36. */  
  37. public void setName(String name) {  
  38. this.name = name;  
  39. }  
  40.   
  41. /** 
  42. * Sets the value. 
  43. * @param value The value to set 
  44. */  
  45. public void setValue(int value) {  
  46. this.value = value;  
  47. }  
  48.   
  49. /** 
  50. * @see java.lang.Object#toString() 
  51. */  
  52. public String toString() {  
  53. return value+"("+name+")";  
  54. }  
  55.   
  56. public int[] getDays() {  
  57. return days;  
  58. }  
  59.   
  60. public void setDays(int[] is) {  
  61. days = is;  
  62. }  
  63.   
  64. }  
三、BeanUtils
这是一个主要应用于Bean的Util(呵呵,这个解释很绝吧),以下是其中几个方法的例子

//static java.util.Map describe(java.lang.Object bean)
//这个方法返回一个Object中所有的可读属性,并将属性名/属性值放入一个Map中,另外还有
//一个名为class的属性,属性值是Object的类名,事实上class是java.lang.Object的一个属性
java 代码
 
  1. Month month=new Month(1"Jan");  
  2.   
  3. try {  
  4. Map map=BeanUtils.describe(month);  
  5. Set keySet=map.keySet();  
  6. for (Iterator iter = keySet.iterator(); iter.hasNext();) {  
  7. Object element = (Object) iter.next();  
  8. System.out.println("KeyClass:"+element.getClass().getName());  
  9. System.out.println("ValueClass:"+map.get(element).getClass().getName());  
  10. System.out.print(element+"\t");  
  11. System.out.print(map.get(element));  
  12. System.out.println();  
  13. }  
  14. catch (IllegalAccessException e) {  
  15. e.printStackTrace();  
  16. catch (InvocationTargetException e) {  
  17. e.printStackTrace();  
  18. catch (NoSuchMethodException e) {  
  19. e.printStackTrace();  
  20. }  
  21. 输出为:  
  22. KeyClass:java.lang.String  
  23. ValueClass:java.lang.String  
  24. value 1  
  25. KeyClass:java.lang.String  
  26. ValueClass:java.lang.String  
  27. class class test.jakarta.commons.beanutils.Month  
  28. KeyClass:java.lang.String  
  29. ValueClass:java.lang.String  
  30. name Jan  
注意到所有Map中的key/value都是String,而不管object中实际的值是多少。
与此对应的还有static void populate(java.lang.Object bean, java.util.Map properties)
用于将刚才describe出的Map再装配成一个对象。


再看这样一段代码
曹晓钢也许还记得,为了取一个不确定对象的property,着实花了不少时间,
难度不大,但要做到100%的正确,仍然需要付出很大的精力。
java 代码
 
  1. //static java.lang.String getProperty(java.lang.Object bean, java.lang.String name)  
  2. Month month=new Month(1"Jan");  
  3.   
  4. try {  
  5. System.out.println(BeanUtils.getProperty(month,"value"));  
  6. catch (Exception e) {  
  7. e.printStackTrace();  
  8. }  
  9. //输出是:1  
  10.   
  11. 与getProperty类似的还有getIndexedProperty, getMappedProperty,  
  12. 以getIndexedProperty为例:  
  13. Month month=new Month(1"Jan");  
  14.   
  15. try {  
  16. System.out.println(BeanUtils.getIndexedProperty(month,"days",1));  
  17. System.out.println(BeanUtils.getIndexedProperty(month,"days[1]"));  
  18. catch (Exception e) {  
  19. e.printStackTrace();  
  20. }  
  21. 这两个调用是相同的。  

BeanUtils中还有一个方法:
static void copyProperties(java.lang.Object dest, java.lang.Object orig)
它真是太有用,还记得struts中满天飞的都是copyProperties,我甚至怀疑整个BeanUtils最初
是不是因为这个方法的需求才写出来的。
它将对象orig中的属性复制到dest中去。


四、PropertyUtils
这个类和BeanUtils类很多的方法在参数上都是相同的,但返回值不同。
BeanUtils着重于"Bean",返回值通常是String,而PropertyUtils着重于属性,
它的返回值通常是Object。


五、ConstructorUtils
这个类中的方法主要分成两种,一种是得到构造方法,一种是创建对象。
事实上多数时候得到构造方法的目的就是创建对象,这里只介绍一下创建对象。
//static java.lang.Object ConstructorUtils.invokeConstructor
//(java.lang.Class klass, java.lang.Object[] args)
//根据一个java.lang.Class以及相应的构造方法的参数,创建一个对象。
Object obj=ConstructorUtils.invokeConstructor(Month.class, {new Integer(1), "Jan"});
Month month=(Month)obj;
try {
System.out.println(BeanUtils.getProperty(month,"value"));
} catch (Exception e) {
e.printStackTrace();
}
输出证明,构造方法的调用是成功的。
如果需要强制指定构造方法的参数类型,可以这样调用:
Object[] args={new Integer(1), "Jan"};
Class[] argsType={int.class, String.class};
Object obj;
obj = ConstructorUtils.invokeExactConstructor(Month.class, args, argsType);
Month month=(Month)obj;
System.out.println(BeanUtils.getProperty(month,"value"));
argsType指定了参数的类型。
六、ConstructorUtils补遗
创建对象还有一个方法:invokeExactConstructor,该方法对参数要求
更加严格,传递进去的参数必须严格符合构造方法的参数列表。
例如:
Object[] args={new Integer(1), "Jan"};
Class[] argsType={int.class, String.class};
Object obj;
//下面这句调用将不会成功,因为args[0]的类型为Integer,而不是int
//obj = ConstructorUtils.invokeExactConstructor(Month.class, args);

//这一句就可以,因为argsType指定了类型。
obj = ConstructorUtils.invokeExactConstructor(Month.class, args, argsType);
Month month=(Month)obj;
System.out.println(BeanUtils.getProperty(month,"value"));


七、MethodUtils
与ConstructorUtils类似,不过调用的时候,通常需要再指定一个method name的参数。

八、DynaClass/DynaBean
这似乎是BeanUtils中最有趣的部分之一了,很简单,简单到光看这两个接口中的方法会不明白
为什么要设计这两个接口。不过看到ResultSetDynaClass后,就明白了。下面是java doc中的代码:
   ResultSet rs = ...;
   ResultSetDynaClass rsdc = new ResultSetDynaClass(rs);
   Iterator rows = rsdc.iterator();
   while (rows.hasNext())  {
     DynaBean row = (DynaBean) rows.next();
     ... process this row ...
   }
   rs.close();
原来这是一个ResultSet的包装器,ResultSetDynaClass实现了DynaClass,它的iterator方法返回一个
ResultSetIterator,则是实现了DynaBean接口。
在获得一个DynaBean之后,我们就可以用
     DynaBean row = (DynaBean) rows.next();
     System.out.println(row.get("field1")); //field1是其中一个字段的名字

再看另一个类RowSetDynaClass的用法,代码如下:
java 代码
 
  1. String driver="com.mysql.jdbc.Driver";  
  2. String url="jdbc:mysql://localhost/2hu?useUnicode=true&characterEncoding=GBK";  
  3. String username="root";  
  4. String password="";  
  5.   
  6. java.sql.Connection con=null;  
  7. PreparedStatement ps=null;  
  8. ResultSet rs=null;  
  9. try {  
  10. Class.forName(driver).newInstance();  
  11. con = DriverManager.getConnection(url);  
  12. ps=con.prepareStatement("select * from forumlist");  
  13. rs=ps.executeQuery();  
  14. //先打印一下,用于检验后面的结果。  
  15. while(rs.next()){  
  16. System.out.println(rs.getString("name"));  
  17. }  
  18. rs.beforeFirst();//这里必须用beforeFirst,因为RowSetDynaClass只从当前位置向前滚动  
  19.   
  20. RowSetDynaClass rsdc = new RowSetDynaClass(rs);  
  21. rs.close();  
  22. ps.close();  
  23. List rows = rsdc.getRows();//返回一个标准的List,存放的是DynaBean  
  24. for (int i = 0; i <rows.size(); i++) {  
  25. DynaBean b=(DynaBean)rows.get(i);  
  26. System.out.println(b.get("name"));  
  27. }  
  28. catch (Exception e) {  
  29. e.printStackTrace();  
  30. }  
  31. finally{  
  32. try {  
  33. con.close();  
  34. catch (Exception e) {  
  35. }  
  36. }  
是不是很有趣?封装了ResultSet的数据,代价是占用内存。如果一个表有10万条记录,rsdc.getRows()
就会返回10万个记录。@_@

需要注意的是ResultSetDynaClass和RowSetDynaClass的不同之处:
1,ResultSetDynaClass是基于Iterator的,一次只返回一条记录,而RowSetDynaClass是基于
List的,一次性返回全部记录。直接影响是在数据比较多时ResultSetDynaClass会比较的快速,
而RowSetDynaClass需要将ResultSet中的全部数据都读出来(并存储在其内部),会占用过多的
内存,并且速度也会比较慢。
2,ResultSetDynaClass一次只处理一条记录,在处理完成之前,ResultSet不可以关闭。
3,ResultSetIterator的next()方法返回的DynaBean其实是指向其内部的一个固定
对象,在每次next()之后,内部的值都会被改变。这样做的目的是节约内存,如果你需要保存每
次生成的DynaBean,就需要创建另一个DynaBean,并将数据复制过去,下面也是java doc中的代码:
java 代码
 
  1. ArrayList results = new ArrayList(); // To hold copied list  
  2.   ResultSetDynaClass rsdc = ...;  
  3.   DynaProperty properties[] = rsdc.getDynaProperties();  
  4.   BasicDynaClass bdc =  
  5.     new BasicDynaClass("foo", BasicDynaBean.class,  
  6.                        rsdc.getDynaProperties());  
  7.   Iterator rows = rsdc.iterator();  
  8.   while (rows.hasNext()) {  
  9.     DynaBean oldRow = (DynaBean) rows.next();  
  10.     DynaBean newRow = bdc.newInstance();  
  11.     PropertyUtils.copyProperties(newRow, oldRow);  
  12.     results.add(newRow);  
  13.   }  
事实上DynaClass/DynaBean可以用于很多地方,存储各种类型的数据。自己想吧。嘿嘿。
九、自定义的CustomRowSetDynaClass
两年前写过一个与RowSetDynaClass目标相同的类,不过多一个功能,就是分页,只取需要的数据,
这样内存占用就会减少。

先看一段代码:
java 代码
 
  1. String driver="com.mysql.jdbc.Driver";  
  2. String url="jdbc:mysql://localhost/2hu?useUnicode=true&characterEncoding=GBK";  
  3. String username="root";  
  4. String password="";  
  5.   
  6. java.sql.Connection con=null;  
  7. PreparedStatement ps=null;  
  8. ResultSet rs=null;  
  9. try {  
  10. Class.forName(driver).newInstance();  
  11. con = DriverManager.getConnection(url);  
  12. ps=con.prepareStatement("select * from forumlist order by name");  
  13. rs=ps.executeQuery();  
  14. /* 
  15. while(rs.next()){ 
  16. System.out.println(rs.getString("name")); 
  17. } 
  18. rs.beforeFirst(); 
  19. */  
  20.   
  21. //第二个参数表示第几页,第三个参数表示页的大小  
  22. CustomRowSetDynaClass rsdc = new CustomRowSetDynaClass(rs, 25);  
  23. //RowSetDynaClass rsdc = new RowSetDynaClass(rs);  
  24. rs.close();  
  25. ps.close();  
  26. List rows = rsdc.getRows();  
  27. for (int i = 0; i <rows.size(); i++) {  
  28. DynaBean b=(DynaBean)rows.get(i);  
  29. System.out.println(b.get("name"));  
  30. }  
  31. catch (Exception e) {  
  32. e.printStackTrace();  
  33. }  
  34. finally{  
  35. try {  
  36. con.close();  
  37. catch (Exception e) {  
  38. }  
  39. }  
在这里用到了一个CustomRowSetDynaClass类,构造方法中增加了page和pageSize两个参数,
这样,不管数据库里有多少条记录,最多只取pageSize条记录,若pageSize==-1,则功能和
RowSetDynaClass一样。这在大多数情况下是适用的。该类的代码如下:
java 代码
 
  1. package test.jakarta.commons.beanutils;  
  2.   
  3. import java.io.*;  
  4. import java.sql.*;  
  5. import java.util.*;  
  6.   
  7. import org.apache.commons.beanutils.*;  
  8.   
  9. /** 
  10. * @author SonyMusic 
  11. * 
  12. * To change this generated comment edit the template variable "typecomment": 
  13. * Window>Preferences>Java>Templates. 
  14. * To enable and disable the creation of type comments go to 
  15. * Window>Preferences>Java>Code Generation. 
  16. */  
  17. public class CustomRowSetDynaClass implements DynaClass, Serializable {  
  18.   
  19. // ----------------------------------------------------------- Constructors  
  20.   
  21. /** 
  22. * <p>Construct a new {@link RowSetDynaClass} for the specified 
  23. * <code>ResultSet</code>.  The property names corresponding 
  24. * to column names in the result set will be lower cased.</p> 
  25. * 
  26. * @param resultSet The result set to be wrapped 
  27. * 
  28. * @exception NullPointerException if <code>resultSet</code> 
  29. *  is <code>null</code> 
  30. * @exception SQLException if the metadata for this result set 
  31. *  cannot be introspected 
  32. */  
  33. public CustomRowSetDynaClass(ResultSet resultSet) throws SQLException {  
  34.   
  35. this(resultSet, true);  
  36.   
  37. }  
  38.   
  39. /** 
  40. * <p>Construct a new {@link RowSetDynaClass} for the specified 
  41. * <code>ResultSet</code>.  The property names corresponding 
  42. * to the column names in the result set will be lower cased or not, 
  43. * depending on the specified <code>lowerCase</code> value.</p> 
  44. * 
  45. * <p><strong>WARNING</strong> - If you specify <code>false</code> 
  46. * for <code>lowerCase</code>, the returned property names will 
  47. * exactly match the column names returned by your JDBC driver. 
  48. * Because different drivers might return column names in different 
  49. * cases, the property names seen by your application will vary 
  50. * depending on which JDBC driver you are using.</p> 
  51. * 
  52. * @param resultSet The result set to be wrapped 
  53. * @param lowerCase Should property names be lower cased? 
  54. * 
  55. * @exception NullPointerException if <code>resultSet</code> 
  56. *  is <code>null</code> 
  57. * @exception SQLException if the metadata for this result set 
  58. *  cannot be introspected 
  59. */  
  60. public CustomRowSetDynaClass(ResultSet resultSet, boolean lowerCase)  
  61. throws SQLException {  
  62.   
  63. this(resultSet, 1, -1, lowerCase);  
  64.   
  65. }  
  66.   
  67. public CustomRowSetDynaClass(  
  68. ResultSet resultSet,  
  69. int page,  
  70. int pageSize,  
  71. boolean lowerCase)  
  72. throws SQLException {  
  73.   
  74. if (resultSet == null) {  
  75. throw new NullPointerException();  
  76. }  
  77. this.lowerCase = lowerCase;  
  78. this.page = page;  
  79. this.pageSize = pageSize;  
  80.   
  81. introspect(resultSet);  
  82. copy(resultSet);  
  83.   
  84. }  
  85.   
  86. public CustomRowSetDynaClass(ResultSet resultSet, int page, int pageSize)  
  87. throws SQLException {  
  88. this(resultSet, page, pageSize, true);  
  89. }  
  90.   
  91. // ----------------------------------------------------- Instance Variables  
  92.   
  93. /** 
  94. * <p>Flag defining whether column names should be lower cased when 
  95. * converted to property names.</p> 
  96. */  
  97. protected boolean lowerCase = true;  
  98.   
  99. protected int page = 1;  
  100. protected int pageSize = -1;  
  101.   
  102. /** 
  103. * <p>The set of dynamic properties that are part of this 
  104. * {@link DynaClass}.</p> 
  105. */  
  106. protected DynaProperty properties[] = null;  
  107.   
  108. /** 
  109. * <p>The set of dynamic properties that are part of this 
  110. * {@link DynaClass}, keyed by the property name.  Individual descriptor 
  111. * instances will be the same instances as those in the 
  112. * <code>properties</code> list.</p> 
  113. */  
  114. protected Map propertiesMap = new HashMap();  
  115.   
  116. /** 
  117. * <p>The list of {@link DynaBean}s representing the contents of 
  118. * the original <code>ResultSet</code> on which this 
  119. * {@link RowSetDynaClass} was based.</p> 
  120. */  
  121. protected List rows = new ArrayList();  
  122.   
  123. // ------------------------------------------------------ DynaClass Methods  
  124.   
  125. /** 
  126. * <p>Return the name of this DynaClass (analogous to the 
  127. * <code>getName()</code> method of <code>java.lang.Class</code), which 
  128. * allows the same <code>DynaClass</code> implementation class to support 
  129. * different dynamic classes, with different sets of properties.</p> 
  130. */  
  131. public String getName() {  
  132.   
  133. return (this.getClass().getName());  
  134.   
  135. }  
  136.   
  137. /** 
  138. * <p>Return a property descriptor for the specified property, if it 
  139. * exists; otherwise, return <code>null</code>.</p> 
  140. * 
  141. * @param name Name of the dynamic property for which a descriptor 
  142. *  is requested 
  143. * 
  144. * @exception IllegalArgumentException if no property name is specified 
  145. */  
  146. public DynaProperty getDynaProperty(String name) {  
  147.   
  148. if (name == null) {  
  149. throw new IllegalArgumentException("No property name specified");  
  150. }  
  151. return ((DynaProperty) propertiesMap.get(name));  
  152.   
  153. }  
  154.   
  155. /** 
  156. * <p>Return an array of <code>ProperyDescriptors</code> for the properties 
  157. * currently defined in this DynaClass.  If no properties are defined, a 
  158. * zero-length array will be returned.</p> 
  159. */  
  160. public DynaProperty[] getDynaProperties() {  
  161.   
  162. return (properties);  
  163.   
  164. }  
  165.   
  166. /** 
  167. * <p>Instantiate and return a new DynaBean instance, associated 
  168. * with this DynaClass.  <strong>NOTE</strong> - This operation is not 
  169. * supported, and throws an exception.</p> 
  170. * 
  171. * @exception IllegalAccessException if the Class or the appropriate 
  172. *  constructor is not accessible 
  173. * @exception InstantiationException if this Class represents an abstract 
  174. *  class, an array class, a primitive type, or void; or if instantiation 
  175. *  fails for some other reason 
  176. */  
  177. public DynaBean newInstance()  
  178. throws IllegalAccessException, InstantiationException {  
  179.   
  180. throw new UnsupportedOperationException("newInstance() not supported");  
  181.   
  182. }  
  183.   
  184. // --------------------------------------------------------- Public Methods  
  185.   
  186. /** 
  187. * <p>Return a <code>List</code> containing the {@link DynaBean}s that 
  188. * represent the contents of each <code>Row</code> from the 
  189. * <code>ResultSet</code> that was the basis of this 
  190. * {@link RowSetDynaClass} instance.  These {@link DynaBean}s are 
  191. * disconnected from the database itself, so there is no problem with 
  192. * modifying the contents of the list, or the values of the properties 
  193. * of these {@link DynaBean}s.  However, it is the application's 
  194. * responsibility to persist any such changes back to the database, 
  195. * if it so desires.</p> 
  196. */  
  197. public List getRows() {  
  198.   
  199. return (this.rows);  
  200.   
  201. }  
  202.   
  203. // ------------------------------------------------------ Protected Methods  
  204.   
  205. /** 
  206. * <p>Copy the column values for each row in the specified 
  207. * <code>ResultSet</code> into a newly created {@link DynaBean}, and add 
  208. * this bean to the list of {@link DynaBean}s that will later by 
  209. * returned by a call to <code>getRows()</code>.</p> 
  210. * 
  211. * @param resultSet The <code>ResultSet</code> whose data is to be 
  212. *  copied 
  213. * 
  214. * @exception SQLException if an error is encountered copying the data 
  215. */  
  216. protected void copy(ResultSet resultSet) throws SQLException {  
  217. int abs = 0;  
  218. int rowsCount = 0;  
  219. int currentPageRows = 0;  
  220. resultSet.last();  
  221. rowsCount = resultSet.getRow();  
  222. if (pageSize != -1) {  
  223. int totalPages = (int) Math.ceil(((double) rowsCount) / pageSize);  
  224. if (page > totalPages)  
  225. page = totalPages;  
  226. if (page < 1)  
  227. page = 1;  
  228. abs = (page - 1) * pageSize;  
  229.   
  230. //currentPageRows=(page==totalPages?rowsCount-pageSize*(totalPages-1):pageSize);  
  231. else  
  232. pageSize = rowsCount;  
  233. if (abs == 0)  
  234. resultSet.beforeFirst();  
  235. else  
  236. resultSet.absolute(abs);  
  237. //int  
  238. while (resultSet.next() && ++currentPageRows <= pageSize) {  
  239. DynaBean bean = new BasicDynaBean(this);  
  240. for (int i = 0; i < properties.length; i++) {  
  241. String name = properties[i].getName();  
  242. bean.set(name, resultSet.getObject(name));  
  243. }  
  244. rows.add(bean);  
  245. }  
  246.   
  247. }  
  248.   
  249. /** 
  250. * <p>Introspect the metadata associated with our result set, and populate 
  251. * the <code>properties</code> and <code>propertiesMap</code> instance 
  252. * variables.</p> 
  253. * 
  254. * @param resultSet The <code>resultSet</code> whose metadata is to 
  255. *  be introspected 
  256. * 
  257. * @exception SQLException if an error is encountered processing the 
  258. *  result set metadata 
  259. */  
  260. protected void introspect(ResultSet resultSet) throws SQLException {  
  261.   
  262. // Accumulate an ordered list of DynaProperties  
  263. ArrayList list = new ArrayList();  
  264. ResultSetMetaData metadata = resultSet.getMetaData();  
  265. int n = metadata.getColumnCount();  
  266. for (int i = 1; i <= n; i++) { // JDBC is one-relative!  
  267. DynaProperty dynaProperty = createDynaProperty(metadata, i);  
  268. if (dynaProperty != null) {  
  269. list.add(dynaProperty);  
  270. }  
  271. }  
  272.   

你可能感兴趣的:(apache,bean,mysql,struts,jdbc)