2.9.解析 ObjectWrapperFactory 元素 解析并配置对象包装工厂

优质
小牛编辑
132浏览
2023-12-01

在前面的文章中,我们接触过很多个工厂对象,比如:SqlSessionFactoryObjectFactoryReflectoryFactory

ObjectWrapperFactory也是一个工厂对象,他用于将普通的JAVA对象包装成ObjectWrapper对象。

在这里我将ObjectWrapper叫做对象包装器,因为ObjectWrapper对象的作用就是包装对象后为其提供统一的属性操作方法。

鉴于ObjectWrapperFactory就是一个创建ObjectWrapper实例的接口定义,因此,我称之为对象包装器工厂。

关系图

ObjectWrapperFactory接口中定义了两个方法:

/**
 * 一个对象包装器创建工厂的接口定义,他的实现类负责将普通的java对象包装成ObjectWrapper实例。
 * @author Clinton Begin
 * @see org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(ResultSetWrapper, ResultMap, String)
 */
public interface ObjectWrapperFactory {
    /**
     * 是否拥有指定对象的装饰对象
     * @param object 指定该对象
     */
    boolean hasWrapperFor(Object object);
    /**
     * 通过对象元数据获取指定对象的包装对象
     * @param metaObject 对象元数据
     * @param object 指定对象
     */
    ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}

其中,hasWrapperFor方法用于判断当前的对象包装工厂是否可以为指定的对象创建对象包装器,getWrapperFor方法则用于实际获取指定对象的对象包装器——一个ObjectWrapper实例。

根据方法的定义来看,可以想象该接口的方法调用大致会是如下格式:

// 通过hasWrapperFor校验,通过getWrapperFor进行转换
if (objectWrapperFactory.hasWrapperFor(object)) {
    // 通过包装器工厂进行包装
    this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
}

ObjectWrapperFactory接口的默认实现类是DefaultObjectWrapperFactory,被硬编码在Configuration对象中:

/**
 * 配置对象包装器工厂,主要用于将java对象包装成{@link org.apache.ibatis.reflection.wrapper.ObjectWrapper}
 */
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

DefaultObjectWrapperFactory的实现比较简单,他的hasWrapperFor方法总是返回false,这就意味着他不会将任何对象包装成ObjectWrapper实例,所以他的getWrapperFor方法理论上也不会被调用:

/**
 * 默认的对象包装器工厂
 *
 * @author Clinton Begin
 */
public class DefaultObjectWrapperFactory implements ObjectWrapperFactory {

  /**
   * 是否拥有指定对象的装饰对象
   *
   * @param object 指定该对象
   */
  @Override
  public boolean hasWrapperFor(Object object) {
    // 恒定false
    return false;
  }

  /**
   * 通过对象元数据获取指定对象的包装对象
   *
   * @param metaObject 对象元数据
   * @param object     指定对象
   */
  @Override
  public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
    throw new ReflectionException("The DefaultObjectWrapperFactory should never be called to provide an ObjectWrapper.");
  }
}

ObjectWrapperFactorygetWrapperFor方法有一个MetaObject类型的参数,该参数的作用和我们之前了解过的MetaClass对象有所类似, 不同于MetaClass用于保存类型元数据,MetaObject用于保存指定对象的元数据并提供操作对象元数据的方法。

换句话来说MetaClass是类型描述对象,MetaObject则是对象描述对象。

作为对象描述对象的MetaObject类中定义了五个属性,这五个属性的存在的目的是封装被描述对象,简化其属性和方法的访问和操作。

/**
 * 被包装的原始对象
 */
private final Object originalObject;
/**
 * 原始对象的包装器
 */
private final ObjectWrapper objectWrapper;
/**
 * 实例化对象的工厂
 */
private final ObjectFactory objectFactory;
/**
 * 创建对象包装器的工厂
 */
private final ObjectWrapperFactory objectWrapperFactory;
/**
 * 创建Reflector对象的反射工厂
 */
private final ReflectorFactory reflectorFactory;

这些属性的初始化操作是在MetaObject的构造方法中完成的:

