Apache Jakarta Commons項(xiàng)目非常有用,。我曾在許多不同的項(xiàng)目上或直接或間接地使用各種流行的commons組件,。其中的一個(gè)強(qiáng)大的組件就是BeanUtils。我將說明如何使用BeanUtils將local實(shí)體bean轉(zhuǎn)換為對(duì)應(yīng)的value 對(duì)象:
上面的代碼從aLocal對(duì)象復(fù)制屬性到aValue對(duì)象,。它相當(dāng)簡(jiǎn)單,!它不管local(或?qū)?yīng)的value)對(duì)象有多少個(gè)屬性,只管進(jìn)行復(fù)制,。我們假設(shè)local對(duì)象有100個(gè)屬性,。上面的代碼使我們可以無需鍵入至少100行的冗長(zhǎng)、容易出錯(cuò)和反復(fù)的get和set方法調(diào)用,。這太棒了,!太強(qiáng)大了!太有用了,! 現(xiàn)在,,還有一個(gè)壞消息:使用BeanUtils的成本驚人地昂貴!我做了一個(gè)簡(jiǎn)單的測(cè)試,,BeanUtils所花費(fèi)的時(shí)間要超過取數(shù)據(jù),、將其復(fù)制到對(duì)應(yīng)的value對(duì)象(通過手動(dòng)調(diào)用get和set方法),以及通過串行化將其返回到遠(yuǎn)程的客戶機(jī)的時(shí)間總和,。所以要小心使用這種威力,!
Beanutils用了魔術(shù)般的反射技術(shù),,實(shí)現(xiàn)了很多夸張有用的功能,都是C/C++時(shí)代不敢想的,。無論誰的項(xiàng)目,,始終一天都會(huì)用得上它。我算是后知后覺了,,第一回看到它的時(shí)候居然錯(cuò)過,。 1.屬性的動(dòng)態(tài)getter,setter
在這框架滿天飛的年代,不能事事都保證執(zhí)行g(shù)etter,setter函數(shù)了,,有時(shí)候?qū)傩允且枰鶕?jù)名字動(dòng)態(tài)取得的,,就像這樣:
BeanUtils.getProperty(myBean,"code");
而BeanUtils更強(qiáng)的功能是直接訪問內(nèi)嵌對(duì)象的屬性,只要使用點(diǎn)號(hào)分隔,。
BeanUtils.getProperty(orderBean, "address.city");
相比之下其他類庫的BeanUtils通常都很簡(jiǎn)單,,不能訪問內(nèi)嵌的對(duì)象,所以經(jīng)常要用Commons
BeanUtils替換它們。
BeanUtils還支持List和Map類型的屬性,。如下面的語法即可取得顧客列表中第一個(gè)顧客的名字 BeanUtils.getProperty(orderBean, "customers[1].name");
其中BeanUtils會(huì)使用ConvertUtils類把字符串轉(zhuǎn)為Bean屬性的真正類型,,方便從HttpServletRequest等對(duì)象中提取bean,或者把bean輸出到頁面,。
而PropertyUtils就會(huì)原色的保留Bean原來的類型,。
2.beanCompartor 動(dòng)態(tài)排序
還是通過反射,動(dòng)態(tài)設(shè)定Bean按照哪個(gè)屬性來排序,,而不再需要在bean的Compare接口進(jìn)行復(fù)雜的條件判斷,。
List peoples = ...; // Person對(duì)象的列表 Collections.sort(peoples, new BeanComparator("age")); 如果要支持多個(gè)屬性的復(fù)合排序,如"Order By lastName,firstName" ArrayList sortFields = new ArrayList(); sortFields.add(new BeanComparator("lastName")); sortFields.add(new BeanComparator("firstName")); ComparatorChain multiSort = new ComparatorChain(sortFields); Collections.sort(rows,multiSort);
其中ComparatorChain屬于jakata commons-collections包,。
3.Converter 把Request或ResultSet中的字符串綁定到對(duì)象的屬性
String a = request.getParameter("a"); bean.setA(a); String b = .... 不妨寫一個(gè)Binder: MyBean bean = ...; HashMap map = new HashMap(); Enumeration names = request.getParameterNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); map.put(name, request.getParameterValues(name)); } BeanUtils.populate(bean, map);
ConvertUtilsBean convertUtils = new ConvertUtilsBean(); 4 其他功能
4.1 PropertyUtils,,當(dāng)屬性為Collection,Map時(shí)的動(dòng)態(tài)讀取:
Collection: 提供index
或者 Map: 提供Key Value 或者 4.2 PropertyUtils,,獲取屬性的Class類型
4.3 ConstructorUtils,,動(dòng)態(tài)創(chuàng)建對(duì)象
4.4 MethodUtils,動(dòng)態(tài)調(diào)用方法 MethodUtils.invokeMethod(bean, methodName, parameter);4.5 動(dòng)態(tài)Bean
一,、概述
第一次看到BeanUtils包,,是在Struts項(xiàng)目中,作為Struts一個(gè)工具來使用的,, 估計(jì)功能越弄越強(qiáng),就移到Common項(xiàng)目中了吧,。 BeanUtils一共有四個(gè)package: org.apache.commons.beanutils org.apache.commons.beanutils.converters org.apache.commons.beanutils.locale org.apache.commons.beanutils.locale.converters 后三個(gè)包主要是用于數(shù)據(jù)的轉(zhuǎn)換,,圍繞著一個(gè)Converter接口,該接口只有一個(gè)方法: java.lang.Object convert(java.lang.Class type, java.lang.Object value) , 用于將一個(gè)value轉(zhuǎn)換成另一個(gè)類型為type的Object,。在一些自動(dòng)化的應(yīng)用中應(yīng)該會(huì)有用,。 這里不作評(píng)論,以后有興趣了,,或者覺得有用了,,再行研究。 這里只講第一個(gè)包,。 二,、測(cè)試用的Bean 在開始所有的測(cè)試之前,我寫了一個(gè)簡(jiǎn)單的Bean,,以便于測(cè)試,,代碼如下: package test.jakarta.commons.beanutils; public class Month { } 三、BeanUtils 這是一個(gè)主要應(yīng)用于Bean的Util(呵呵,,這個(gè)解釋很絕吧),,以下是其中幾個(gè)方法的例子 //static java.util.Map describe(java.lang.Object bean) //這個(gè)方法返回一個(gè)Object中所有的可讀屬性,并將屬性名/屬性值放入一個(gè)Map中,,另外還有 //一個(gè)名為class的屬性,,屬性值是Object的類名,事實(shí)上class是java.lang.Object的一個(gè)屬性 輸出為: KeyClass:java.lang.String ValueClass:java.lang.String value KeyClass:java.lang.String ValueClass:java.lang.String class KeyClass:java.lang.String ValueClass:java.lang.String name 注意到所有Map中的key/value都是String,,而不管object中實(shí)際的值是多少,。 與此對(duì)應(yīng)的還有static void populate(java.lang.Object bean, java.util.Map properties) 用于將剛才describe出的Map再裝配成一個(gè)對(duì)象。 再看這樣一段代碼 曹曉鋼也許還記得,,為了取一個(gè)不確定對(duì)象的property,,著實(shí)花了不少時(shí)間, 難度不大,,但要做到100%的正確,,仍然需要付出很大的精力。 //static java.lang.String getProperty(java.lang.Object bean, java.lang.String name) //輸出是:1 與getProperty類似的還有g(shù)etIndexedProperty, getMappedProperty,, 以getIndexedProperty為例: 這兩個(gè)調(diào)用是相同的,。 BeanUtils中還有一個(gè)方法: static void copyProperties(java.lang.Object dest, java.lang.Object orig) 它真是太有用,還記得struts中滿天飛的都是copyProperties,,我甚至懷疑整個(gè)BeanUtils最初 是不是因?yàn)檫@個(gè)方法的需求才寫出來的,。 它將對(duì)象orig中的屬性復(fù)制到dest中去。 四,、PropertyUtils 這個(gè)類和BeanUtils類很多的方法在參數(shù)上都是相同的,,但返回值不同。 BeanUtils著重于"Bean",,返回值通常是String,,而PropertyUtils著重于屬性,, 它的返回值通常是Object。 五,、ConstructorUtils 這個(gè)類中的方法主要分成兩種,,一種是得到構(gòu)造方法,一種是創(chuàng)建對(duì)象,。 事實(shí)上多數(shù)時(shí)候得到構(gòu)造方法的目的就是創(chuàng)建對(duì)象,,這里只介紹一下創(chuàng)建對(duì)象。 //static java.lang.Object ConstructorUtils.invokeConstructor //(java.lang.Class klass, java.lang.Object[] args) //根據(jù)一個(gè)java.lang.Class以及相應(yīng)的構(gòu)造方法的參數(shù),,創(chuàng)建一個(gè)對(duì)象,。 輸出證明,構(gòu)造方法的調(diào)用是成功的,。 如果需要強(qiáng)制指定構(gòu)造方法的參數(shù)類型,,可以這樣調(diào)用: argsType指定了參數(shù)的類型。
六,、ConstructorUtils補(bǔ)遺
創(chuàng)建對(duì)象還有一個(gè)方法:invokeExactConstructor,,該方法對(duì)參數(shù)要求 更加嚴(yán)格,傳遞進(jìn)去的參數(shù)必須嚴(yán)格符合構(gòu)造方法的參數(shù)列表,。 例如: Object[] args={new Integer(1), "Jan"}; Class[] argsType={int.class, String.class}; Object obj; //下面這句調(diào)用將不會(huì)成功,,因?yàn)閍rgs[0]的類型為Integer,而不是int //obj = ConstructorUtils.invokeExactConstructor(Month.class, args); //這一句就可以,,因?yàn)閍rgsType指定了類型,。 obj = ConstructorUtils.invokeExactConstructor(Month.class, args, argsType); Month month=(Month)obj; System.out.println(BeanUtils.getProperty(month,"value")); 七、MethodUtils 與ConstructorUtils類似,,不過調(diào)用的時(shí)候,,通常需要再指定一個(gè)method name的參數(shù)。 八,、DynaClass/DynaBean 這似乎是BeanUtils中最有趣的部分之一了,,很簡(jiǎn)單,簡(jiǎn)單到光看這兩個(gè)接口中的方法會(huì)不明白 為什么要設(shè)計(jì)這兩個(gè)接口,。不過看到ResultSetDynaClass后,,就明白了。下面是java doc中的代碼: 原來這是一個(gè)ResultSet的包裝器,,ResultSetDynaClass實(shí)現(xiàn)了DynaClass,,它的iterator方法返回一個(gè) ResultSetIterator,則是實(shí)現(xiàn)了DynaBean接口,。 在獲得一個(gè)DynaBean之后,,我們就可以用 再看另一個(gè)類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(); //先打印一下,,用于檢驗(yàn)后面的結(jié)果,。 while(rs.next()){ System.out.println(rs.getString("name")); } rs.beforeFirst();//這里必須用beforeFirst,,因?yàn)镽owSetDynaClass只從當(dāng)前位置向前滾動(dòng) RowSetDynaClass rsdc = new RowSetDynaClass(rs); rs.close(); ps.close(); List rows = rsdc.getRows();//返回一個(gè)標(biāo)準(zhǔn)的List,存放的是DynaBean for (int i = 0; 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的數(shù)據(jù),代價(jià)是占用內(nèi)存,。如果一個(gè)表有10萬條記錄,,rsdc.getRows() 就會(huì)返回10萬個(gè)記錄。@_@ 需要注意的是ResultSetDynaClass和RowSetDynaClass的不同之處: 1,,ResultSetDynaClass是基于Iterator的,,一次只返回一條記錄,而RowSetDynaClass是基于 List的,,一次性返回全部記錄,。直接影響是在數(shù)據(jù)比較多時(shí)ResultSetDynaClass會(huì)比較的快速, 而RowSetDynaClass需要將ResultSet中的全部數(shù)據(jù)都讀出來(并存儲(chǔ)在其內(nèi)部),,會(huì)占用過多的 內(nèi)存,,并且速度也會(huì)比較慢。 2,,ResultSetDynaClass一次只處理一條記錄,,在處理完成之前,ResultSet不可以關(guān)閉,。 3,,ResultSetIterator的next()方法返回的DynaBean其實(shí)是指向其內(nèi)部的一個(gè)固定 對(duì)象,在每次next()之后,,內(nèi)部的值都會(huì)被改變,。這樣做的目的是節(jié)約內(nèi)存,如果你需要保存每 次生成的DynaBean,,就需要?jiǎng)?chuàng)建另一個(gè)DynaBean,,并將數(shù)據(jù)復(fù)制過去,下面也是java doc中的代碼: 事實(shí)上DynaClass/DynaBean可以用于很多地方,,存儲(chǔ)各種類型的數(shù)據(jù),。自己想吧。嘿嘿,。 九,、自定義的CustomRowSetDynaClass 兩年前寫過一個(gè)與RowSetDynaClass目標(biāo)相同的類,不過多一個(gè)功能,,就是分頁,,只取需要的數(shù)據(jù), 這樣內(nèi)存占用就會(huì)減少,。 先看一段代碼: 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(); //第二個(gè)參數(shù)表示第幾頁,,第三個(gè)參數(shù)表示頁的大小 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 DynaBean b=(DynaBean)rows.get(i); System.out.println(b.get("name")); } } catch (Exception e) { e.printStackTrace(); } finally{ try { con.close(); } catch (Exception e) { } } 在這里用到了一個(gè)CustomRowSetDynaClass類,,構(gòu)造方法中增加了page和pageSize兩個(gè)參數(shù), 這樣,,不管數(shù)據(jù)庫里有多少條記錄,,最多只取pageSize條記錄,若pageSize==-1,,則功能和 RowSetDynaClass一樣,。這在大多數(shù)情況下是適用的。該類的代碼如下: package test.jakarta.commons.beanutils; import java.io.*; import java.sql.*; import java.util.*; import org.apache.commons.beanutils.*; public class CustomRowSetDynaClass implements DynaClass, Serializable { // ----------------------------------------------------------- Constructors public CustomRowSetDynaClass(ResultSet resultSet) throws SQLException { this(resultSet, true); } 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 protected boolean lowerCase = true; protected int page = 1; protected int pageSize = -1; protected DynaProperty properties[] = null; protected Map propertiesMap = new HashMap(); protected List rows = new ArrayList(); // ------------------------------------------------------ DynaClass Methods public String getName() { return (this.getClass().getName()); } public DynaProperty getDynaProperty(String name) { if (name == null) { throw new IllegalArgumentException } return ((DynaProperty) propertiesMap.get(name)); } public DynaProperty[] getDynaProperties() { return (properties); } public DynaBean newInstance() throws IllegalAccessException, InstantiationException { throw new UnsupportedOperationExce } // --------------------------------------------------------- Public Methods public List getRows() { return (this.rows); } // ------------------------------------------------------ Protected Methods 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); } } 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]); } } 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); } 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); } } } 大部分代碼從BeanUtils的源碼中取得,,只做了簡(jiǎn)單的修改,,沒有加多余的注釋。如果要正式使用,, 需要再做精加工,。 |
|