MyBatis(十二) - 一些API

后面我们就要进入到 MyBatis 的源码底层,去探究 MyBatis 的整体生命周期执行原理了,作为前置章,小册有必要先给各位讲解一些 MyBatis 内部的 API ,了解它们之后,会更有助于我们去理解 MyBatis 的原理。

1. 反射系工具类

首先我们先来了解一些 MyBatis 内置的反射系工具类。反射是框架最最喜欢也最最常用的特性了,有了反射,框架可以完成非常多的底层工作,以支撑起我们自己开发的功能。MyBatis 的反射系工具比较多,大概有 SpringFramework 的那些反射相关工具的两倍多,不过难度相比较于 SpringFramework 的要低,我们选择其中比较重要的来看。

1.1 Reflector

Reflector 是 MyBatis 中反射的基础,我们可以先这么简单的理解一句话:一个 **Reflector** 对象对应了一个 Class 的反射信息。怎么理解这句话呢?这样吧,咱先看下 Reflector 的结构:

public class Reflector {

    private final Class<?> type;
    private final String[] readablePropertyNames;
    private final String[] writablePropertyNames;
    private final Map<String, Invoker> setMethods = new HashMap<>();
    private final Map<String, Invoker> getMethods = new HashMap<>();
    private final Map<String, Class<?>> setTypes = new HashMap<>();
    private final Map<String, Class<?>> getTypes = new HashMap<>();
    private Constructor<?> defaultConstructor;

好家伙,一个类中的信息几乎都解释全了,不过这里面可能有一些我们比较陌生的概念,小册先解释一下。

1.1.1 可读&可写

上面 Reflector 的类成员中有两个数组:readablePropertyNameswritablePropertyNames ,它们会分别存储一个类中可读的属性和可写的属性,何为可读与可写?其实就是 get 和 set 。按照规范来讲,我们在声明实体模型类时,通常都是这么来写的:

public class Department {
    
    private String name;
    
    public String getName() {
        return this.name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

MyBatis 在解析这个 Department 类时,发现这里面定义了一个 name 属性,随后又发现了一个 getName 方法,则会认定这个 name 属性可读;后面解析时又发现了 setName 方法,则会认定这个 name 属性可写。

这组概念是非常好理解的,小册不多啰嗦。

1.1.2 getTypes & setTypes

这一对属性记录的是 getter 与 setter 方法,它们对应的属性类型是什么,比方说我们之前定义的 User 类,它的 department 属性的 get 方法,就是 Department 类型:

包括其余的属性,都会有所记录(如 id 的返回值是 String ),可它记录这些是为了什么呢?

不为别的,反射一次,缓存起来,以后就不用费劲了。

1.1.3 Reflector的构造方法

了解了 Reflector 中的几个成员,下面我们简单的看一下 Reflector 的构造方法:(注释已标全)

public Reflector(Class<?> clazz) {
    // 缓存当前Reflector对应的类
    type = clazz;
    // 如果有的话,记录下默认无参构造器
    addDefaultConstructor(clazz);
    // 缓存所有的getter方法
    addGetMethods(clazz);
    // 缓存所有的setter方法
    addSetMethods(clazz);
    // 缓存所有的属性字段(主要是处理没有getter/setter的)
    addFields(clazz);
    // Map转数组,都是为了缓存
    readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    for (String propName : readablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
}

可见,一个 Class 的所有属性方法信息,在 Reflector 的构造阶段就全部准备好了,之后再读取的时候就是走内部加载好的缓存了,这种设计,就是典型的拿空间换时间(MyBatis 认为时间更宝贵)。

至于这里面的方法都是如何实现的,小册认为没有必要深入了解,因为我们主要把控的是 MyBatis 框架的工具、API ,对于过于深层次的实现我们不是很关心,所以各位如果感兴趣的话可以深入进去学习,小册就不再展开了。

1.2 ReflectorFactory

反射工厂,依照 MyBatis 的编码风格,凡是 Factory 最终都是创建前面的单词的对象,所以 ReflectorFactory 就是负责创建 Reflector 的了。这个家伙本身是一个接口:

public interface ReflectorFactory {
    boolean isClassCacheEnabled();
    void setClassCacheEnabled(boolean classCacheEnabled);
    Reflector findForClass(Class<?> type);
}

核心方法是 findForClass ,在它的默认实现(也是唯一实现)DefaultReflectorFactory 中是这样的:

private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
        // synchronized (type) removed see issue #461
        return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
        return new Reflector(type);
    }
}

可以发现这个 ReflectorFactory 还有缓存的能力呢,这么做也是充分的体现了空间换时间的设计方式。

至于创建 Reflector 的方式,那简直朴实的不能再朴实了。。。

1.3 Invoker

上面的 Reflector 中,属性 getMethodssetMethods 都是一个 Map ,注意这个 Map 的 value 类型是 Invoker ,这家伙是个啥呢?

public interface Invoker {
    Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
    Class<?> getType();
}

这家伙又是一个接口,注意它的核心方法 invoke ,咋看上去那么像反射中 Methodinvoke 呢?哎没错,它其实就有一个落地实现,里面套了一个 Method 。这个接口一共有 3 个主要实现,我们可以简单地都看一下。

1.3.1 MethodInvoker

MethodInvoker ,基于方法的 Invoker ,它的核心就是执行 method.invoke() ,还真就是套了层壳,没啥好说的哈。

public class MethodInvoker implements Invoker {