/**
 * @param object               原始对象
 * @param objectFactory        对象工厂
 * @param objectWrapperFactory 对象包装工厂
 * @param reflectorFactory     反射工厂
 */
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    // 初始化被包装的原始对象
    this.originalObject = object;
    // 初始化实例化对象的工厂类
    this.objectFactory = objectFactory;
    // 初始化创建对象包装器的工厂类
    this.objectWrapperFactory = objectWrapperFactory;
    // 初始化用于获取类描述对象的工厂类
    this.reflectorFactory = reflectorFactory;

    // 为原始对象创建对象包装器

    if (object instanceof ObjectWrapper) {
        // 无需包装
        this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
        // 通过包装器工厂进行包装
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
        this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

在构造方法中,除了objectWrapper属性之外,其余属性的初始化全都是简单的赋值操作。

本篇文章开头时有说过ObjectWrapper是一个对象包装器接口定义,他为被包装对象提供了统一的操作属性的方法:

/**
 * 对象包装器
 *
 * 包装对象后提供统一的属性操作方法。
 *
 * @author Clinton Begin
 */
public interface ObjectWrapper {

    /**
     * 根据属性名称描述符获取指定属性的值
     *
     * @param prop 属性名称描述符
     * @return 指定属性的值
     */
    Object get(PropertyTokenizer prop);

    /**
     * 根据属性名称描述符为指定的属性赋值
     *
     * @param prop  属性名称描述符
     * @param value 指定的值
     */
    void set(PropertyTokenizer prop, Object value);

    /**
     * 查找属性名称
     *
     * @param name                用于获取属性名的名称,比如可以通过name获取Name,或者Name获取name。
     * @param useCamelCaseMapping 是否使用驼峰命名法
     * @return 实际属性名称
     */
    String findProperty(String name, boolean useCamelCaseMapping);

    /**
     * 获取所有可读属性名
     *
     * @return 可读属性名集合
     */
    String[] getGetterNames();

    /**
     * 获取所有可写属性名
     *
     * @return 可写属性名集合
     */
    String[] getSetterNames();

    /**
     * 获取指定属性的setter方法入参类型
     *
     * @param name 属性名
     * @return 该属性setter方法入参类型
     */
    Class<?> getSetterType(String name);

    /**
     * 获取指定属性的getter方法返回类型
     *
     * @param name 属性名称
     * @return 该属性的getter方法的返回类型
     */
    Class<?> getGetterType(String name);

    /**
     * 是否有指定属性的setter方法
     *
     * @param name 属性名
     * @return 是否有指定属性的setter方法
     */
    boolean hasSetter(String name);

    /**
     * 是否有指定属性的getter方法
     *
     * @param name 属性名称
     * @return 是否有指定属性的getter方法
     */
    boolean hasGetter(String name);

    /**
     * 实例化某个属性的值,并获取对应的MetaObject对象
     *
     * @param name          完整的属性名
     * @param prop          属性名描述符
     * @param objectFactory 对象创建工厂
     * @return 指定的属性的值对应的MetaObject对象
     */
    MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

    /**
     * 判断被包装的对象是否是集合
     *
     * @return 被包装对象是否是集合
     */
    boolean isCollection();


    /**
     * 往被包装的集合对象中添加新的元素
     *
     * @param element 被添加的元素
     */
    void add(Object element);

    /**
     * 往被包装的集合对象中添加一组元素
     *
     * @param element 被添加的元素集合
     * @param <E>     元素类型
     */
    <E> void addAll(List<E> element);

}

因为不同的JAVA对象,其属性操作方法也有所不同,所以综合绝大多数会被操作的标准对象的特点,mybatis默认为ObjectWrapper提供了四个实现类:BaseWrapperBeanWrapperMapWrapperCollectionWrapper

ObjectWrapper

其中BaseWrapper是一个抽象类,它定义了对象包装器中操作集合的通用方法,他有两个属性定义:NO_ARGUMENTSmetaObject

/**
 * 执行getter方法时使用的入参
 */
protected static final Object[] NO_ARGUMENTS = new Object[0];
/**
 * 被包装对象的元数据
 */
protected final MetaObject metaObject;

其中名为NO_ARGUMENTS的属性常作为方法入参被用于执行getter类型方法的反射操作,比如:

Invoker method = metaClass.getGetInvoker(prop.getName());
return method.invoke(object, NO_ARGUMENTS);

至于名为metaObject的属性则负责维护被包装对象的元数据。

BeanWrapperMapWrapper都是BaseWrapper的实现类,BeanWrapper用于包装普通的java对象,MapWrapper负责包装Map集合对象,至于另一个ObjectWrapper的实现类CollectionWrapper则负责包装普通集合对象。

MetaObject的构造方法中,对于objectWrapper属性的赋值操作比较简单:

  • 如果被包装的原始对象本身就是ObjectWrapper接口的实例的话,不进行任何处理,直接赋值给objectWrapper属性。
  • 如果被包装的原始对象本身不是ObjectWrapper接口的实例,那就优先使用ObjectWrapperFactory对其进行包装处理,
  • 如果ObjectWrapperFactory也不能处理该对象,那么就按照该对象的实际类型将其包装成MapWrapperCollectionWrapper或者BeanWrapper类型的实例。
if (object instanceof ObjectWrapper) {
    // 无需包装
    this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
    // 通过包装器工厂进行包装
    this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
    this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
    this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
    this.objectWrapper = new BeanWrapper(this, object);
}

通过上面的方法我们可以看到无论是MapWrapperCollectionWrapper亦或是BeanWrapper他们的构造方法都需要一个被包装对象的实例以及该实例对应的MetaObject对象。

new MapWrapper(this, (Map) object);
new CollectionWrapper(this, (Collection) object);
new BeanWrapper(this, object);

MapWrapper的构造方法中:

public MapWrapper(MetaObject metaObject, Map<String, Object> map) {
  super(metaObject);
  this.map = map;
}

会首先调用其父类BaseWrapper的构造方法来缓存当前MetaObject对象,之后将需要被包装的Map对象赋值给唯一的map属性:

/**
 * 被包装的Map集合对象
 */
private final Map<String, Object> map;

负责将MetaObject对象保存起来的BaseWrapper的构造方法实现也很简单:

/**
 * 被包装对象的元数据
 */
protected final MetaObject metaObject;

protected BaseWrapper(MetaObject metaObject) {
    this.metaObject = metaObject;
}

BaseWrapper的另一个实现类BeanWrapper的构造方法同样先调用了BaseWrapper的构造方法保存MetaObject实例,之后又保存了被缓存的对象以及该对象的类型元数据:

/**
 * 原始对象
 */
private final Object object;
/**
 * 类型元数据
 */
private final MetaClass metaClass;

public BeanWrapper(MetaObject metaObject, Object object) {
    super(metaObject);
    // 缓存被包装的原始对象
    this.object = object;
    // 获取原始对象的类型描述元数据
    this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
}

最后,关于CollectionWrapper的构造方法,则只是简单的将被包装的集合对象保存起来而已:

/**
 * 被包装的集合对象
 */
private final Collection<Object> object;

public CollectionWrapper(MetaObject metaObject, Collection<Object> object) {
    this.object = object;
}

在完成ObjectWrapper实例的创建工作之后,MetaObject会将其赋值给objectWrapper属性,至此MetaObject对象也创建完成了。

在完成了MetaObject对象的创建工作之后,为了简化后续学习的复杂度,这里我们提前看一下MetaObject对象的方法定义:

MetaObject不仅对外暴露了objectFactoryobjectWrapperFactoryreflectorFactoryoriginalObject以及objectWrapper属性的getter方法:

// 对外暴露五个属性的getter方法
public ObjectFactory getObjectFactory() {
    return objectFactory;
}

public ObjectWrapperFactory getObjectWrapperFactory() {
    return objectWrapperFactory;
}

public ReflectorFactory getReflectorFactory() {
    return reflectorFactory;
}

public Object getOriginalObject() {
    return originalObject;
}
public ObjectWrapper getObjectWrapper() {
     return objectWrapper;
 }

作为一个可以操作对象属性的元数据对象,自然少不了对外提供用于操作对象属性的方法,这些方法中大部分实现都是直接委托给了ObjectWrapper的同名方法来完成的,

比如:

/**
 * 查找属性属性名称
 *
 * @param propName            用于获取属性名的名称,比如可以通过name获取Name,或者Name获取name。
 * @param useCamelCaseMapping 是否使用驼峰命名法,即下划线转驼峰,比如通过first_name 获取firstName属性
 * @return 实际属性名称
 */
public String findProperty(String propName, boolean useCamelCaseMapping) {
    return objectWrapper.findProperty(propName, useCamelCaseMapping);
}
/**
 * 获取所有可读属性名
 *
 * @return 可读属性名集合
 */
public String[] getGetterNames() {
    return objectWrapper.getGetterNames();
}

/**
 * 获取所有可写属性名
 *
 * @return 可写属性名集合
 */
public String[] getSetterNames() {
    return objectWrapper.getSetterNames();
}

/**
 * 获取指定属性的setter方法入参类型
 *
 * @param name 属性名
 * @return 该属性setter方法入参类型
 */
public Class<?> getSetterType(String name) {
    return objectWrapper.getSetterType(name);
}

/**
 * 获取指定属性的getter方法返回类型
 *
 * @param name 属性名称
 * @return 该属性的getter方法的返回类型
 */
public Class<?> getGetterType(String name) {
    return objectWrapper.getGetterType(name);
}

/**
 * 是否有指定名称属性的setter方法
 *
 * @param name 指定属性名称
 */
public boolean hasSetter(String name) {
    return objectWrapper.hasSetter(name);
}

/**
 * 是否有指定名称属性的getter方法
 *
 * @param name 指定属性名称
 */
public boolean hasGetter(String name) {
    return objectWrapper.hasGetter(name);
}

/**
 * 维护的对象是否是集合类型
 */
public boolean isCollection() {
    return objectWrapper.isCollection();
}

/**
 * 往维护集合对象中添加一个元素
 */
public void add(Object element) {
    objectWrapper.add(element);
}

/**
 * 往维护集合对象中添加一组元素
 */
public <E> void addAll(List<E> list) {
    objectWrapper.addAll(list);
}

剩余的方法则是在经过业务处理之后,才整体或者部分委托给ObjectWrapper的实现类来完成:

/**
 * 获取指定属性的值
 *
 * @param name 属性名称
 * @return 属性值
 */
public Object getValue(String name) {
    // 将传入的属性名生成属性迭代器
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        // 如果属性描述中包含了索引名称,即存在 [] 。
        // 解析出索引对应的值
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        } else {
            // 递归
            return metaValue.getValue(prop.getChildren());
        }
    } else {
        // 获取属性值
        return objectWrapper.get(prop);
    }
}

