天天看點

初用apache.commons.beanutils.BeanUtils

引言

該class提供了一系列的靜态方法操作業已存在的符合JavaBean規範定義的Java Class.這裡強調的JavaBean規範,簡單來說就是一個Java Class通過一系列getter和setter的方法向外界展示其内在的成員變量(屬性).通過BeanUtils的靜态方法,我們可以:

  • 複制一個JavaBean的執行個體--BeanUtils.cloneBean();
  • 在一個JavaBean的兩個執行個體之間複制屬性--BeanUtils.copyProperties(),BeanUtils.copyProperty();
  • 為一個JavaBean的執行個體設定成員變量(屬性)值--BeanUtils.populate(),BeanUtils.setProperty();
  • 從一個一個JavaBean的執行個體中讀取成員變量(屬性)的值--BeanUtils.getArrayProperty(),BeanUtils.getIndexedProperty(),BeanUtils.getMappedProperty(),BeanUtils.getNestedProperty(),BeanUtils.getSimpleProperty(),BeanUtils.getProperty(),BeanUtils.describe();

總的來看BeanUtils類提供了兩大類的功能:讀,寫成員變量.

準備工作

下面逐一分析使用方法.首先我們建立兩個JavaBean,名位SampleObject和SampleObjectA,具體如下:

package beanutil;

import java.util.HashMap;

import java.util.Map;

public class SampleObject {

 String name = null;

 String display = null;

 int num = -1;

 char[] words = {'a','b','c','d'};

 boolean tag = false;

 Map map = new HashMap();

 SampleObjectA sample = null;

 public SampleObject() {

  this.map.put("home","localhost");

  this.map.put("port","80");

 }

//the following is getters and setters

 public String getDisplay() {

  return display;

 }

 public void setDisplay(String display) {

  this.display = display;

 }

 public String getName() {

  return name;

 }

 public void setName(String name) {

  this.name = name;

 }

 public int getNum() {

  return num;

 }

 public void setNum(int num) {

  this.num = num;

 }

 public char[] getWords() {

  return words;

 }

 public void setWords(char[] words) {

  this.words = words;

 }

 public boolean isTag() {

  return tag;

 }

 public void setTag(boolean tag) {

  this.tag = tag;

 }

 public Map getMap() {

  return map;

 }

 public void setMap(Map map) {

  this.map = map;

 }

 public SampleObject getSample() {

  return sample;

 }

 public void setSample(SampleObject sample) {

  this.sample = sample;

 }

}

package beanutil;

public class SampleObjectA {

 String name = null;

 String display = null;

 Double num = null;

 public Double getNum() {

  return num;

 }

 public void setNum(Double num) {

  this.num = num;

 }

 public String getDisplay() {

  return display;

 }

 public void setDisplay(String display) {

  this.display = display;

 }

 public String getName() {

  return name;

 }

 public void setName(String name) {

  this.name = name;

 }

}

所有測試使用的bean,如果未有說明,均使用SampleObject.

所有測試使用的bean,如果未有說明,均使用SampleObject.

BeanUtils.cloneBean(java.lang.object bean)

為bean建立一個clone對象,方法傳回類型為Object.注意bean即使沒有實作java.lang.Cloneable接口,此方法依然有效.此方法的實作機制建立在bean提供的一系列的getters和setters的基礎之上.此方法的正常使用代碼非常簡單,故略掉.

下面讨論下如果bean沒有提供getters和setters,會出現什麼情況,很明顯如果将其中的一對getter和setter注釋掉,如getDisplay()和setDisplay(),那麼結果是根本不會針對display這個成員變量進行複制;另外,如果将setDisplay()的通路限定符号設定為private的話,結果也是一樣的,成員變量-display在clone的過程中不會被複制.注意上面讨論的兩種情況,在運作時不會抛出任何的exception.對于不抛出exception的問題,我也感到非常迷惑,因為此方法的javadoc上明明指出當不能通路bean上的accessor或不存在accessor時,應該抛出java.lang.IllegalAccessException或java.lang.NotSuchMethodException.為了再次确認,我将SampleObject中的所有getter和setter都注釋掉了,結果依然一樣,看來要看下源碼了.

BeanUtils.copyProperties(java.lang.Object dest, java.lang.Object orig)

一個bean class有兩個執行個體:orig和dest,将orig中的成員變量的值複制給dest,即将已經存在的dest變為orig的副本.與BeanUtils.cloneBean(java.lang.object bean)的差別就在于是不是需要建立新的執行個體了.同樣正常使用代碼非常簡單,這裡也略掉.

如果bean class中沒有提供或是不完全提供getters和setters,結果如同在BeanUtils.cloneBean(java.lang.object bean)部分中的讨論結果一樣.

