Mybatis - 反射工具箱

247 阅读14分钟

概述

为了降低反射使用门槛,MyBatis 内部封装了一个反射工具箱,其中包含了 MyBatis 自身常用的反射操作,MyBatis 其他模块只需要调用反射工具箱暴露的简洁 API 即可实现想要的反射功能。

反射工具箱的具体代码实现位于 org.apache.ibatis.reflection 包中。

Reflector

Reflector 是 MyBatis 反射模块的基础。要使用反射模块操作一个 Class,都会先将该 Class 封装成一个 Reflector 对象,在 Reflector 中缓存 Class 的元数据信息,这可以提高反射执行的效率。

public class Reflector {

    private final Class<?> type;// 对应的Class类型

    // 可读属性的名称集合,可读属性就是存在相应getter方法的属性,初始值为空数组
    private final String[] readablePropertyNames;

    // 可写属性的名称集合,可写属性就是存在相应setter方法的属性,初始值为空数组
    private final String[] writablePropertyNames;

    // 记录了属性相应的setter方法,key是属性名称,value是Invoker对象,它是对setter方法对应
    // Method对象的封装
    private final Map<String, Invoker> setMethods = new HashMap<>();

    // 属性相应的getter方法集合,key是属性名称,value也是Invoker对象
    private final Map<String, Invoker> getMethods = new HashMap<>();

    // 记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
    private final Map<String, Class<?>> setTypes = new HashMap<>();

    // 记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
    private final Map<String, Class<?>> getTypes = new HashMap<>();

    // 记录了默认构造方法
    private Constructor<?> defaultConstructor;

    // 记录了所有属性名称的集合
    private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