/**
 * 未指定的属性赋值
 * @param name 属性名称
 * @param value 属性值
 */
public void setValue(String name, Object value) {

    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        // [param.id]
        // 获取指定名称的值的对象元数据
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            if (value == null) {
                // don't instantiate child path if value is null
                return;
            } else {
                // 实例化属性值
                metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
            }
        }
        // 保存属性对应的值
        metaValue.setValue(prop.getChildren(), value);
    } else {
        // [id]
        // 保存值
        objectWrapper.set(prop, value);
    }
}

/**
 * 将指定属性的值包装成元数据对象
 *
 * @param name 名称
 * @return 指定名称的值的对象元数据
 */
public MetaObject metaObjectForProperty(String name) {
    // 从对象元数据中获取指定名称的属性值
    Object value = getValue(name);
    // 生成该值对应的对象元数据
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}

方法很多,我们慢慢看。

用于查找属性名称的findProperty方法

我们先来看用于查找属性名称的findProperty方法,findProperty有两个入参,一个是表示用于查找属性名称的属性名称描述符propName,另一个表示在获取属性名称时是否将propName中的下划线转换为驼峰之后再进行查找操作的useCamelCaseMapping

findProperty方法的作用是在指定对象中解析出propName参数的有效部分,那么什么叫做propName参数的有效部分呢?

propName是一个属性名称描述符,在mybatis中他通常会被转换为PropertyTokenizer对象来使用,在学习过PropertyTokenizer对象之后,我们了解到属性名称描述符可以用来描述多层嵌套的属性,比如: panda.name.first

关于PropertyTokenizer对象,如果不太了解,可以点击了解PropertyTokenizer对象

既然propName可以描述多层嵌套的属性,那就有可能会产生属性描述符中的某一级属性并不存在的场景,比如:当propName的值为panda.name.first时,如果panda中不存在name属性定义,这时候panda就是propName参数的有效部分,而name.first则是无效部分。

我们看一个下面这个示例:

/**
 * 公司
 */
public static class Company {

    private Staff ceo;

    private Staff cto;

    private Mascot mascot;

    public Company(Staff ceo) {
        this.ceo = ceo;
    }
}

/**
 * 员工
 */
