引言
该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;
/**
* @author samepoint
*
* SampleObject contains some types of member varaibles:String,int,Array,Map,Object(self defined),just for test usaged of apache.commons.beanutils.BeanUtils
*/
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;
/**
* default constructor. initialized members of map and sample.
*/
public SampleObject() {
this.map.put("home","localhost");
this.map.put("port","80");
}
//the following is getters and setters
/**
* @return Returns the display.
*/
public String getDisplay() {
return display;
}
/**
* @param display The display to set.
*/
public void setDisplay(String display) {
this.display = display;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @param name The name to set.
*/
public void setName(String name) {
this.name = name;
}
/**
* @return Returns the num.
*/
public int getNum() {
return num;
}
/**
* @param num The num to set.
*/
public void setNum(int num) {
this.num = num;
}
/**
* @return Returns the words.
*/
public char[] getWords() {
return words;
}
/**
* @param words The words to set.
*/
public void setWords(char[] words) {
this.words = words;
}
/**
* @return Returns the tag.
*/
public boolean isTag() {
return tag;
}
/**
* @param tag The tag to set.
*/
public void setTag(boolean tag) {
this.tag = tag;
}
/**
* @return Returns the map.
*/
public Map getMap() {
return map;
}
/**
* @param map The map to set.
*/
public void setMap(Map map) {
this.map = map;
}
/**
* @return Returns the sample.
*/
public SampleObject getSample() {
return sample;
}
/**
* @param sample The sample to set.
*/
public void setSample(SampleObject sample) {
this.sample = sample;
}
}
package beanutil;
/**
* @author samepoint
*
* Used to copy properties from SampleOjbect.
* Used to nested property.
*/
public class SampleObjectA {
String name = null;
String display = null;
Double num = null;
/**
* @return Returns the num.
*/
public Double getNum() {
return num;
}
/**
* @param num The num to set.
*/
public void setNum(Double num) {
this.num = num;
}
/**
* @return Returns the display.
*/
public String getDisplay() {
return display;
}
/**
* @param display The display to set.
*/
public void setDisplay(String display) {
this.display = display;
}
/**
* @return Returns the name.
*/
public String getName() {
return name;
}
/**
* @param name The name to set.
*/
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的源代码中.
/**
* set word with against
* @param index
* @param word
*/
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中添加如下代码:
/**
* set map with key
* @param key
* @param value
*/
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, sample=beanutil.SampleObjectA@be2358, 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的属性的.