    public Reflector(Class<?> clazz) {
        type = clazz;
        // 查找clazz的默认构造方法(无参构造方法),具体实现是通过反射遍历所有构造方法
        addDefaultConstructor(clazz);
        // 处理clazz中的getter方法,填充getMethods集合和getTypes集合
        addGetMethods(clazz);
        // 处理clazz中的setter方法,填充setMethods集合和setTypes集合
        addSetMethods(clazz);
        // 处理没有getter/setter方法的字段
        addFields(clazz);
        // 根据getMethods/setMethods集合,初始化可读/写属性的名称集合
        readablePropertyNames = getMethods.keySet().toArray(new String[0]);
        writablePropertyNames = setMethods.keySet().toArray(new String[0]);

        // 初始化caseInsensitivePropertyMap集合,其中记录了所有大写格式的属性名称
        for (String propName : readablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
    // 省略部分代码
}

1. 核心初始化流程

Reflector 的核心字段:

  • type(Class<?> 类型):该 Reflector 对象封装的 Class 类型。
  • readablePropertyNames、writablePropertyNames(String[] 类型):可读、可写属性的名称集合。
  • getMethods、setMethods(Map<String, Invoker> 类型):可读、可写属性对应的 getter 方法和 setter 方法集合,key 是属性的名称,value 是一个 Invoker 对象。Invoker 是对 Method 对象的封装。
  • getTypes、setTypes(Map<String, Class<?>> 类型):属性对应的 getter 方法返回值以及 setter 方法的参数值类型,key 是属性名称,value 是方法的返回值类型或参数类型。
  • defaultConstructor(Constructor<?> 类型):默认构造方法。
  • caseInsensitivePropertyMap(Map<String, String> 类型):所有属性名称的集合,记录到这个集合中的属性名称都是大写的。

在我们构造一个 Reflector 对象的时候,传入一个 Class 对象,通过解析这个 Class 对象,即可填充上述核心字段,整个核心流程大致可描述为如下。

  1. 用 type 字段记录传入的 Class 对象。
  2. 通过反射拿到 Class 类的全部构造方法,并进行遍历,过滤得到唯一的无参构造方法来初始化 defaultConstructor 字段。这部分逻辑在 addDefaultConstructor() 方法中实现。
  3. 读取 Class 类中的 getter方法,填充上面介绍的 getMethods 集合和 getTypes 集合。这部分逻辑在 addGetMethods() 方法中实现。
  4. 读取 Class 类中的 setter 方法,填充上面介绍的 setMethods 集合和 setTypes 集合。这部分逻辑在 addSetMethods() 方法中实现。
  5. 读取 Class 中没有 getter/setter 方法的字段,生成对应的 Invoker 对象,填充 getMethods 集合、getTypes 集合以及 setMethods 集合、setTypes 集合。这部分逻辑在 addFields() 方法中实现。
  6. 根据前面三步构造的 getMethods/setMethods 集合的 keySet,初始化 readablePropertyNames、writablePropertyNames 集合。
  7. 遍历构造的 readablePropertyNames、writablePropertyNames 集合,将其中的属性名称全部转化成大写并记录到 caseInsensitivePropertyMap 集合中。

2. 核心方法解析

addGetMethods() 方法和 addSetMethods() 方法,它们分别用来解析传入 Class 类中的 getter方法和 setter() 方法,两者的逻辑十分相似。

以 addGetMethods() 方法为例深入分析,其主要包括如下三个核心步骤。

private void addGetMethods(Class<?> clazz) {
        Map<String, List<Method>> conflictingGetters = new HashMap<>();
        // 定位所有方法信息,获取当前 Class 类以及父类中定义的所有方法的唯一签名,以及相应的Method对象。
        Method[] methods = getClassMethods(clazz);
        // 按照 Java 的规范,从步骤 1 中返回的 Method 数组中查找 getter方法,将其记录到 conflictingGetters 集合中。
        // conflictingGetters集合(HashMap<String, List<Method>>()类型)的key为属性名称,value是该属性对应的getter方法集合。
        Arrays.stream(methods)
                .filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
                .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
        resolveGetterConflicts(conflictingGetters);
    }

第一步,获取方法信息。 这里会调用 getClassMethods() 方法获取当前 Class 类的所有方法的唯一签名(注意一下,这里同时包含继承自父类以及接口的方法),以及每个方法对应的 Method 对象。

在递归扫描父类以及父接口的过程中,会使用 Map<String, Method> 集合记录遍历到的方法,实现去重的效果,其中 Key 是对应的方法签名,Value 为方法对应的 Method 对象。生成的方法签名的格式如下:

返回值类型#方法名称:参数类型列表

例如,addGetMethods(Class) 方法的唯一签名是:

java.lang.String#addGetMethods:java.lang.Class

这里生成的方法签名是包含返回值的,可以作为该方法全局唯一的标识。

第二步,按照 Java 的规范,从上一步返回的 Method 数组中查找 getter 方法,将其记录到 conflictingGetters 集合中。 这里的 conflictingGetters 集合(HashMap<String, List>()类型)中的 Key 为属性名称,Value 是该属性对应的 getter 方法集合。

为什么一个属性会查找到多个 getter 方法呢?这主要是由于类间继承导致的,在子类中我们可以覆盖父类的方法,覆盖不仅可以修改方法的具体实现,还可以修改方法的返回值,getter 方法也不例外,这就导致在第一步中产生了两个签名不同的方法。

第三步,解决方法签名冲突。 这里会调用 resolveGetterConflicts() 方法对这种 getter 方法的冲突进行处理,处理冲突的核心逻辑其实就是比较 getter 方法的返回值,优先选择返回值为子类的 getter 方法

在 resolveGetterConflicts() 方法处理完上述 getter 方法冲突之后,会为每个 getter 方法创建对应的 MethodInvoker 对象,然后统一保存到 getMethods 集合中。同时,还会在 getTypes 集合中维护属性名称与对应 getter 方法返回值类型的映射。

在通过 addGetMethods() 和 addSetMethods() 方法,完成 Class 类中 getter/setter 方法的处理之后,会继续调用 addFields() 方法处理没有 getter/setter 方法的字段。

    private void addFields(Class<?> clazz) {
        // 获取clazz中定义的全部字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 当setMethods集合不包含同名属性时,将其记录到setMethods集合和setTypes集合
            if (!setMethods.containsKey(field.getName())) {
                // issue #379 - removed the check for final because JDK 1.5 allows
                // modification of final fields through reflection (JSR-133). (JGB)
                // pr #16 - final static can only be set by the classloader
                int modifiers = field.getModifiers();
                if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
                    // addSetField()方法的主要功能是填充setMethods集合和setTypes集合,
                    // 与addGetMethod()方法类似,不再贴出代码
                    addSetField(field);
                }
            }
            if (!getMethods.containsKey(field.getName())) {
                addGetField(field);
            }
        }
        if (clazz.getSuperclass() != null) {
            addFields(clazz.getSuperclass());
        }
    }

