Apache Commons项目简介之BeanUtils
0.简介
Apache Commons项目是专注于开发可重用的Java组件。
Apache Commons项目由三部分组成:
Commons Proper - 可重用Java组件库。
Commons Sandbox - Java组件开发工作空间。
Commons Dormant - sandbox中不活跃的项目存储库。
Apache Commons开发人员使每个组件尽可能少的依赖其他的lib,以便这些组件可以方便的部署,而不是需要将依赖的库依次部署。另外,Commons组件的接口尽可能的保持不变,这样可以在实现这些组件的时候针对接口进行开发,确保兼容性。
本文主要介绍第一部分的项目,即可重用的稳定的Java组件库。由于库中的每个子项目都包含了特定的功能,所以本文不会对这些功能面面俱到的介绍,而是针对库中的每个子项目,尽可能的介绍其应用场景,以及列举典型的样例。
1.BeanUtils介绍
很多Java开发人员习惯于创建符合JavaBeans命名模式(属性的getter和setter)的Java类。然后直接访问这些方法,通过使用getXxx和setXxx方法。然而,有时需要动态的访问Java对象的属性。一些应用场景包括:
与java对象模型交互的构建脚本语言(例如Bean Scripting Framework)
构建用于web表示和相关应用的模型语言处理器(JSP或者Velocity)
为JSP和XSP环境构建自定义标签(例如Jakarta标签库,Struts,Cocoon)
解析基于XML的配置资源(例如Ant构建脚本,Web部署描述符,Tomcat的server.xml文件)
Java语言提供了反射和自省API,包含在java.lang.reflect和java.beans包中。然而,这些API难于理解和使用,所以BeanUtils组件提供了简单易用的封装。
BeanUtils核心和模型:
从1.7.0发布版本开始,都包含3个jar文件:
commons-beanutils.jar - 包含了所有的内容
commons-beanutils-core.jar - 不包含Bean Collections类
commons-beanutils-bean-collections.jar - 只包含Bean Collections类
common-beanutils.jar有对Commons Collections的一个可选的依赖。
关于Bean Collections:
Bean collections是将BeanUtils和Commons Collections组合起来的一个库,用来提供对beans集合的service。以前发布一个BeanComparator类,其他的类是新发布的。Bean Collections依赖于Commons Collections。
2.项目依赖
本项目依赖的库包括如下的内容:
编译:
commons-logging1.1.1
commons-collections3.2.1
test:
commons-collections3.2.1
junit3.8.1
依赖关系树:
* commons-beanutils:commons-beanutils:jar
o junit:junit:jar
o commons-collections:commons-collections:jar
o commons-collections:commons-collections-testframework:jar
o commons-logging:commons-logging:jar
3.背景
JavaBeans这个名字来自于Java语言中的一个组件框架API。开发符合JavaBeans设计模式的类可以使java开发人员更容易的理解其功能,同时使得基于JavaBeans的工具使用Java内省机制的功能来获取类的属性和方法。
JavaBeans规范描述了Java类是否为JavaBean的特点。规范中要求的JavaBeans的特点有下面这些:
类必须是public,提供public无参构造方法。这使得应用程序或者工具可以直接创建类的实例,而不是在创建实例之前先知道类的名字。
作为无参构造方法的结果,必须独立的完成bean的初始化。一般通过定义bean的属性来完成。
典型的,每个Bean属性都包含public的getter和setter方法来获取和设置属性,使用getXxx和setXxx来作为方法名。
对于boolean型的属性,可以使用isXxx来代替getXxx。
getter方法和setter方法中,getter方法的返回值类型要和setter方法的参数类型一致。
并不要求对每个属性都设置getter和setter方法。例如如果属性是只读的,那么就不需要setter方法。只写的属性不常见。但是是允许的。
同时,可以书写命名规则和JavaBeans的getter和setter不一致的类,标准的JavaBeans支持Java语言中的类,也支持BeanUtils包,允许在与Bean类关联的BeanInfo类中描述实际的方法名。
JavaBeans规范中还描述了很多额外的设计模式,例如事件监听器,将JavaBeans集成到组件继承层次,以及其他一些超出BeanUtils范围的特性。
4.标准JavaBeans
如前所述,可以很方便的通过合适的getter方法来获取beans的属性。但是有的时候没有办法在使用之前知道类的名字,或者不知道该获取/设置什么属性。Java语言提供了java.beans.Introspector这样的类,它们可以在运行时检查java类并标识属性的getter和setter类。同时,反射功能还可以动态的调用这些方法。然而,这些API用起来比较困难,并且暴露了太多的不必要的细节给程序员。BeanUtils API用来在运行时动态的get和set bean的属性、访问对象的位置以及属性的名字,而不是在编译时。
这些需求都可以通过PropertyUtils类的静态方法来满足。
JavaBean支持的属性的类型可以分为三类,一些是标准规范支持,另一些只有BeanUtils支持。
Simple:或者叫标量属性,包含一个单一的值,可以被获取或者设置。属性类型可以是原始类型或者简单对象,例如java.lang.String,或者更复杂一些的对象。
Indexed:带索引的属性存书了一个有序的对象集合(相同类型的对象),可以通过非负整数作为索引来访问。也就是说,整个值可以通过数组来设置或者获取。作为对Javabeans的扩展,BeanUtils将类型为java.util.List的属性看作是索引的。
Mapped:BeanUtils将类型为java.util.Map的属性看作是mapped类型的属性。可以通过字符串的key-value来访问和设置。
可以通过PropertyUtils类来设置或者获取所有上述三种类型的属性。
* PropertyUtils.getSimpleProperty(Object bean, String name)
* PropertyUtils.setSimpleProperty(Object bean, String name, Object value)
* PropertyUtils.getIndexedProperty(Object bean, String name)
* PropertyUtils.getIndexedProperty(Object bean, String name, int index)
* PropertyUtils.setIndexedProperty(Object bean, String name, Object value)
* PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value)
* PropertyUtils.getMappedProperty(Object bean, String name)
* PropertyUtils.getMappedProperty(Object bean, String name, String key)
* PropertyUtils.setMappedProperty(Object bean, String name, Object value)
* PropertyUtils.setMappedProperty(Object bean, String name, String key, Object value)
一些例子如下:
Employee employee = ...;
String firstName = (String)
PropertyUtils.getSimpleProperty(employee, "firstName");
String lastName = (String)
PropertyUtils.getSimpleProperty(employee, "lastName");
... manipulate the values ...
PropertyUtils.setSimpleProperty(employee, "firstName", firstName);
PropertyUtils.setSimpleProperty(employee, "lastName", lastName);
Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address(home)", address);
通过使用属性名+[+index+]的方式来作为索引属性的属性名。
Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address", "home", address);
Employee employee = ...;
int index = ...;
String name = "subordinate[" + index + "]";
Employee subordinate = (Employee)
PropertyUtils.getIndexedProperty(employee, name);
通过属性名+(+key+})来作为mapped属性的属性名。
Employee employee = ...;
int index = ...;
Employee subordinate = (Employee)
PropertyUtils.getIndexedProperty(employee, "subordinate", index);
Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address(home)", address);
Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address", "home", address);
上面的例子中,都是直接获取bean的属性值,但是如果bean的属性是一个对象,而我们需要获取那个对象的属性呢?
可以通过PropertyUtils类的getNestedProperty来获取。将属性名路径和属性名用.分隔符连接。例如我们要获取employee bean的home的address的city部分。
Employee:
Map
Address:
String city;
* PropertyUtils.getNestedProperty(Object bean, String name)
* PropertyUtils.setNestedProperty(Object bean, String name, Object value)
String city = (String)
PropertyUtils.getNestedProperty(employee, "address(home).city");
为了方便使用,还提供了统一的获取和设置属性的方法:
* PropertyUtils.getProperty(Object bean, String name)
* PropertyUtils.setProperty(Object bean, String name, Object value)
5.动态beans(DynaBeans)
PropertyUtils类提供了动态获取/设置既存JavaBean类的属性,但是并不修改JavaBean类。另一种动态属性访问的方式是希望表示动态计算的JavaBean属性集,而不是去编写实际的Java类来表示这些属性。除了节省创建和维护单独的java类的工作之外,这个功能意味着可以可以处理动态决定的属性。(例如将SQL查询语句的结果作为JavaBeans来表示)。为了提供这个功能,BeanUtils提供了DynaBean接口以及关联的DynaClass接口,DynaClass接口定义了特定一组DynaBeans支持的属性,这和java.lang.Class定义的被所有JavaBean类支持的属性集相同。例如,Employee类可以实现DynaBean接口,而不是标准的JavaBean,此时可以通过下面的方式来访问其属性:
DynaBean employee = ...; // Details depend on which
// DynaBean implementation you use
String firstName = (String) employee.get("firstName");
Address homeAddress = (Address) employee.get("address", "home");
Object subordinate = employee.get("subordinate", 2);
由于DynaBean和DynaClass都是接口,所以多次被实现,在不同的应用场景以及以不同的方式。
BasicDynaBean和BasicDynaClass提供了基本的实现。
DynaProperty[] props = new DynaProperty[]{
new DynaProperty("address", java.util.Map.class),
new DynaProperty("subordinate", mypackage.Employee[].class),
new DynaProperty("firstName", String.class),
new DynaProperty("lastName", String.class)
};
BasicDynaClass dynaClass = new BasicDynaClass("employee", null, props);
注意BasicDynaClass的dynaBeanClass参数可以设置为null,此时,dynaClass.getDynaBeanClass返回BasicDynaBean的Class。
接下来就可以使用newInstance方法来创建DynaClass实例,并进行填充初始值。
DynaBean employee = dynaClass.newInstance();
employee.set("address", new HashMap());
employee.set("subordinate", new mypackage.Employee[0]);
employee.set("firstName", "Fred");
employee.set("lastName", "Flintstone");
此时可以将DynaBean作为第一个对象提供给PropertyUtils类的方法。
而在此过程中,并不需要创建独立的java源文件。
ResultSetDynaClass专门用于封装SQL SELECT语句的结果。但是ResultSet必须一致保持open的状态。这阻碍了在MVC结构(例如struts)中使用ResultSetDynaClass在model层和视图层之间的交互,因为无法确保ResultSet最后被关闭了。
RowSetDynaClass用于解决上述问题。当创建RowSetDynaClass时,底层的数据被拷贝到内存中的DynaBeans集合中。该方式的好处在于可以在创建之后就关闭ResultSet。缺点自然就是性能上的损失,因为要将数据放在内存中。
WrapDynaBean和WrapDynaClass提供了对标准JavaBeans的封装。
MyBean bean = ...;
DynaBean wrapper = new WrapDynaBean(bean);
String firstName = wrapper.get("firstName");
Lazy DynaBeans提供了以下的特性:添加属性,增长List/Array长度,List/Array实例化,Map实例化,Bean实例化。Lazy DynaBeans包含以下几个类:
LazyDynaBean,LazyDynaList,LazyDynaMap,LazyDynaClass。
6.类型转换
有时数据的类型是已知的,或者可以通过Java的强制类型转换来完成,但是如果无法进行强制类型转换呢?BeanUtils提供了大量的API以及设计模式来执行类型转换工作。
一个普遍的应用也是BeanUtils库开发的初衷就是将javax.servlet.HttpServletRequest中包含的请求参数转换为一个属性集JavaBean中。
HttpServletRequest request = ...;
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);
BeanUtils类依赖于ConvertUtils类中定义的转换方法,来进行实际的转换工作,这些方法也可以直接使用。但是显式的使用ConvertUtils在已经是deprecated,作为替代,可以使用Converter接口的自定义实现。
现在已经有很多转换实现类存在。
7.集合
org.apache.commons.beanutils.BeanComparator是一个Comparator接口的实现,用于比较具有相同属性值的beans。
common-collections项目中的Closure接口封装了执行任意输入对象的代码块。commons-collections包含了允许Closures应用于Collection的代码。
BeanPropertyValueChangeClosure是一个Closure,用于为特定的属性设置特定的值。一个典型的应用就是将集合中的所有bean的一个属性都设置特定的值。
例如将整个集合的bean的activeEmployee属性设置为TRUE:
// create the closure
BeanPropertyValueChangeClosure closure =
new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
// update the Collection
CollectionUtils.forAllDo( peopleCollection, closure );
commons-collections中的Predicate接口封装了对输入对象的判断,返回true或者false。commons-collections允许Predicates应用于集合的过滤。
BeanPropertyValueEqualsPredicate是一个Predicate,用于过滤针对特定值的bean。
例如过滤整个集合,查找activeEmployee的值是false的所有beans:
BeanPropertyValueEqualsPredicate predicate =
new BeanPropertyValueEqualsPredicate( "activeEmployee", Boolean.FALSE );
// filter the Collection
CollectionUtils.filter( peopleCollection, predicate );
commons-collections中的Transformer接口封装了将输入对象转换为输出对象。commons-collections允许使用Transformer来通过输入集合创建输出集合。
BeanToPropertyTransformer是一个Transformer,用于将bean转换为它的属性的值。
例如获取每个bean的person属性中的地址属性中的城市信息,输出到集合中:
// create the transformer
BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer( "person.address.city" );
// transform the Collection
Collection peoplesCities = CollectionUtils.collect( peopleCollection, transformer );
8.总结
本文对Apache Commons项目的BeanUtils子项目进行了简要的介绍。