public static class Staff {
    /**
     * 姓名
     */
    private String name;

    public Staff(String name) {
        this.name = name;
    }
}

/**
 * 吉祥物
 */
private static class Mascot {
    // 无属性定义
}

请仔细看下面这个测试方法的内容:

@Test
public void findPropertyTest() {

  // 初始化实例对象
  Company company = new Company(new Staff("jpanda"));

  // 获取该对象的MetaObject

  // SystemMetaObject后面会讲,他的作用是创建MetaObject对象
  MetaObject metaObject = SystemMetaObject.forObject(company);

  // 测试

  // 有ceo属性,且ceo有name属性
  String ceoName = metaObject.findProperty("ceo.name", false);
  assert ceoName.equals("ceo.name");

  // 有cto属性,且cto有name属性,虽然cto没有被赋值,但不影响属性名的解析
  String ctoName = metaObject.findProperty("cto.name", false);
  assert ctoName.equals("cto.name");

  // 有mascot属性,但是mascot没有name属性
  String mascotName = metaObject.findProperty("mascot.name", false);
  assert mascotName.equals("mascot.");

  // 无coo属性定义
  String cooName = metaObject.findProperty("coo.name", false);
  assert cooName == null;
}

在上面的示例代码中,我们不难发现,一个属性是否有效,不在于这个属性是否被赋值,被初始化,而在于他所对应的类中是否定义了该属性。

了解了的有效属性的概念,我们继续看findProperty方法的具体实现。

MetaObjectfindProperty方法是完全委托给了ObjectWrapperfindProperty方法来完成的。

因为ObjectWrapper除去抽象的BaseWrapper类定义之外,还有三个有效实现,所以相应的ObjectWrapperfindProperty方法也就有三种实现:

  • 因为CollectionWrapper负责包装集合类型,而常用的集合类型通常被用来维护一组元素,这些集合类型的类定义中往往不会对外暴露有效的属性名称定义,甚至对于其维护的元素来讲,获取具体某个元素的操作通常也是使用集合下标index来完成的,但是一方面Collection接口的实现类Set并不支持通过索引下标获取元素,另一方面通过索引下标获取数据还可能会触发IndexOutOfBoundsException异常,因此CollectionWrapper不支持调用findProperty方法:
      @Override
       public String findProperty(String name, boolean useCamelCaseMapping) {
           throw new UnsupportedOperationException();
       }
    
  • MapWrapper用于维护Map集合,虽然在Map集合的类型定义中通常也不会对外暴露有效的属性定义,但是因为对Map集合维护的元素的操作是统一的,往往是通过Map集合维护的Entitykey值来操作相对应的value,因此在一定程度上我们可以认为Map集合所维护的每个Entitykey值就是属性名称,那么因为Map对字符串类型的key值没有什么限制,所以在理论上传入的属性名可以是任意字符串内容,不过需要注意的是因为mybatis在PropertyTokenizer中为.[两个符号赋予了特殊含义,所以需要尽可能避免使用这两个符号。

    因为Mapkey集合在运行期间才会确定,所以MapWrapperfindProperty方法会直接返回传入的属性名称本身:

      @Override
       public String findProperty(String name, boolean useCamelCaseMapping) {
         return name;
       }
    
  • 至于用于包装其余JAVA对象BeanWrapper,这里的属性就是常规意义上的属性,因此,BeanWrapperfindProperty方法实现被委托给了MetaClassfindProperty方法来完成:

    @Override
    public String findProperty(String name, boolean useCamelCaseMapping) {
      return metaClass.findProperty(name, useCamelCaseMapping);
    }
    

MetaClassfindProperty方法中,会根据useCamelCaseMapping参数的值来决定是否移除属性名中的下划线,之后调用重载的findProperty(String)方法来完成后续的操作:

public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
        // 移除下划线
        name = name.replace("_", "");
    }
    return findProperty(name);
}

findProperty(String)重载方法内,通过buildProperty方法,来完成具体属性名称的处理操作:

public String findProperty(String name) {
    StringBuilder prop = buildProperty(name, new StringBuilder());
    return prop.length() > 0 ? prop.toString() : null;
}

buildProperty方法会将属性名称转换为PropertyTokenizer对象,然后根据PropertyTokenizer对象是否有子节点分别执行两套不同的业务逻辑:

private StringBuilder buildProperty(String name, StringBuilder builder) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        // 获取指定名称的属性
        String propertyName = reflector.findPropertyName(prop.getName());
        // 属性不存在,跳出递归

        if (propertyName != null) {
            // 属性存在
            builder.append(propertyName);
            builder.append(".");
            // 获取该属性对应的类型元数据
            MetaClass metaProp = metaClassForProperty(propertyName);
            // 递归解析属性
            metaProp.buildProperty(prop.getChildren(), builder);
        }
    } else {
        // 获取属性名
        String propertyName = reflector.findPropertyName(name);
        if (propertyName != null) {
            builder.append(propertyName);
        }
    }
    return builder;
}
  • 如果PropertyTokenizer包含子节点,则调用Reflector对象的findPropertyName方法获取当前属性的真实名称:

    • 如果当前属性名称存在,则继续调用metaClassForProperty方法获取该属性对应的类型元数据,并将PropertyTokenizer的子节点名称和builder作为参数继续传递给MetaClassbuildProperty方法递归调用完成解析属性名称的工作。
      /**
       * 获取指定属性的类型元数据
       *
       * @param name 指定字段
       * @return 指定字段的类型元数据
       */
      public MetaClass metaClassForProperty(String name) {
          // 从反射元数据中获取该名称的getter方法的类型
          Class<?> propType = reflector.getGetterType(name);
          // 生成元数据
          return MetaClass.forClass(propType, reflectorFactory);
      }
    
    • 如果当前属性名称不存在,就表示对应的java类型中没有定义该属性,那么就结束递归操作,直接返回已经获取到属性名称,那么这个已经获取到的属性名称,就是前面提到过的有效属性名称。
  • 如果PropertyTokenizer不包含子节点,则调用Reflector对象的findPropertyName方法获取当前属性的真实名称,将其追加到builder中返回。

@startuml
hide footbox

participant MetaClass as mc
participant PropertyTokenizer as rt
participant Reflector as r

autonumber  1  1 
loop 有未处理的有效子属性

[-> mc :<color:red><b><size:16>buildProperty()
activate mc
mc -> rt ++: hasNext()
return 是否有子属性

alt  有子属性

mc -> r ++: findPropertyName()
return 大小写不敏感的属性名
note  left
获取指定名称对应的
大小写不敏感的属性名
endnote


opt 存在对应的属性
activate mc
mc -> mc :metaClassForProperty()

note right 
  获取该属性对应的类型元数据:
  一个新的MetaClass
end note


mc -> mc ++  : <color:red><b><size:16>buildProperty()

return  有效属性名

note right
  递归调用MetaClass的buildProperty方法
  完成子属性的解析
endnote
end


else 无子属性

mc -> r ++:findPropertyName()
return 大小写不敏感的属性名

end
end
[<- mc : 有效属性名
title 通过递归获取有效的属性名称
header  mybatis 源码学习笔记
footer  jpanda@aliyun.com  jpanda.cn
@enduml

用于获取指定对象中所有可读属性名的getGetterNames方法

在了解了findProperty方法之后,我们继续看用于获取指定对象中所有可读属性名的getGetterNames方法:

/**
 * 获取所有可读属性名
 *
 * @return 可读属性名集合
 */
public String[] getGetterNames() {
    return objectWrapper.getGetterNames();
}

MetaObjectgetGetterNames方法也是完全委托给了ObjectWrapper的实现类来完成的,根据在解析findProperty方法时梳理出的CollectionWrapperMapWrapper以及BeanWrapper三者对于属性名称定义的不同含义,可以很容易的理解下面这些包装器对象的getGetterNames方法实现:

  • CollectionWrapper不支持属性名称,因此也就不支持getGetterNames方法的调用:
    @Override
    public String[] getGetterNames() {
    throw new UnsupportedOperationException();
    }
    
  • MapWrapperEntity对象的key值作为属性名称,因此会直接返回所有Entitykey值集合:
    @Override
    public String[] getGetterNames() {
    return map.keySet().toArray(new String[map.keySet().size()]);
    }
    
  • BeanWrapper的属性就是常规意义上的属性名称,所以会真的去获取JAVA对象的所有可读属性名称:
    @Override
    public String[] getGetterNames() {
      return metaClass.getGetterNames();
    }
    

三个实现中,也就BeanWrapper稍显复杂一下,在实现上,BeanWrapper将获取可读属性名称集合的操作委托了MetaClassgetGetterNames方法来完成:

public String[] getGetterNames() {
    return reflector.getGetablePropertyNames();
}

MetaClass又将实现委托给了ReflectorgetGetterNames方法:

/**
 * 获取可读属性名称集合
 *
 * @return 可读属性名称集合
 */
public String[] getGetablePropertyNames() {
    return readablePropertyNames;
}

ReflectorgetGetterNames方法则直接返回了用于维护可读属性名称集合的readablePropertyNames属性。

Reflector中属性的赋值操作在前文已经提到过。

@startuml
hide footbox
participant MetaObject as mo
participant BeanWrapper as bw
participant MetaClass as mc
participant Reflector as r

autonumber  1  1 

[-> mo  : getGetterNames()
activate mo

mo -> bw ++ : getGetterNames()

bw -> mc ++ : getGetterNames()

mc -> r ++ :getGetablePropertyNames()
return readablePropertyNames

return readablePropertyNames

return readablePropertyNames

[<- mo : 返回拥有<color:Blue>Getter</color>方法的属性名集合

note over mo,r
 readablePropertyNames:
 可读属性集合,该属性在<color:red>Reflector</color>对象的构造方法中完成初始化
end note

title   获取指定对象所有可读属性名
header  mybatis 源码学习笔记
footer  jpanda@aliyun.com  jpanda.cn
@enduml

MetaObject中用于获取所有可写属性名称集合的getSetterNames方法实现和原理同getGetterNames近乎一致:

/**
 * 获取所有可写属性名
 *
 * @return 可写属性名集合
 */
public String[] getSetterNames() {
    return objectWrapper.getSetterNames();
}

getSetterNames方法也直接被委托给ObjectWrapper来实现,基于同样的原因:

  • CollectionWrapper不支持getSetterNames方法。
  • MapWrapper会直接返回所有Entitykey值集合。
  • BeanWrapper的调用栈还是延续到Reflector中,直到获取到Reflector中负责维护可写属性名称集合的writeablePropertyNames属性值。

用于操作属性值的getValue和setValue方法

MetaObject还有两个用于操作属性值的方法:getValuesetValue

  • getValue负责获取属性描述符对应的属性值
    /**
    * 获取指定属性的值
    *
    * @param name 属性名称
    * @return 属性值
    */
    public Object getValue(String name) {
      // 将传入的属性名生成属性迭代器
      PropertyTokenizer prop = new PropertyTokenizer(name);
      if (prop.hasNext()) {
          // 获取属性值对应的MetaObject
          MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
          if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
              // 当前属性是空对象,子类自然不存在值
              return null;
          } else {
              // 递归处理属性描述符
              return metaValue.getValue(prop.getChildren());
          }
      } else {
          // 真正获取属性值
          return objectWrapper.get(prop);
      }
    }
    
  • setValue负责为属性描述符对应的属性设值

      /**
       * 未指定的属性赋值
       * @param name 属性名称
       * @param value 属性值
       */
      public void setValue(String name, Object value) {
    
          PropertyTokenizer prop = new PropertyTokenizer(name);
          if (prop.hasNext()) {
              // [param.id]
              // 获取指定名称的值的对象元数据
              MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
              if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                  if (value == null) {
                      // 保存的值为null,没有必要实例化
                      // don't instantiate child path if value is null
                      return;
                  } else {
                      // 实例化属性值
                      metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
                  }
              }
              // 保存属性对应的值
              metaValue.setValue(prop.getChildren(), value);
          } else {
              // [id]
              // 保存值
              objectWrapper.set(prop, value);
          }
      }
    

getValuesetValue两个方法的在实现上有些类似,都是先解析出属性描述符对应的具体属性,然后交给ObjectWrapper的同名方法完成取值或者赋值操作。

但是因为取值操作和赋值操作在本质上的不同,在多层嵌套的属性结构中,如果某一层属性值为null,取值操作会直接返回null,但是赋值操作则需要根据用于为属性设值的对象是否为null来决定是否实例化对应的属性。

比如:

let panda={
  "name":null
};

当我们获取panda.name.first描述符对应的属性值时,我们会得到一个null,当我们为panda.name.first属性赋值时,我们会得到对象:

let panda={
  "name":{
    "first":"j"
  }
};

这里就是getValuesetValue两个方法的主要区别之一,getValue解析null值属性时,直接返回null即可,setValue则需要进行进一步的判断。

getValue方法中,MetaObject会将传入的属性名称描述符转换为PropertyTokenizer对象,然后根据PropertyTokenizer是否有子节点来执行不同的业务逻辑

如果PropertyTokenizer有子节点定义,getValue方法会利用metaObjectForProperty方法依次获取每层属性值对应的对象元数据,在新的对象元数据实例不是SystemMetaObject.NULL_META_OBJECT的前提下,递归调用MetaObjectgetValue方法来完成相应的下一级属性值的获取操作。

PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
   // 获取属性值对应的MetaObject
   MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
   if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
       // 当前属性是空对象,子类自然不存在值
       return null;
   } else {
       // 递归处理
       return metaValue.getValue(prop.getChildren());
   }
}

SystemMetaObject.NULL_META_OBJECT用于表示空的对象元数据,关于SystemMetaObjec相关的内容在后面会讲。

metaObjectForProperty方法中,会先利用MetaObjectgetValue方法来完成获取属性值的操作,因此在这里metaObjectForPropertygetValue方法就构成了相互依赖的递归关系。

/**
 * 将指定属性的值包装成元数据对象
 *
 * @param name 名称
 * @return 指定名称的值的对象元数据
 */
public MetaObject metaObjectForProperty(String name) {
    // 从对象元数据中获取指定名称的属性值
    Object value = getValue(name);
    // 生成该值对应的对象元数据
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}

不过,因为getValue方法调用metaObjectForProperty时传入的参数是PropertyTokenizerindexedName属性,indexedName不可能是多层属性描述符:

MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());

所以当metaObjectForProperty继续递归调用getValue方法时,就会触发getValue的第二种处理方式。

// 从对象元数据中获取指定名称的属性值
Object value = getValue(name);
// 生成该值对应的对象元数据
return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);

metaObjectForProperty调用getValue方法获取到属性描述符对应的属性值之后,会借助于MetaObjectforObject方法将其包装成一个对象元数据返回给调用方。

MetaObjectforObject方法实现十分简单,他在被包装对象为null值时将会返回SystemMetaObject.NULL_META_OBJECT,非null值时调用MetaObject的构造参数完成对象元数据的构造工作。

/**
 * 生成对象元数据
 *
 * @param object               对象
 * @param objectFactory        对象工厂
 * @param objectWrapperFactory 对象包装工厂
 * @param reflectorFactory     反射工厂
 * @return 对象元数据
 */
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    if (object == null) {
        // 返回空对象元数据
        return SystemMetaObject.NULL_META_OBJECT;
    } else {
        // 生成对象元数据
        return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
    }
}

MetaObject的构造工作前面已经讲过,所以不再赘述。

继续看getValue的第二种处理方式,该方式用于处理属性描述符描述单个属性的场景,具体的实现逻辑交给了ObjectWrapper对象的get方法来完成。

// 真正获取属性值
return objectWrapper.get(prop);

基于相同的理论,ObjectWrapperget方法同样有三个实现,因为CollectionWrapper不支持属性名称,所以也就不支持get方法.

@Override
public Object get(PropertyTokenizer prop) {
    throw new UnsupportedOperationException();
}

MapWrapperBeanWrapper都是支持属性名称,所以他们提供了get方法的实现,而且两者的实现比较类似,他们都会判断传入的PropertyTokenizer参数是否有索引定义,如果有索引定义就按照集合的方式取值,如果没有的话,就按照常规的方式取值。

// MapWrapper
// TODO
// BeanWrapper
// TODO

MapWrapperBeanWrapper对于集合的取值操作实现是一致的,他们都会调用父类BaseWrapper中定义的resolveCollection方法获取PropertyTokenizer对应的属性集合,再利用BaseWrappergetCollectionValue方法处理获取到的属性集合和PropertyTokenizer参数,完成从集合中取值的操作:

if (prop.getIndex() != null) {
    //  属性描述符中包含索引定义,从Map中获取到对应的属性集合
    //  比如: prop=names[panda],resolveCollection会获取map集合中key为names的value。
    Object collection = resolveCollection(prop, map);
    // 从key为names的value属性集合中获取key为panda的属性值
    return getCollectionValue(prop, collection);
}