    private final Class<?> type;
    private final Method method;

    @Override
    public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
        try {
            return method.invoke(target, args);
        } catch (IllegalAccessException e) {
            if (Reflector.canControlMemberAccessible()) {
                method.setAccessible(true);
                return method.invoke(target, args);
            } else {
                throw e;
            }
        }
    }

1.3.2 GetFieldInvoker

GetFieldInvoker ,基于 Fieldget 动作封装,这就是上面 Reflector 中考虑到某些属性本身没有 getter 方法,但 MyBatis 可能还需要获取它的值,所以就使用了这样一层 Invoker 去获取属性的值,底层核心是 field.get()

public class GetFieldInvoker implements Invoker {
    private final Field field;

    @Override
    public Object invoke(Object target, Object[] args) throws IllegalAccessException {
        try {
            return field.get(target);
        } catch (IllegalAccessException e) {
            if (Reflector.canControlMemberAccessible()) {
                field.setAccessible(true);
                return field.get(target);
            } else {
                throw e;
            }
        }
    }

1.3.3 SetFieldInvoker

SetFieldInvoker ,同样是考虑到类中的某些属性没有 setter 方法,MyBatis 采取的措施,它的底层是 field.set()

public class SetFieldInvoker implements Invoker {
    private final Field field;

    @Override
    public Object invoke(Object target, Object[] args) throws IllegalAccessException {
        try {
            field.set(target, args[0]);
        } catch (IllegalAccessException e) {
            if (Reflector.canControlMemberAccessible()) {
                field.setAccessible(true);
                field.set(target, args[0]);
            } else {
                throw e;
            }
        }
        return null;
    }

以上就是 Invoker 的三个基本实现,后面我们还会遇到它们,小册也会及时提醒。

1.4 PropertyTokenizer

乍一看,这个 PropertyTokenizer 跟上面的几个类都没有关联,从字面意思上理解,它是叫属性分词器,那它的作用是什么呢?

1.4.1 复杂result映射

之前小册讲解的 result 映射都不是很难很离谱,其实 MyBatis 支持一些很 “离谱” 的映射配置,我们可以来假设一个场景。

假设有一张表的表结构如下:

列名类型说明

id

varchar

主键

name

varchar

记录名称

key1

varchar

自定义属性1

value1

int

自定义属性值1

key2

varchar

自定义属性2

value2

varchar

自定义属性值2

这表也是够奇葩的了吧,对应的实体模型类也有点奇葩:

public class Model {
    private String id;
    private String name;
    private List<Entry> props;
    
    private static class Entry {
        private String key;
        private Object value;
    }
}

上面的一条记录对应下面的一个 Model ,两对自定义属性放到 props 这个集合中。

像这样的一个奇葩的场景,resultMap 应该怎么写呢?MyBatis 表示这根本不在话下:

<resultMap id=="model" class="test.Model">
    <id property="id" column="id" />
    <property name="name" column="name" />
    <property name="props[0].key" column="key1" />
    <property name="props[0].value" column="value1" />
    <property name="props[1].key" column="key2" />
    <property name="props[1].value" column="value2" />
</resultMap>

看,使用 [0] 的写法,就可以精准定义好映射了,回头表中的数据也都能封装过去。

1.4.2 写法的底层设计支持

看到上面的写法,是不是我们又会感叹,MyBatis 的结果集映射真的是非常强大了,感叹之余,我们要打一个问号了:MyBatis 怎么支持这种诡异的写法呢?这既是我们要说的这个 PropertyTokenizer 了。

从类名上看,PropertyTokenizer 译为 “属性分词器” ,它可以将 resultMap 中定义的复杂属性拆解为多个属性段,以便 MyBatis 对这些属性段分别解析处理。

我们可以简单看一下它的设计,这个家伙比较有趣,它实现了 Iterator 接口:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    private String name;
    private final String indexedName;
    private String index;
    private final String children;

而且迭代的类型是它自身,说明 PropertyTokenizer 本身是自迭代的,这个设计相当巧妙,我们可以设想一下有这样一个复杂的 OGNL 表达式:users[0].department.name ,如果使用自迭代的模式解析,则可以有如下的迭代过程:

可以发现,这种迭代方式每次迭代一个 “节点” ,迭代一层之后就像 “扒掉一层衣服” 一样,并包装成另一个全新的对象,PropertyTokenizer 就是这么设计的。

1.4.3 具体逻辑支持

明白了 PropertyTokenizer 的逻辑实现,下面我们来简单看一下代码的逻辑实现。

首先是 PropertyTokenizer 的构造方法:(关键逻辑的注释已标注)

public PropertyTokenizer(String fullname) {
    // 试图将表达式拆分为多块
    int delim = fullname.indexOf('.');
    if (delim > -1) {
        name = fullname.substring(0, delim);
        children = fullname.substring(delim + 1);
    } else {
        // 没有级联属性
        name = fullname;
        children = null;
    }
    indexedName = name;
    // 处理可能存在的索引下标[]
    delim = name.indexOf('[');
    if (delim > -1) {
        index = name.substring(delim + 1, name.length() - 1);
        name = name.substring(0, delim);
    }
}

构造方法中,它会事先把属性名拆分为两部分,以便往下迭代解析。

再就是往下迭代的时候,由于实现了 Iterator 接口,所以只需要关注它的 hasNextnext 方法即可:

@Override
public boolean hasNext() {
    return children != null;
}

@Override
public PropertyTokenizer next() {
    return new PropertyTokenizer(children);
}

可见,它直接用 children 属性构造新的 PropertyTokenizer 对象,这样就实现了自迭代。

1.5 MetaClass

说完了 PropertyTokenizer ,下面就可以继续往下聊这个 MetaClass 了,之所以没有 1.4 立马就继续聊 MetaClass ,是因为这里面有涉及到 PropertyTokenizer 的操作逻辑。

OK 我们还是先简单聊下 MetaClass ,类名译为 “类的元信息” ,它的底层其实就是利用 ReflectorPropertyTokenizer ,实现了对类以及其中复杂的属性表达式的解析,并且还能获取这些属性的一些描述信息。

下面小册针对其中几个比较关键的方法简单解析一下。

1.5.1 findProperty

findProperty ,它就是去尝试着获取一个 Class 中的指定属性是否存在,换句话说,它的目的是检验指定的 Class 中是否存在指定的属性。我们简单看一下它的方法实现:

public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
        // 如果使用了下划线转驼峰,则这个name要去掉下划线(但没有将下划线后的字母改为大写!)
        name = name.replace("_", "");
    }
    return findProperty(name);
}

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

上面的方法会处理下划线转驼峰的情况,下面的方法是处理和判断,只要经过 buildProperty 方法后,StringBuilder 中有内容,就代表找到了属性,否则直接返回 null ,而调用 findProperty 方法的代码,都有一个是否为 null 的判断,这也就证实了我们上面说的 findProperty检验属性是否存在的功能。

如何解析属性是否存在呢?工作逻辑都在 buildProperty 中,我们进去一探究竟:(关键逻辑已标注注释))

private StringBuilder buildProperty(String name, StringBuilder builder) {
    // 借助PropertyTokenizer解析
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        String propertyName = reflector.findPropertyName(prop.getName());
        if (propertyName != null) {
            builder.append(propertyName);
            builder.append(".");
            // 多级属性,再new一个MetaClass继续递归解析
            MetaClass metaProp = metaClassForProperty(propertyName);
            metaProp.buildProperty(prop.getChildren(), builder);
        }
    } else {
        // 单层属性,直接借助反射判断是否存在
        String propertyName = reflector.findPropertyName(name);
        if (propertyName != null) {
            builder.append(propertyName);
        }
    }
    return builder;
}