这里我们以处理没有 getter 方法的字段为例,addFields() 方法会为这些字段生成对应的 GetFieldInvoker 对象并记录到 getMethods 集合中,同时也会将属性名称和属性类型记录到 getTypes 集合中。

3. Invoker

在 Reflector 对象的初始化过程中,所有属性的 getter/setter 方法都会被封装成 MethodInvoker 对象,没有 getter/setter 的字段也会生成对应的 Get/SetFieldInvoker 对象。

public interface Invoker {
   // 调用底层封装的Method方法或是读写指定的字段
   Object invoke(Object target, Object[] args);
   Class<?> getType(); // 返回属性的类型
}

Invoker 接口的继承关系:

1.png

MethodInvoker 是通过反射方式执行底层封装的 Method 方法(例如,getter/setter 方法)完成属性读写效果的,Get/SetFieldInvoker 是通过反射方式读写底层封装的 Field 字段,进而实现属性读写效果的。

4. ReflectorFactory

Reflector 初始化过程会有一系列的反射操作,为了提升 Reflector 的初始化速度,MyBatis 提供了 ReflectorFactory 这个工厂接口对 Reflector 对象进行缓存,其中最核心的方法是用来获取 Reflector 对象的 findForClass() 方法。

public interface ReflectorFactory {

  boolean isClassCacheEnabled();

  void setClassCacheEnabled(boolean classCacheEnabled);

  Reflector findForClass(Class<?> type);
}

DefaultReflectorFactory 是 ReflectorFactory 接口的默认实现,它默认会在内存中维护一个 ConcurrentHashMap<Class<?>, Reflector> 集合(reflectorMap 字段)缓存其创建的所有 Reflector 对象。

public class DefaultReflectorFactory implements ReflectorFactory {

  // 是否开启 Reflector 对象 缓存,默认开启
  private boolean classCacheEnabled = true;
  
  // 缓存其创建的所有 Reflector 对象
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

  public DefaultReflectorFactory() {
  }
  // 省略部分代码
} 

在其 findForClass() 方法实现中,首先会根据传入的 Class 类查询 reflectorMap 缓存,如果查找到对应的 Reflector 对象,则直接返回;否则创建相应的 Reflector 对象,并记录到 reflectorMap 中缓存,等待下次使用。

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

默认对象工厂

ObjectFactory 是 MyBatis 中的反射工厂,其中提供了两个 create() 方法的重载,我们可以通过两个 create() 方法创建指定类型的对象。

public interface ObjectFactory {
  // 设置配置信息
  default void setProperties(Properties properties) {
  }
  // 通过无参构造器创建指定类的对象
  <T> T create(Class<T> type);
  // 根据参数列表,从指定类型中选择合适的构造器创建对象
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
  // 检测指定类型是否为集合类型,主要处理java.util.Collection及其子类
  <T> boolean isCollection(Class<T> type);
}

DefaultObjectFactory 是 ObjectFactory 接口的默认实现,其 create() 方法底层是通过调用 instantiateClass() 方法创建对象的。instantiateClass() 方法会通过反射的方式根据传入的参数列表,选择合适的构造函数实例化对象。

除了使用 DefaultObjectFactory 这个默认实现之外,我们还可以在 mybatis-config.xml 配置文件中配置自定义 ObjectFactory 接口扩展实现类。

属性解析工具

reflection.property 包下的三个属性解析相关的工具类:

  • PropertyTokenizer 工具类负责解析由“.”和“[]”构成的表达式。PropertyTokenizer 继承了 Iterator 接口,可以迭代处理嵌套多层表达式。
  • PropertyCopier 是一个属性拷贝的工具类,提供了与 Spring 中 BeanUtils.copyProperties() 类似的功能,实现相同类型的两个对象之间的属性值拷贝,其核心方法是 copyBeanProperties() 方法。
  • PropertyNamer 工具类提供的功能是转换方法名到属性名,以及检测一个方法名是否为 getter 或 setter 方法。

MetaClass

