Mybatis PropertyTokenizer

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是我补充进去的:

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值