可以发现逻辑不算复杂,它利用了 PropertyTokenizer 可以解析多层级属性的特性,可以实现级联属性的检查,可能只从源码层面上不是特别好理解,小伙伴们可以实际动手测试一下,Debug 跟一遍流程体会一下。

1.5.2 hasGetter & hasSetter

这两个方法,可以快速检查指定的属性是否包含 getter / setter 方法,它同样支持多层级属性的检查。由于源码的逻辑比较相似,所以这里我们以 hasGetter 方法为例简单过一下:(注释已标注在源码,不再配文字解读)

public boolean hasGetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        // 借助迭代和递归,逐层检查是否有getter
        if (reflector.hasGetter(prop.getName())) {
            MetaClass metaProp = metaClassForProperty(prop);
            return metaProp.hasGetter(prop.getChildren());
        } else {
            return false;
        }
    } else {
        // 单层属性,直接借助Reflector检查是否有getter方法
        return reflector.hasGetter(prop.getName());
    }
}

1.6 ObjectWrapper

MetaClass 类似的还有一个 MetaObject ,不过 MetaObject 针对的是对象实例。MetaObject 的反射,底层其实还是借助 MetaClass ,不过从对象到类,中间有一个过程,MyBatis 设计了一个 **ObjectWrapper** 包装需要反射处理的对象,所以我们先看下这个 ObjectWrapper

1.6.1 接口核心方法

ObjectWrapper 本身是一个接口,这里面定义的核心方法,各位一眼便知:

public interface ObjectWrapper {
    // 根据指定属性取值
    Object get(PropertyTokenizer prop);
    // 根据指定属性赋值
    void set(PropertyTokenizer prop, Object value);
    // 检查对象是否包含指定属性
    String findProperty(String name, boolean useCamelCaseMapping);
    // 检查对象是否包含指定属性的getter方法
    boolean hasSetter(String name);
    // 检查对象是否包含指定属性的setter方法
    boolean hasGetter(String name);

    // ......
}

除了上面的两个方法之外,下面的方法我们不用点进去源码实现,都应该有一种强烈的感觉吧:它不就是调用 MetaClass 吗?对的,它还真就是直接调用的 MetaClass 的方法:(举例)

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

1.6.2 核心实现BeanWrapper

接口只定义行为,具体的实现还是得找实现类,ObjectWrapper 有 3 个实现类:

  • BeanWrapper :基于 Bean 对象的包装

  • MapWrapper :基于 Map 的包装

  • CollectionWrapper :基于 Collection 集合的包装