这里有一点值得注意的是ObjectWrapperget方法的入参PropertyTokenizer属性不会是多层属性描述符。

BaseWrapperresolveCollection方法实现比较简单,他的作用就是从当前维护的对象元数据中取出指定的属性值,看代码实现会发现方法名叫resolveCollection,但实际上该方法也能获取到普通的JAVA属性值。

/**
  * 解析集合对象
  *
  * @param prop   属性名称
  * @param object 实例值
  * @return 最终对象的值
  */
 protected Object resolveCollection(PropertyTokenizer prop, Object object) {
     if ("".equals(prop.getName())) {
         // 如果属性名称为空,直接返回对象
         return object;
     } else {
         // 继续从对象元数据中解析该属性
         return metaObject.getValue(prop.getName());
     }
 }

getCollectionValue方法负责根据传入的PropertyTokenizer参数从指定的集合中取值:

/**
 * 解析属性名称描述符获取集合内对应的值
 *
 * @param prop       属性描述符
 * @param collection 集合
 * @return 集合内对应的值
 */
protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
    if (collection instanceof Map) {
        // 如果是map,直接根据key取值
        return ((Map) collection).get(prop.getIndex());
    } else {
        // 获取下标
        int i = Integer.parseInt(prop.getIndex());
        // 读取数据
        if (collection instanceof List) {
            return ((List) collection).get(i);
        } else if (collection instanceof Object[]) {
            return ((Object[]) collection)[i];
        } else if (collection instanceof char[]) {
            return ((char[]) collection)[i];
        } else if (collection instanceof boolean[]) {
            return ((boolean[]) collection)[i];
        } else if (collection instanceof byte[]) {
            return ((byte[]) collection)[i];
        } else if (collection instanceof double[]) {
            return ((double[]) collection)[i];
        } else if (collection instanceof float[]) {
            return ((float[]) collection)[i];
        } else if (collection instanceof int[]) {
            return ((int[]) collection)[i];
        } else if (collection instanceof long[]) {
            return ((long[]) collection)[i];
        } else if (collection instanceof short[]) {
            return ((short[]) collection)[i];
        } else {
            throw new ReflectionException("The '" + prop.getName() + "' property of " + collection + " is not a List or Array.");
        }
    }
}

getCollectionValue方法实现比较简单,唯一需要注意的就是如果该方法的入参collection不是一个集合,将会导致异常的发生,比如下面这个场景:

// 数据对象
user={
  "names":["j","panda"]
  ,"age":18
}

当我们获取通过names[0]属性描述符获取user对象的数据时能够正确获得值j,但当我们通过age[0]属性描述符获取user对象的数据时将会导致异常的发生,这是因为age[0]中包含了索引定义,所以会命中resolveCollection方法来获取userage属性,但是因为age不是集合定义,所以在getCollectionValue方法中就会导致异常的产生。

看完BeanWrapperMapWrapper的集合取值,我们继续看一下BeanWrapperMapWrapper的常规取值实现。

BeanWrapperMapWrapper的常规取值实现略有不同,但是都很好理解,MapWrapper是直接从他维护的map属性中取值:

{
    // 直接从map中取值
    return map.get(prop.getName());
}

BeanWrapper稍微复杂点,因为他维护的是真正意义上的JAVA属性定义,所以他在获取到属性定义之后还需要执行反射操作获取属性对应的值。

{
    // 获取属性值
    return getBeanProperty(prop, object);
}

在前面的文章中,我们已经了解mybatis会将关于操作属性值的反射操作进一步包装成Invoker执行器对象来使用。

BeanWrapper获取属性值的具体实现是交给getBeanProperty方法来完成的:

private Object getBeanProperty(PropertyTokenizer prop, Object object) {
     try {
         // 获取该属性对应的get方法反射操作包装器
         Invoker method = metaClass.getGetInvoker(prop.getName());
         try {
             // 执行反射操作,获取数据
             return method.invoke(object, NO_ARGUMENTS);
         } catch (Throwable t) {
             throw ExceptionUtil.unwrapThrowable(t);
         }
     } catch (RuntimeException e) {
         throw e;
     } catch (Throwable t) {
         throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
     }
 }

方法看起来很长,实际上就做了两件事:获取读取属性值的Invoker执行器和通过执行器获取属性值。

获取Invoker执行器的工作是由MetaClassgetGetInvoker方法委托给Reflector对象的getGetInvoker方法来完成的:


// MetaClass
public Invoker getGetInvoker(String name) {
    return reflector.getGetInvoker(name);
}

ReflectorgetGetInvoker方法直接从负责维护getter方法集合和执行器的映射列表的getMethods属性中获取到所需的执行器对象返回给调用方:

/**
  * 获取指定属性的getter执行器
  * @param propertyName 属性名称
  * @return getter执行器
  */
 public Invoker getGetInvoker(String propertyName) {
     Invoker method = getMethods.get(propertyName);
     if (method == null) {
         throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
     }
     return method;
 }

通过执行器获取属性值的操作也比较简单,直接执行获取到的Invoker对象的invoke方法即可,注意看在这里就用到了BaseWrapperNO_ARGUMENTS属性:

// 执行反射操作,获取数据
return method.invoke(object, NO_ARGUMENTS);

关于Invoker对象的invoke方法的不同实现之前已经讲过了,这里就不说了,

BeanWrappergetBeanProperty方法从MetaClass中获取该属性对应的getter执行器,然后通过该执行器进行取值操作:

负责获取getter执行器的MetaClassgetGetInvoker方法将具体实现委托给了Reflector对象的getGetInvoker方法来完成:

public Invoker getGetInvoker(String name) {
    return reflector.getGetInvoker(name);
}

