PropertyTokenizer,比较常见的中文叫法是属性分词器,顾名思义,就是用来将属性表达式解析为Bean的字段。其主要功能有两个,
1.将目标表达式一 一 解析为目标对象的字段名。
2.对于有List|Array类型的表达式,PropertyTokenizer不但会把其属性名解析出来,还会将下标|key解析出来,有了下标|key,我们就可以操作指定下标|key的集合|map属性。
属性表达式我把它分为两类,一般表达式和嵌套表达式
一般表达式:只有一个属性构成,e.g:companyName.
嵌套表达式:由多个单一属性构成,各属性之间用“.”号连接, e.g: company.departs;company.departs[0].departName;
PropertyTokenizer 有以下成员变量:
name:父表达式,一般对应被操作Bean的fieldName,根据该name可以获取被操作bean的属性的getter/setter,e.g:
indexedName:带索引的表达式,由父表达式和下标组成,indexedName一般应用于带下标的表达式。一些bean的属性类型可能是array|list,对于array|list,我们必须知道下标才能操作其中的元素,indexedName负责保存这类属性的字段名称和下标,e.g:
index:下标,该属性只对字段类型为map|list|array类型的字段有效,非map|list|array的字段,该属性总是为null。对于map类型的字段,index保存的是key,对于list和array类型的字段,index保存的是下标。
children:子表达式:该属性只对嵌套表达式有效,对于非嵌套表达式,该属性总是为null。对于嵌套表达式,children一般会做为递归操作的出口条件,调用者会使用PropertyTokenizer递归解析,直到children为null时才结束,e.g:
PropertyTokenizer的关键代码:
public PropertyTokenizer(String fullname) {
//step1 解析出parent表达式和children表达式
int delim = fullname.indexOf('.'); //寻找.分割符首次出现的位置
if (delim > -1) { //如果有.分隔符,根据.分隔符首次出现的位置对属性表达式进行截取
name = fullname.substring(0, delim); //截取到parent表达式
children = fullname.substring(delim + 1);//截取到children表达式
} else {
name = fullname; //fullname 即为parent表达式
children = null; //无children
}
indexedName = name; //indexedName一般应用于带有下标的属性表达式,e.g:departs[0].departName
// departs[0]即为indexedName,一般它会被再次解析后获取name和index,例如 departs[0]
//再次解析后的name=departs,index=0,有了name和index,我们就可以get/set集合属性中的元素
//name为被操作对象的字段名称,如果被操作对象如果是array|list,index则是下标
//step2 解析出field name和下标(如果有)
delim = name.indexOf('[');
if (delim > -1) { //如果有下标
index = name.substring(delim + 1, name.length() - 1); //保存下标到index
name = name.substring(0, delim); //3.截取出field name,
}
}
如上所述,调用者一般会递归解析嵌套属性表达式,那么递归的出口就是PropertyTokenizer中的hasNext():
public boolean hasNext() {
return children != null;
}
在PropertyTokenizer的调用者MetaObjet中,setValue(name,value)方法就将hasNext()方法做为递归调用出口,从而完成Bean的复杂属性的set,代码如下:
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
//实例化Parent MetaObject,如果Parent的值为null时,则实例化为SystemMetaObject.NULL_META_OBJECT
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null //parent为null objet且value也无null,返回
return;
} else {
//如果value不为null,但Parent MetaObject为Null,则实例化Parent并重新构造MetaObject
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);//实例化父属性后,再处理子属性
} else {
objectWrapper.set(prop, value);//objectWrapper完成set
}
}
为了更好的理解setValue方法,我画了流程图供参考:
在PropertyTokenizer中,比较容易混淆的是name和indexedName,name一般用于获取被操作对象的getter/setter,而indexedName则是为了处理集合属性,因为操作集合元素不是通过getter/setter,而是通过下标,一般的,indexedName会被调用者做为一般表达式进行二次解析,从而得到name和index属性,name用来获取集合属性的getter/setter,从而获取集合属性的示例,index则提供了被操作集合属性元素的下标。有了集合和下标,才能完成对集合元素的操作。indexedName可以看作是name+index的合成体,当表达式中没有带下标的属性的时候,indexedName总是和name相等。
下面列出一些Test Case,来简单列举下 PropertyTokenizer的常用场景,由于官方没有提供Test Case,以下Test Case是我补充进去的: