隨筆-14  評論-0  文章-6  trackbacks-0

          ? 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,以便于測試,代碼如下:
          package test.jakarta.commons.beanutils;

          /**
          * @author SonyMusic
          *
          */
          public class Month {
          private int value;
          private String name;
          private int[] days={11,22,33,44,55};

          public Month(int v, String n){
          value=v;
          name=n;
          }

          /**
          * Returns the name.
          * @return String
          */
          public String getName() {
          return name;
          }

          /**
          * Returns the value.
          * @return int
          */
          public int getValue() {
          return value;
          }

          /**
          * Sets the name.
          * @param name The name to set
          */
          public void setName(String name) {
          this.name = name;
          }

          /**
          * Sets the value.
          * @param value The value to set
          */
          public void setValue(int value) {
          this.value = value;
          }

          /**
          * @see java.lang.Object#toString()
          */
          public String toString() {
          return value+"("+name+")";
          }

          public int[] getDays() {
          return days;
          }

          public void setDays(int[] is) {
          days = is;
          }

          }

          三、BeanUtils
          這是一個主要應用于Bean的Util(呵呵,這個解釋很絕吧),以下是其中幾個方法的例子

          //static java.util.Map describe(java.lang.Object bean)
          //這個方法返回一個Object中所有的可讀屬性,并將屬性名/屬性值放入一個Map中,另外還有
          //一個名為class的屬性,屬性值是Object的類名,事實上class是java.lang.Object的一個屬性
          Month month=new Month(1, "Jan");

          try {
          Map map=BeanUtils.describe(month);
          Set keySet=map.keySet();
          for (Iterator iter = keySet.iterator(); iter.hasNext();) {
          Object element = (Object) iter.next();
          System.out.println("KeyClass:"+element.getClass().getName());
          System.out.println("ValueClass:"+map.get(element).getClass().getName());
          System.out.print(element+"\t");
          System.out.print(map.get(element));
          System.out.println();
          }
          } catch (IllegalAccessException e) {
          e.printStackTrace();
          } catch (InvocationTargetException e) {
          e.printStackTrace();
          } catch (NoSuchMethodException e) {
          e.printStackTrace();
          }
          輸出為:
          KeyClass:java.lang.String
          ValueClass:java.lang.String
          value 1
          KeyClass:java.lang.String
          ValueClass:java.lang.String
          class class test.jakarta.commons.beanutils.Month
          KeyClass:java.lang.String
          ValueClass:java.lang.String
          name Jan
          注意到所有Map中的key/value都是String,而不管object中實際的值是多少。
          與此對應的還有static void populate(java.lang.Object bean, java.util.Map properties)
          用于將剛才describe出的Map再裝配成一個對象。


          再看這樣一段代碼
          曹曉鋼也許還記得,為了取一個不確定對象的property,著實花了不少時間,
          難度不大,但要做到100%的正確,仍然需要付出很大的精力。
          //static java.lang.String getProperty(java.lang.Object bean, java.lang.String name)
          Month month=new Month(1, "Jan");

          try {
          System.out.println(BeanUtils.getProperty(month,"value"));
          } catch (Exception e) {
          e.printStackTrace();
          }
          //輸出是:1

          與getProperty類似的還有getIndexedProperty, getMappedProperty,
          以getIndexedProperty為例:
          Month month=new Month(1, "Jan");

          try {
          System.out.println(BeanUtils.getIndexedProperty(month,"days",1));
          System.out.println(BeanUtils.getIndexedProperty(month,"days[1]"));
          } catch (Exception e) {
          e.printStackTrace();
          }
          這兩個調用是相同的。


          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的用法,代碼如下:
          String driver="com.mysql.jdbc.Driver";
          String url="jdbc:mysql://localhost/2hu?useUnicode=true&characterEncoding=GBK";
          String username="root";
          String password="";

          java.sql.Connection con=null;
          PreparedStatement ps=null;
          ResultSet rs=null;
          try {
          Class.forName(driver).newInstance();
          con = DriverManager.getConnection(url);
          ps=con.prepareStatement("select * from forumlist");
          rs=ps.executeQuery();
          //先打印一下,用于檢驗后面的結果。
          while(rs.next()){
          System.out.println(rs.getString("name"));
          }
          rs.beforeFirst();//這里必須用beforeFirst,因為RowSetDynaClass只從當前位置向前滾動

          RowSetDynaClass rsdc = new RowSetDynaClass(rs);
          rs.close();
          ps.close();
          List rows = rsdc.getRows();//返回一個標準的List,存放的是DynaBean
          for (int i = 0; i <rows.size(); i++) {
          DynaBean b=(DynaBean)rows.get(i);
          System.out.println(b.get("name"));
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          finally{
          try {
          con.close();
          } catch (Exception e) {
          }
          }

          是不是很有趣?封裝了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中的代碼:
          ?? ArrayList results = new ArrayList(); // To hold copied list
          ?? ResultSetDynaClass rsdc = ...;
          ?? DynaProperty properties[] = rsdc.getDynaProperties();
          ?? BasicDynaClass bdc =
          ???? new BasicDynaClass("foo", BasicDynaBean.class,
          ??????????????????????? rsdc.getDynaProperties());
          ?? Iterator rows = rsdc.iterator();
          ?? while (rows.hasNext()) {
          ???? DynaBean oldRow = (DynaBean) rows.next();
          ???? DynaBean newRow = bdc.newInstance();
          ???? PropertyUtils.copyProperties(newRow, oldRow);
          ???? results.add(newRow);
          ?? }

          事實上DynaClass/DynaBean可以用于很多地方,存儲各種類型的數據。自己想吧。嘿嘿。


          九、自定義的CustomRowSetDynaClass
          兩年前寫過一個與RowSetDynaClass目標相同的類,不過多一個功能,就是分頁,只取需要的數據,
          這樣內存占用就會減少。

          先看一段代碼:
          String driver="com.mysql.jdbc.Driver";
          String url="jdbc:mysql://localhost/2hu?useUnicode=true&characterEncoding=GBK";
          String username="root";
          String password="";

          java.sql.Connection con=null;
          PreparedStatement ps=null;
          ResultSet rs=null;
          try {
          Class.forName(driver).newInstance();
          con = DriverManager.getConnection(url);
          ps=con.prepareStatement("select * from forumlist order by name");
          rs=ps.executeQuery();
          /*
          while(rs.next()){
          System.out.println(rs.getString("name"));
          }
          rs.beforeFirst();
          */

          //第二個參數表示第幾頁,第三個參數表示頁的大小
          CustomRowSetDynaClass rsdc = new CustomRowSetDynaClass(rs, 2, 5);
          //RowSetDynaClass rsdc = new RowSetDynaClass(rs);
          rs.close();
          ps.close();
          List rows = rsdc.getRows();
          for (int i = 0; i <rows.size(); i++) {
          DynaBean b=(DynaBean)rows.get(i);
          System.out.println(b.get("name"));
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          finally{
          try {
          con.close();
          } catch (Exception e) {
          }
          }
          在這里用到了一個CustomRowSetDynaClass類,構造方法中增加了page和pageSize兩個參數,
          這樣,不管數據庫里有多少條記錄,最多只取pageSize條記錄,若pageSize==-1,則功能和
          RowSetDynaClass一樣。這在大多數情況下是適用的。該類的代碼如下:

          package test.jakarta.commons.beanutils;

          import java.io.*;
          import java.sql.*;
          import java.util.*;

          import org.apache.commons.beanutils.*;

          /**
          * @author SonyMusic
          *
          * To change this generated comment edit the template variable "typecomment":
          * Window>Preferences>Java>Templates.
          * To enable and disable the creation of type comments go to
          * Window>Preferences>Java>Code Generation.
          */
          public class CustomRowSetDynaClass implements DynaClass, Serializable {

          // ----------------------------------------------------------- Constructors

          /**
          * <p>Construct a new {@link RowSetDynaClass} for the specified
          * <code>ResultSet</code>.? The property names corresponding
          * to column names in the result set will be lower cased.</p>
          *
          * @param resultSet The result set to be wrapped
          *
          * @exception NullPointerException if <code>resultSet</code>
          *? is <code>null</code>
          * @exception SQLException if the metadata for this result set
          *? cannot be introspected
          */
          public CustomRowSetDynaClass(ResultSet resultSet) throws SQLException {

          this(resultSet, true);

          }

          /**
          * <p>Construct a new {@link RowSetDynaClass} for the specified
          * <code>ResultSet</code>.? The property names corresponding
          * to the column names in the result set will be lower cased or not,
          * depending on the specified <code>lowerCase</code> value.</p>
          *
          * <p><strong>WARNING</strong> - If you specify <code>false</code>
          * for <code>lowerCase</code>, the returned property names will
          * exactly match the column names returned by your JDBC driver.
          * Because different drivers might return column names in different
          * cases, the property names seen by your application will vary
          * depending on which JDBC driver you are using.</p>
          *
          * @param resultSet The result set to be wrapped
          * @param lowerCase Should property names be lower cased?
          *
          * @exception NullPointerException if <code>resultSet</code>
          *? is <code>null</code>
          * @exception SQLException if the metadata for this result set
          *? cannot be introspected
          */
          public CustomRowSetDynaClass(ResultSet resultSet, boolean lowerCase)
          throws SQLException {

          this(resultSet, 1, -1, lowerCase);

          }

          public CustomRowSetDynaClass(
          ResultSet resultSet,
          int page,
          int pageSize,
          boolean lowerCase)
          throws SQLException {

          if (resultSet == null) {
          throw new NullPointerException();
          }
          this.lowerCase = lowerCase;
          this.page = page;
          this.pageSize = pageSize;

          introspect(resultSet);
          copy(resultSet);

          }

          public CustomRowSetDynaClass(ResultSet resultSet, int page, int pageSize)
          throws SQLException {
          this(resultSet, page, pageSize, true);
          }

          // ----------------------------------------------------- Instance Variables

          /**
          * <p>Flag defining whether column names should be lower cased when
          * converted to property names.</p>
          */
          protected boolean lowerCase = true;

          protected int page = 1;
          protected int pageSize = -1;

          /**
          * <p>The set of dynamic properties that are part of this
          * {@link DynaClass}.</p>
          */
          protected DynaProperty properties[] = null;

          /**
          * <p>The set of dynamic properties that are part of this
          * {@link DynaClass}, keyed by the property name.? Individual descriptor
          * instances will be the same instances as those in the
          * <code>properties</code> list.</p>
          */
          protected Map propertiesMap = new HashMap();

          /**
          * <p>The list of {@link DynaBean}s representing the contents of
          * the original <code>ResultSet</code> on which this
          * {@link RowSetDynaClass} was based.</p>
          */
          protected List rows = new ArrayList();

          // ------------------------------------------------------ DynaClass Methods

          /**
          * <p>Return the name of this DynaClass (analogous to the
          * <code>getName()</code> method of <code>java.lang.Class</code), which
          * allows the same <code>DynaClass</code> implementation class to support
          * different dynamic classes, with different sets of properties.</p>
          */
          public String getName() {

          return (this.getClass().getName());

          }

          /**
          * <p>Return a property descriptor for the specified property, if it
          * exists; otherwise, return <code>null</code>.</p>
          *
          * @param name Name of the dynamic property for which a descriptor
          *? is requested
          *
          * @exception IllegalArgumentException if no property name is specified
          */
          public DynaProperty getDynaProperty(String name) {

          if (name == null) {
          throw new IllegalArgumentException("No property name specified");
          }
          return ((DynaProperty) propertiesMap.get(name));

          }

          /**
          * <p>Return an array of <code>ProperyDescriptors</code> for the properties
          * currently defined in this DynaClass.? If no properties are defined, a
          * zero-length array will be returned.</p>
          */
          public DynaProperty[] getDynaProperties() {

          return (properties);

          }

          /**
          * <p>Instantiate and return a new DynaBean instance, associated
          * with this DynaClass.? <strong>NOTE</strong> - This operation is not
          * supported, and throws an exception.</p>
          *
          * @exception IllegalAccessException if the Class or the appropriate
          *? constructor is not accessible
          * @exception InstantiationException if this Class represents an abstract
          *? class, an array class, a primitive type, or void; or if instantiation
          *? fails for some other reason
          */
          public DynaBean newInstance()
          throws IllegalAccessException, InstantiationException {

          throw new UnsupportedOperationException("newInstance() not supported");

          }

          // --------------------------------------------------------- Public Methods

          /**
          * <p>Return a <code>List</code> containing the {@link DynaBean}s that
          * represent the contents of each <code>Row</code> from the
          * <code>ResultSet</code> that was the basis of this
          * {@link RowSetDynaClass} instance.? These {@link DynaBean}s are
          * disconnected from the database itself, so there is no problem with
          * modifying the contents of the list, or the values of the properties
          * of these {@link DynaBean}s.? However, it is the application's
          * responsibility to persist any such changes back to the database,
          * if it so desires.</p>
          */
          public List getRows() {

          return (this.rows);

          }

          // ------------------------------------------------------ Protected Methods

          /**
          * <p>Copy the column values for each row in the specified
          * <code>ResultSet</code> into a newly created {@link DynaBean}, and add
          * this bean to the list of {@link DynaBean}s that will later by
          * returned by a call to <code>getRows()</code>.</p>
          *
          * @param resultSet The <code>ResultSet</code> whose data is to be
          *? copied
          *
          * @exception SQLException if an error is encountered copying the data
          */
          protected void copy(ResultSet resultSet) throws SQLException {
          int abs = 0;
          int rowsCount = 0;
          int currentPageRows = 0;
          resultSet.last();
          rowsCount = resultSet.getRow();
          if (pageSize != -1) {
          int totalPages = (int) Math.ceil(((double) rowsCount) / pageSize);
          if (page > totalPages)
          page = totalPages;
          if (page < 1)
          page = 1;
          abs = (page - 1) * pageSize;

          //currentPageRows=(page==totalPages?rowsCount-pageSize*(totalPages-1):pageSize);
          } else
          pageSize = rowsCount;
          if (abs == 0)
          resultSet.beforeFirst();
          else
          resultSet.absolute(abs);
          //int
          while (resultSet.next() && ++currentPageRows <= pageSize) {
          DynaBean bean = new BasicDynaBean(this);
          for (int i = 0; i < properties.length; i++) {
          String name = properties[i].getName();
          bean.set(name, resultSet.getObject(name));
          }
          rows.add(bean);
          }

          }

          /**
          * <p>Introspect the metadata associated with our result set, and populate
          * the <code>properties</code> and <code>propertiesMap</code> instance
          * variables.</p>
          *
          * @param resultSet The <code>resultSet</code> whose metadata is to
          *? be introspected
          *
          * @exception SQLException if an error is encountered processing the
          *? result set metadata
          */
          protected void introspect(ResultSet resultSet) throws SQLException {

          // Accumulate an ordered list of DynaProperties
          ArrayList list = new ArrayList();
          ResultSetMetaData metadata = resultSet.getMetaData();
          int n = metadata.getColumnCount();
          for (int i = 1; i <= n; i++) { // JDBC is one-relative!
          DynaProperty dynaProperty = createDynaProperty(metadata, i);
          if (dynaProperty != null) {
          list.add(dynaProperty);
          }
          }

          // Convert this list into the internal data structures we need
          properties =
          (DynaProperty[]) list.toArray(new DynaProperty[list.size()]);
          for (int i = 0; i < properties.length; i++) {
          propertiesMap.put(properties[i].getName(), properties[i]);
          }

          }

          /**
          * <p>Factory method to create a new DynaProperty for the given index
          * into the result set metadata.</p>
          *
          * @param metadata is the result set metadata
          * @param i is the column index in the metadata
          * @return the newly created DynaProperty instance
          */
          protected DynaProperty createDynaProperty(
          ResultSetMetaData metadata,
          int i)
          throws SQLException {

          String name = null;
          if (lowerCase) {
          name = metadata.getColumnName(i).toLowerCase();
          } else {
          name = metadata.getColumnName(i);
          }
          String className = null;
          try {
          className = metadata.getColumnClassName(i);
          } catch (SQLException e) {
          // this is a patch for HsqlDb to ignore exceptions
          // thrown by its metadata implementation
          }

          // Default to Object type if no class name could be retrieved
          // from the metadata
          Class clazz = Object.class;
          if (className != null) {
          clazz = loadClass(className);
          }
          return new DynaProperty(name, clazz);

          }

          /**
          * <p>Loads and returns the <code>Class</code> of the given name.
          * By default, a load from the thread context class loader is attempted.
          * If there is no such class loader, the class loader used to load this
          * class will be utilized.</p>
          *
          * @exception SQLException if an exception was thrown trying to load
          *? the specified class
          */
          protected Class loadClass(String className) throws SQLException {

          try {
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          if (cl == null) {
          cl = this.getClass().getClassLoader();
          }
          return (cl.loadClass(className));
          } catch (Exception e) {
          throw new SQLException(
          "Cannot load column class '" + className + "': " + e);
          }

          }

          }

          posted on 2006-04-07 16:47 高遠 閱讀(213) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 舟山市| 且末县| 周宁县| 蚌埠市| 鄂托克旗| 澜沧| 洪洞县| 习水县| 库尔勒市| 方城县| 滕州市| 卢氏县| 江永县| 双流县| 贺州市| 宜宾市| 兖州市| 东源县| 瑞昌市| 新沂市| 贵定县| 余姚市| 哈密市| 班戈县| 香港 | 宝清县| 延边| 湾仔区| 鹤壁市| 额尔古纳市| 奈曼旗| 蓝田县| 庆元县| 孝感市| 鄯善县| 顺平县| 绥芬河市| 丰城市| 衡东县| 陇西县| 五原县|