另外,我曾經這樣想,如果有兩個bean class,他們之間沒有任何關系,隻是在成員變量的命名上有重疊(以SampleObject為例,如果我們有另外的bean class--AnotherSampleObject,也包含了成員變量display,name和num),他們之間是否可以利用BeanUtils.copyProperties(java.lang.Object dest, java.lang.Object orig)進行複制呢?(這個想法來自于<struts in action>中formBean章節中關于formBean與valueObject的讨論)答案是可以的,該方法會複制名稱完全一樣的成員變量,即使成員變量的類型不同也會自動進行轉換的(我在AnotherSampleObject中将num的類型定義為Double,而SampleObject中的num為int),感覺真的是很神奇.回頭再去看看javadoc,發現這個方法原本就是如此設計的,原文如下:

Copy property values from the origin bean to the destination bean for all cases where the property names are the same.

其中for all cases where the property names are the same正是很好的明證,以後可以放心大膽的使用了.

ps:又對javadoc的重要性進行重新認識,同時認識到自己的英文是那麼的爛.

BeanUtils.copyProperty(java.lang.Object bean,java.lang.String name,java.lang.Object value)

這個方法簡單的說就是将bean中的成員變量name指派為value.使用方法如下:

SampleObject sample = new SampleObject();

 BeanUtils.copyProperty(sample,"num",new Integer(10));

如果成員變量為數組,如何為資料内的成員指派呢?apache的java doc上說的很明白,就是要提供一個包含索引參數的setter,是以要将以下代碼加到SampleObject的源代碼中.

 public void setWords(int index,char word){

  this.words[index] = word;

 }

如果我們要為SampleObject中的words[2]指派為S,那麼代碼如下:

BeanUtils.copyProperty(a,"words[2]","S");

如果成員變量為Map,如何為Map内指定key指派呢?同上面講的數組的方式一樣,就是要提供一個包含key參數的setter,在SampleObject中添加如下代碼:

 public void setMap(Object key,Object value){

  this.map.put(key,value);

 }

如果我們要将SampleObject.map中home對應值改為remote,那麼代碼如下:

BeanUtils.copyProperty(a,"map(home)","remote");

最後說下如何為嵌套屬性的指派,(所謂嵌套屬性就是beanA中一個成員變量是另外一個beanB,那麼beanB中的屬性就叫做beanA的嵌套屬性了.),用法如下:

BeanUtils.copyProperty(a,"sample.display","second one");

BeanUtils.setProperty(java.lang.Object bean,java.lang.String name,java.lang.Object value)

這個方法讓我郁悶了一會,因為它提供的功能與上面說的BeanUtils.copyProperty(java.lang.Object bean,java.lang.String name,java.lang.Object value)完全一緻,apache的hero們沒理由為同一功能提供兩種展示方法啊,後來我看了apache.commons.beanutils.BeanUtilsBean中的javadoc,才明白了一點點.如果我們隻是為bean的屬性指派的話,使用copyProperty()就可以了;而setProperty()方法是實作BeanUtils.populate()(後面會說到)機制的基礎,也就是說如果我們需要自定義實作populate()方法,那麼我們可以override setProperty()方法.

是以,做為一般的日常使用,setProperty()方法是不推薦使用的.

BeanUtils.populate(java.lang.Object bean, java.util.Map properties)

使用一個map為bean指派,該map中的key的名稱與bean中的成員變量名稱相對應.注意:隻有在key和成員變量名稱完全對應的時候,populate機制才發生作用;但是在數量上沒有任何要求,如map中的key如果是成員變量名稱的子集,那麼成員變量中有的而map中不包含的項将會保留預設值;同樣,如果成員變量是map中key的子集,那麼多餘的key不會對populate的結果産生任何影響.恩,結果就是populate隻針對map中key名稱集合與bean中成員變量名稱集合的交集産生作用.(很饒口啊)

正常用法很簡單,這裡略掉.

同樣,這個方法也支援對數組中單個元素,map中單個元素和嵌套屬性的指派,具體做法和copyProperty()方法類似,具體如下:

 values.put("words[1]","U");

  values.put("map(home)","remote");

  values.put("sample.display",new Double(5.0));

注意:apache的javadoc中,明确指明這個方法是為解析http請求參數特别定義和使用的,在正常的使用中不推薦使用.他們推薦使用BeanUtils.copyProperties()方法.(struts中的FormBean應該是用這個方法裝配的)

BeanUtils.getArrayProperty(java.lang.Object bean,java.lang.String name)

擷取bean中數組成員變量(屬性)的值.

沒什麼好說的,用法很簡單,略.