下面两种我们不是很关心,主要还是研究基于 Bean 对象的 BeanWrapper ,毕竟这才是我们平时用的比较多的。

1.6.2.1 结构和构造方法

先来看下它的结构以及构造方法:

public class BeanWrapper extends BaseWrapper {

    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());
    }

诶?怎么这里又传入了一个 MetaObject 呢?先不要着急,后面我们马上就讲到了,我们只需要注意一点,构造方法中它会将 bean 对象传入,并保存在对象内部。

1.6.2.2 get & set

上面在看 ObjectWrapperget 方法时我们就很容易得知,它是根据一个属性表达式,去对象中获取对应的属性值,BeanWrapper 中实现了该方法:

@Override
public Object get(PropertyTokenizer prop) {
    // 检查属性表达式中是否有 [index] 索引下标值
    if (prop.getIndex() != null) {
        // 有的话,先解析成集合,再取值
        Object collection = resolveCollection(prop, object);
        return getCollectionValue(prop, collection);
    } else {
        // 取普通属性值,往下走
        return getBeanProperty(prop, object);
    }
}

private Object getBeanProperty(PropertyTokenizer prop, Object object) {
    try {
        // 直接取出Invoker,调用相应的方法(getter,或者直接反射取值)
        Invoker method = metaClass.getGetInvoker(prop.getName());
        try {
            return method.invoke(object, NO_ARGUMENTS);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    } // catch ......
}

纵观整段方法实现,其实难度不大,但关键的一点是,注意观察下面的 getBeanProperty ,它并没有做递归处理,说明这个 get 方法只支持单层级属性获取

同样的,set 方法也是如此的套路,各位扫一眼源码实现即可,小册不再展开啰嗦。

@Override
public void set(PropertyTokenizer prop, Object value) {
    if (prop.getIndex() != null) {
        Object collection = resolveCollection(prop, object);
        setCollectionValue(prop, collection, value);
    } else {
        setBeanProperty(prop, object, value);
    }
}

private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
        Invoker method = metaClass.getSetInvoker(prop.getName());
        Object[] params = {value};
        try {
            method.invoke(object, params);
        } // catch ......
    } // catch ......
}

有关其余的方法,小伙伴们可自行借助 IDE 查看,底层实现都比较简单,小册也不再展开了。

1.7 MetaObject

既然 ObjectWrapper 只支持单层级的属性获取 / 设置,那遇到复杂的属性表达式怎么办呢?所以 MyBatis 有对标 MetaClass 的对象级处理:MetaObject

1.7.1 MetaObject的创建

我们先来看下它的创建,一个 MetaObject 中存的东西还蛮多的,不过仔细看下来大部分我们都熟:

private MetaObject(Object object, ObjectFactory objectFactory, 
         ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    // 根据被包装的object决定如何包装
    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);
    }
}

// 静态方法创建MetaObject
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);
    }
}

注意创建 ObjectWrapper 的位置,它会将 this ,也就是 MetaObject 对象本身传入 ObjectWrapper 中,这也就呼应了前面我们看到的 ObjectWrapper 中怎么还要传入 MetaObject 的奇怪现象了。

1.7.2 getValue & setValue

既然 ObjectWrapper ,那 MetaObject 就有必要承担起递归获取的责任了。就跟 MetaClass 递归获取属性一样,MetaObject 递归获取属性值的套路,简直是一个模子出来的:

public Object getValue(String name) {
    // 借助PropertyTokenizer拆分属性
    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 {
        // 单层级属性,直接调用ObjectWrapper
        return objectWrapper.get(prop);
    }
}

相应的,setValue 的套路是几乎一样的,小册不再啰嗦了。

到这里,有关反射系的工具类,小册就列举这么多,内容不少,不过小伙伴们没有必要全部记住,只需要保证后面分析 MyBatis 底层源码时,遇到不会的能回过头来翻看就够了。

后续还会遇到哪些 API 或者工具类,本章的后续章节会持续补充。

最后更新于