MetaClass 提供了获取类中属性描述信息的功能,底层依赖前面介绍的 Reflector,在 MetaClass 的构造方法中会将传入的 Class 封装成一个 Reflector 对象,并记录到 reflector 字段中,MetaClass 的后续属性查找都会使用到该 Reflector 对象。

public class MetaClass {

    private final ReflectorFactory reflectorFactory;
    private final Reflector reflector;

    private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
        this.reflectorFactory = reflectorFactory;
        this.reflector = reflectorFactory.findForClass(type);
    }
	// 省略部分代码    
}

MetaClass 中的 findProperty() 方法是实现属性查找的核心方法,它主要处理了“.”导航的属性查找,该方法会用前文介绍的 PropertyTokenizer 解析传入的 name 表达式,该表达式可能通过“.”导航多层,例如,order.deliveryAddress.customer.name。

MetaClass 会逐层处理这个表达式,首先通过 Order 类型对应的 Reflector 查找 deliveryAddress 属性,查找成功之后,根据 deliveryAddress 属性的类型(即 Address 类型)创建对应的 MetaClass 对象(以及底层的 Reflector 对象),再继续查找其中的 customer 属性,如此递归处理,直至最后查找到 Customer 中的 name 属性。这部分递归查找逻辑位于 MetaClass.buildProperty() 方法中。

    private StringBuilder buildProperty(String name, StringBuilder builder) {
        // 解析属性表达式
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) { // 是否还有子表达式要处理
            // 查找PropertyTokenizer解析得到的属性名称
            String propertyName = reflector.findPropertyName(prop.getName());
            if (propertyName != null) { // 追加属性名
                builder.append(propertyName);
                builder.append(".");
                // 查找指定属性对应的Class,为该属性创建对应的MetaClass对象
                MetaClass metaProp = metaClassForProperty(propertyName);
                // 递归解析PropertyTokenizer解析得到的子表达式字段,并将解析结果添加到builder中保存
                metaProp.buildProperty(prop.getChildren(), builder);
            }
        } else { // 递归出口
            String propertyName = reflector.findPropertyName(name);
            if (propertyName != null) {
                builder.append(propertyName);
            }
        }
        return builder;
    }

在上述 MetaClass 查找属性的过程中,还会调用 hasGetter() 和 hasSetter() 方法负责判断属性表达式中指定的属性是否有对应的 getter/setter 方法。这两个方法也是先通过 PropertyTokenizer 解析传入的 name 表达式,然后进行递归查询,在递归查询中会依赖 Reflector.hasGetter() 方法查找前文介绍的 getMethods 集合或 setMethods 集合,查找属性对应的 getter/setter 方法。

MetaClass 中的其他方法实现也都大多是依赖 PropertyTokenizer 解析表达式,然后递归查找,查找过程会依赖 Reflector 的相关方法。

ObjectWrapper

MetaClass 中封装的是 Class 元信息,ObjectWrapper 封装的则是对象元信息。在 ObjectWrapper 中抽象了一个对象的属性信息,并提供了查询对象属性信息的相关方法,以及更新属性值的相关方法。

public interface ObjectWrapper {
  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应getter方法,
  // 如果封装的是集合类,则获取指定key或下标对应的value值
  Object get(PropertyTokenizer prop);
  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应setter方法,
  // 如果封装的是集合类,则设置指定key或下标对应的value值
  void set(PropertyTokenizer prop, Object value);
  // 查找属性表达式指定的属性,第二个参数表示是否将name指定的属性名称修改为驼峰命名方式
  String findProperty(String name, boolean useCamelCaseMapping);
  // 查找可写属性的名称集合
  String[] getGetterNames();
  // 查找可读属性的名称集合
  String[] getSetterNames();
  // 解析属性表达式指定属性的setter方法的参数类型
  Class<?> getSetterType(String name);
  // 解析属性表达式指定属性的getter方法的返回值类型
  Class<?> getGetterType(String name);
  // 判断属性表达式指定属性是否有getter/setter方法
  boolean hasSetter(String name);
  boolean hasGetter(String name);
  // 为属性表达式指定的属性创建相应的MetaObject对象
  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
  // 封装的对象是否为Collection类型
  boolean isCollection();
  // 调用Collection对象的add()方法
  void add(Object element);
  // 调用Collection对象的addAll()方法
  <E> void addAll(List<E> element);
}