還是要說一句,如果我們指定的name不是數組類型的成員變量,結果會如何?會不會抛出類型錯誤的exception呢?回答是不會,仍然會傳回一個String的數組,數組的第一項就是name對應的值(如果不是String類型的話,JVM會自動的調用toString()方法的).

BeanUtils.getIndexedProperty(java.lang.Object bean,java.lang.String name)

BeanUtils.getIndexedProperty(java.lang.Object bean,java.lang.String name,int index)

這兩個方法都是擷取數組成員變量(屬性)中的單一進制素值的方法.比如,我想得到SampleObject中words[1]的值,用法如下:

BeanUtils.getIndexedProperty(sampleOjbectInstance,"words[1]");

 BeanUtils.getIndexedProperty(sampleOjbectInstance,"words",1);

BeanUtils.getMappedProperty(java.lang.Object bean,java.lang.String name)

BeanUtils.getMappedProperty(java.lang.Object bean,java.lang.String name,java.lang.String key)

這兩個方法是擷取map成員變量中單一進制素值的方法,用法與getIndexedProperty()方法相似,如我想得到SampleObject中map中home對應的值,用法如下:

BeanUtils.getMappedProperty(sampleOjbectInstance,map(home));

 BeanUtils.getMappedProperty(sampleOjbectInstance,map,"home");

BeanUtils.getNestedProperty(java.lang.Object bean,java.lang.String name)

擷取嵌套屬性值的方法,如我想得到SampleOjbect中成員變量sample中的display的值,用法如下:

BeanUtils.getNestedProperty(sampleOjbectInstance,"sample.display");

BeanUtils.getSimpleProperty(java.lang.Object bean, java.lang.String name)

BeanUtils.getProperty(java.lang.Object bean, java.lang.String name)

擷取屬性值的方法.api已經很清楚了,我唯一的問題是這個simple是什麼意思.javadoc隻是說了getProperty()方法中的name參數可以為普通屬性名稱,數組屬性名稱或嵌套屬性名稱的一種,而getSimpleProperty()方法中的name參數應該為普通屬性名稱了.我的想法是通過對方法簽名的不同,讓developers可以顯示差別對待普通屬性,數組屬性,map屬性和嵌套屬性.

ps:具體有何差別,看來要仔細看看源代碼了.

BeanUtils.describe(java.lang.Object bean)

将一個bean以map的形式展示.(這個方法和populate()是我夢想中的雙手劍)

但是使用這個方法得到的結果有點令我失望,以SampleObject為例,代碼片段如下:

 SampleObject a = new SampleObject();

  a.setDisplay("first one");

  a.setName("A");

  a.setNum(5);

  a.setWords("goto".toCharArray());

  SampleObjectA b = new SampleObjectA();

  b.setDisplay("nested property");

  b.setNum(new Double(2.0));

  b.setName("sampleA");

  a.setSample(b);

  try {

   Map descMap = BeanUtils.describe(a);

   System.out.println(descMap);

  }

  ......

運作結果如下:

{num=5, display=first one, class=class beanutil.SampleObject, words=g, tag=false, [email protected], map={port=80, home=localhost}, name=A}

  • 首先可以看出,除了輸出SampleObject中定義的key-value外,還會包含class=class beanutil.SampleObject這一項,我想這是為了通過獲得的map我們可以知道原來的bean的具體類型;
  • 其次,作為數組成員變量(屬性)的words,在map中隻包含了首個元素,而map類型的成員變量的輸出結果到是非常令人滿意.為什麼明明長度為4的words數組現在輸出隻有一個字元呢,我又進行了debug,并監控了words變量,發現在傳回的descMap中,words對應的值的類型為String,長度為1.

    ps:不知道是不是我使用錯誤,真不知道為什麼會這樣.

  • 最後,嵌套屬性不會逐一進行輸出的,除非你override了toString()方法.
與apache.commons.beanutils.BeanUtilsBean的關系

apache.commons.beanutils.BeanUtils中每個方法是通過apache.commons.beanutils.BeanUtilsBean實作的,apache.commons.beanutils.BeanUtils中靜态方法功能是預設方法,也就是最基本和最普通的,如果需要更複雜的功能實作的話,則需要使用apache.commons.beanutils.BeanUtilsBean中的方法.apache.commons.beanutils.BeanUtilsBean可以在不同的緩沖區記憶體在不同的執行個體,進而可以提供不同的服務,主要是converter的不同.通過這個機制可以為不同的使用者提供本地化的支援(我想這個在internet application上經常要用到吧).我想這也是為什麼apache.commons.beanutils.BeanUtilsBean不是interface而是class的原因.

總結

BeanUtils是利用java的反射和自醒機制來讀寫javabean的屬性的.

BeanUtils和BeanUtilsBean之間是個什麼設計模式呢?我也搞不清楚.

ps:道行還是太低啊.