BeanWrappergetBeanProperty方法在得到getter执行器之后,会执行Invoker对象的invoke方法来完成读取属性值的工作,Invoker执行器在前面已经讲过,此处不再赘述。

这样,我们就完成了MetaObject的取值操作。

@startuml
hide footbox
participant MetaObject as mo

participant BeanWrapper as bw
participant MetaClass as mc
participant Reflector as r

autonumber  1  1 

[-> mo : getValue()
activate mo 
create participant  PropertyTokenizer as pt
activate pt
mo -> pt : hasNext()
return 是否有子属性定义
alt 有子属性定义
mo -> mo++ : metaObjectForProperty()
note left 
获取主属性对应的MataObject
end note
return 当前属性的MataObject对象
alt 当前属性是空对象,子类自然不存在值 
[<- mo : 返回null
else 递归处理属性描述符
mo -> mo ++ :getValue()
return 真正获取到的属性值
end

else 无子属性定义
mo -> bw ++ : get()

alt 属性描述符中包含索引定义
bw -> bw ++: resolveCollection()
return 获取集合对象
bw -> bw : getCollectionValue()
return 返回索引在该集合对应的值 


else 属性描述符中不包含索引定义

bw -> bw ++: getBeanProperty()
bw -> mc ++ : getGetInvoker()
create participant Invoker
mc -> Invoker++: invoke()
return 属性值
destroy Invoker

return 属性值

deactivate bw
bw -> mo : 返回属性值
end
destroy pt
[<- mo : 返回获取到的属性值
end

title   根据属性描述符获取指定对象的属性值
header  mybatis 源码学习笔记
footer  jpanda@aliyun.com  jpanda.cn

@enduml

MetaObject还有两个用于获取指定属性方法类型的方法定义,getGetterType用于获取指定属性getter方法的返回类型,getSetterType用于获取指定属性的setter方法的入参类型

两个方法的实现也比较类似:

都是先将属性描述符转换为PropertyTokenizer对象,然后借助于MetaObjectmetaObjectForProperty依次解析出每一层属性描述符对应的类型和子属性名称 ,在获取到最终的子属性名称之后,调用子属性所属对象的MetaClass实例的getSetterType,完成最终的类型取值操作。

@startuml
hide footbox
participant MetaObject as mo

participant BeanWrapper as bw
participant MetaClass as mc

autonumber  1  1 

[-> mo : getGetterType()
activate mo 

mo -> bw ++ : getGetterType()
create participant  PropertyTokenizer as pt 
bw -> pt ++ : hasNext()
alt 有子属性定义
  bw -> mo ++ : metaObjectForProperty()
  return 属性描述对应的当前属性对象元数据
  alt null对象定义
  bw -> mc ++ : getGetterType()
  return 属性类型
  else 非null对象定义
    bw -> mo++ : getGetterType()
    return 属性类型
  end

else 无子属性定义
   bw -> mc ++ : getGetterType()
  return 属性类型
end

bw -> mo : 属性类型
[<- mo :属性类型

title   根据属性描述符获取指定对象的属性类型定义
header  mybatis 源码学习笔记
footer  jpanda@aliyun.com  jpanda.cn

@enduml

上面的流程图没有展示MetaClass解析类下单具体操作,我们再看一下metaClass是如何解析类型的:

@startuml
hide footbox

participant BeanWrapper as bw
participant MetaClass as mc
participant Reflector as r

autonumber  1  1 

[-> mc : getGetterType()
activate mc 
create participant  PropertyTokenizer as pt 
mc -> pt ++ : hasNext()
alt 有子属性定义
  mc -> mc ++ : metaClassForProperty()
  return 新的MetaClass实例
  mc -> mc ++ : getGetterType()
  return 递归解析,获取属性描述符对应的类型


else 无子属性定义

 mc -> r ++ : getGetterType()
  opt 属性为集合类型且属性描述符包含索引定义
    r -> r++: getGenericGetterType()
    return 集合中字属性类型
  end
  return 属性描述符对应的类型
end

[<- mc : 属性描述符对应的类型

title   根据属性描述符获取指定对象的属性类型
header  mybatis 源码学习笔记
footer  jpanda@aliyun.com  jpanda.cn

@enduml

在初步了解了ObjectWrapperFactory对象相关信息之后,我们来看一下objectWrapperFactoryDTD定义和解析工作。

objectWrapperFactory的DTO定义如下:

<!ELEMENT configuration (..., objectWrapperFactory?, ...)>

<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>

在mybatis的全局配置文件内,最多只能配置一个objectWrapperFactory节点,objectWrapperFactory有一个必填的type属性,该属性用于指向一个ObjectWrapperFactory的实例,这里可以使用实例别名。

XmlConfigBuilderparseConfiguration方法负责触发objectWrapperFactoryt节点的解析工作:

private void parseConfiguration(XNode root) {
    // ...
   // 配置对象包装工厂
   objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
   // ...
}

/**
 * 解析objectWrapperFactory节点
 *
 * @param context objectWrapperFactory节点
 */
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
}

objectWrapperFactoryElement方法的实现也很简单,通过反射获取ObjectWrapperFactory的实例之后, 交给Configuration对象的方法setObjectWrapperFactory将获取到的ObjectWrapperFactory实例赋值给ConfigurationobjectWrapperFactory的属性,取代默认的DefaultObjectWrapperFactory实现。

/**
 * 配置对象包装工厂
 * @param objectWrapperFactory 对象包装工厂
 */
public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
    this.objectWrapperFactory = objectWrapperFactory;
}

到此,ObjectWrapperFactory元素的解析工作也就完成了。

这一章的内容比较多,但是了解了这里的知识之后,在后续的源码过程中,可以大大降低问题理解的难度。