ObjectWrapper 的实现类:

2.png

BaseWrapper 是 ObjectWrapper 接口的抽象实现,其中只有一个 MetaObject 类型的字段。BaseWrapper 为子类实现了 resolveCollection()、getCollectionValue() 和 setCollectionValue() 三个针对集合对象的处理方法。其中,resolveCollection() 方法会将指定属性作为集合对象返回,底层依赖 MetaObject.getValue()方法实现。getCollectionValue() 方法和 setCollectionValue() 方法会解析属性表达式的下标信息,然后获取/设置集合中的对应元素,这里解析属性表达式依然是依赖前面介绍的 PropertyTokenizer 工具类。

BeanWrapper 继承了 BaseWrapper 抽象类,底层除了封装了一个 JavaBean 对象之外,还封装了该 JavaBean 类型对应的 MetaClass 对象,以及从 BaseWrapper 继承下来的 MetaObject 对象。

在 get() 方法和 set() 方法实现中,BeanWrapper 会根据传入的属性表达式,获取/设置相应的属性值。以 get() 方法为例,首先会判断表达式中是否含有数组下标,如果含有下标,会通过 resolveCollection() 和 getCollectionValue() 方法从集合中获取相应元素;如果不包含下标,则通过 MetaClass 查找属性名称在 Reflector.getMethods 集合中相应的 GetFieldInvoker,然后调用 Invoker.invoke() 方法读取属性值。

CollectionWrapper 是 ObjectWrapper 接口针对 Collection 集合的一个实现,其中封装了Collection 集合对象,只有 isCollection()、add()、addAll() 方法以及从 BaseWrapper 继承下来的方法是可用的,其他方法都会抛出 UnsupportedOperationException 异常。

MapWrapper 是针对 Map 类型的一个实现。

MetaObject

ObjectWrapper 实现了读写对象属性值、检测getter/setter 等基础功能,在分析 BeanWrapper 等实现类时,我们可以看到其底层会依赖 MetaObject。在 MetaObject 中维护了一个 originalObject 字段指向被封装的 JavaBean 对象,还维护了该 JavaBean 对象对应的 ObjectWrapper 对象(objectWrapper 字段)。

public class MetaObject {

    private final Object originalObject;
    private final ObjectWrapper objectWrapper;
    private final ObjectFactory objectFactory;
    private final ObjectWrapperFactory objectWrapperFactory;
    private final ReflectorFactory 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);
        }
    }
    // 省略部分代码
}

MetaObject 和 ObjectWrapper 中关于类级别的方法,例如,hasGetter() 方法、hasSetter() 方法、findProperty() 方法等,都是直接调用 MetaClass 或 ObjectWrapper 的对应方法实现的。其他关于对象级别的方法,都是与 ObjectWrapper 配合实现,例如 MetaObject.getValue()/setValue() 方法等。

以 getValue() 方法为例,该方法首先根据 PropertyTokenizer 解析指定的属性表达式,如果该表达式是包含“.”导航的多级属性查询,则获取子表达式并为其对应的属性对象创建关联的 MetaObject 对象,继续递归调用 getValue() 方法,直至递归处理结束,递归出口会调用 ObjectWrapper.get() 方法获取最终的属性值。

在 MetaObject 中,setValue() 方法的核心逻辑与 getValue() 方法基本类似,也是递归查找。但是,其中有一个不同之处需要你注意:如果需要设置的最终属性值不为空时,在递归查找 setter() 方法的过程中会调用 ObjectWrapper.instantiatePropertyValue() 方法初始化递归过程中碰到的任意空对象,但如果碰到为空的集合元素,则无法通过该方法初始化。ObjectWrapper.instantiatePropertyValue() 方法实际上是依赖 ObjectFactory 接口的 create()方法(默认实现是 DefaultObjectFactory )创建相应类型的对象。

总结

反射工具箱中最核心、最底层的是 Reflector 类的核心实现。以及在 Reflector 基础之上提供的各种工具类,其中包括 ObjectFactory 工厂类、ObjectWrapper 包装类以及记录元数据的 MetaClass、MetaObject 。