基础支持层——反射模块

2019-10-25 书籍 《MyBatis技术内幕》

MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇介绍MyBatis的反射模块

反射工具箱

Mybatis 在进行参数处理、结果映射等操作时,会涉及大量的反射操作。Java 中的反射虽然功能强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码,Mybatis 提供了专门的反射模块,该模块位于 org.apache.ibatis.reflection 包中,它对常见的反射操作做了进一步封装,提供了更加简洁方便的反射 API。

Reflector & ReflectorFactory

Reflector是MyBatis中反射模块的基础,每隔Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的累的元信息。

Reflector中各个字段的含义如下:

Private Class <?> type; //对应的 C1 ass 类型

//可读属性的名称集合,可读属性就是存在相应 getter 方法的属性,初始值为空数组
private String I readablepropertynames= EMPTY STRING ARRAY

//可写属性的名称集合,可写属性就是存在相应 setter 方法的属性,初始值为空数组
private String  [writeablepropertynames EMPTY STRING ARRAY;
//记录了属性相应的 setter 方法,key 是属性名称,value 是 Invoker 对象,它是对 setter 方法对应 
// Me thod 对象的封装,后面会详细介绍
private Map <String, Invoker> setmethods =new Hashmap <String, Invoker>  ();

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

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

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

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

//记录了所有属性名称的集合
private Map <string, String> caseinsensitivepropertymap-new Hashmap <String, String> ();

在Reflector的构造方法中会解析制定的Class对象,并填充上述集合。具体如下:

public Reflector(Class<?> clazz) {
  type = clazz; // 初始化type字段
  // 查找clazz的默认构造方法,具体实现是通过反射遍历所有构造方法
  addDefaultConstructor(clazz);
  addGetMethods(clazz); // 处理clazz中的getter方法,填充getMethods集合和getTypes集合
  addSetMethods(clazz); // 处理clazz中的setter方法,填充setMethods集合和setTypes集合
  addFields(clazz); // 处理没有getter/setter方法的字段

  // 根据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);
  }
}

Reflector.Addgetmethodso()方法主要负责解析类中定义的 getter 方法,Reflector. addSetMethods()方法负责解析类中定义的 setter 方法,两者的逻辑类似。

Reflector. Addgetmethods() 方法有如下三个核心步骤。

  1. 首先,调用Reflector.getClassMethods()方法获取当前类及其父类中定义的所有方法的唯一签名以及响应的Method对象。

    private Method[] getClassMethods(Class<?> clazz) {
      // 用于记录制定类中定义的全部方法的唯一签名以及对应的Method对象
      Map<String, Method> uniqueMethods = new HashMap<>();
      Class<?> currentClass = clazz;
      while (currentClass != null && currentClass != Object.class) {
    
        // 记录currentClass这个类中定义的全部方法
        addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
    
        // we also need to look for interface methods -
        // because the class may be abstract
        // 记录接口中定义的方法
        Class<?>[] interfaces = currentClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
          addUniqueMethods(uniqueMethods, anInterface.getMethods());
        }
        // 获取父类,继续while循环
        currentClass = currentClass.getSuperclass();
      }
    
      Collection<Method> methods = uniqueMethods.values();
    
      return methods.toArray(new Method[0]);// 转换成Methods数组返回
    }
  2. 然后,按照JavaBean的规范,从Reflector.getClassMethods()方法返回的Method数组中查找该类中定义的getter方法,将其记录到conflictingGetters集合中。conflictingGetters集合的key为属性名称,value是该属性对应的getter方法的集合。

  3. 当子类覆盖了父类的getter方法且返回值发生变化时,在步骤1中就会产生两个签名不同的方法。

TypeParameterResolver

在开 始介绍 TypeParameterResolver之前,先简 单 介绍 一下Type接口的基础 知识 。Type是所有类 型的父接口,它 有四个 子接口和一个 实 现 类 ,如图所示。

image-20200606160310736

下面来 看这 些子接口和子类 所代表的类 型。

  • Class比较 常见 ,它 表示的是原始类 型。

    Class类 的对 象表示JVM中的一个 类 或接口, 每个 Java类 在JVM里都表现 为 一个 Class对 象。

    在程序中可以通过 “类名.class”、“对象.getClass()”或是“Class.forName(“类 名”)”等方式获 取Class对 象。

    数 组 也被映射为 Class对 象,所有元素类 型相同且维 数 相同的数 组 都共享同一个 Class对 象。

  • ParameterizedType 表示的是参 数 化类 型,例如 List、 Map<Integer,String>、 Service这 种 带 有泛型的类 型。

    ParameterizedType接口中常用的方法有三个 ,分别 是:

    • Type getRawType()—–返回参 数 化类 型中的原始类 型,例如List的原始类 型为 List。
    • Type[] getActualTypeArguments()—–获 取参 数 化类 型的类 型变 量或是实 际 类 型列 表,例如Map<Integer,String>的实 际 泛型列表Integer和 String。需要注意的是, 该 列表的元素类 型都是Type,也就是说 ,可能存在多层 嵌套的情况 。
    • Type getOwnerType()—–返回是类 型所属 的类 型,例如存在A类 ,其中定义 了 内 部类 InnerA,则 InnerA属 的类 型为 A,如果是顶 层 类 型则 返回null。 这 种 关 系比较 常见 的示例是Map<K,V>接口与 Map.Entry<K,V>接口,Map<K,V> 接 口 是Map<K,V>接口的所有者。
  • TypeVariable表示的是类 型变 量,它 用来 反映在JVM编 译 该 泛型前的信息。

    例如List 中的T就是类 型变 量,它 在编 译 时 需被转 换 为 一个 具体 的类 型后才能正常使用。

    该 接口中常用的方法有三个 ,分别 是:

    • Type[] getBounds()—–获 取类 型变 量的上边 界,如果未明确 声 明上边 界则 默认 为 Object。

      例如 class Test中 K 的上界就是 Person。

    • D getGenericDeclaration()—–获 取声 明该 类 型变 量的原始类 型,例 如 class Test 中 的原始类 型是 Test。

    • String getName()— 获 取在源码 中定义 时 的名字,上例中为 K。

  • GenericArrayType 表示的是数 组 类 型且组 成元素是 ParameterizedType 或 TypeVariable。

    例如 List或T[]。该 接口只有 Type getGenericComponentType()—个 方法,它 返回数 组 的组 成元素。

  • WildcardType 表示的是通配符泛型,例如? extends Number? uper Integer

    WildcardType接口有两 个 方法,分别 是:

    • Type[] getUpperBounds()—-返回泛型变 量的上界。
    • Type[] getLowerBounds()—- 返回泛型变 量的下界。

    回到对 TypeParameterResolve,它 是一个 工具类 ,提供了一系列静 态 方法来 解析指定类 中的字段、方法返回值 或方法参 数 的类 型。TypeParameterResolver中各个 静 态 方法之间 的调 用关 系大致如下图 所示,为 保持清 晰 ,其中递 归 调 用没 有表现 出来 ,在后面 的代码 分析过 程中会 进 行强调 。

image-20200606162023961

TypeParameterResolver 中 通 过 resolveFieldType()方 法 、 resolveRetumType()方 法 、 resolveParamTypes()方法分别 解析字段类 型、方法返回值 类 型和方法参 数 列表中各个 参 数 的类 型。 这 三个 方法的逻 辑 基本类 似,这 里以resolveFieldType()方法为 例进 行介绍 ,TypeParameterResolver.resolveFieldType()方法的具体 实 现 如下:

/**
   * @return The field type as {@link Type}. If it has type parameters in the declaration,
   *         they will be resolved to the actual runtime {@link Type}s.
   */
public static Type resolveFieldType(Field field, Type srcType) {
  Type fieldType = field.getGenericType();
  Class<?> declaringClass = field.getDeclaringClass();
  return resolveType(fieldType, srcType, declaringClass);
}

上述三个 方法都会 调 用resolveType()方法,该 方法会 根据其第一个 参 数 的 类 型,即 字段、方法返回值 或方法参 数 的类 型,选 择 合适的方法进 行解析。resolveType()方法的 第二个 参 数 表示查 找 该 字段、返回值 或方法参 数 的起始位置。第三个 参 数 则 表示该 字段、方法 定义 所在的类 。TypeParameterResolver.resolveType()方法代码 如下:

private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
  if (type instanceof TypeVariable) {
    // 解析TypeVariable类型
    return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
  } else if (type instanceof ParameterizedType) {
    // 解析ParameterizedType类型
    return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
  } else if (type instanceof GenericArrayType) {
    // 解析genericArrayType类型
    return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
  } else {
    return type; // class类型
  }
  // 字段、返回值 、参 数 不可能直接定义 成WildcardType类 型,但可以嵌套在别 的类 型中
}

为了便于理解,通过一个示例分析resolveType方法, 假设有三个类 ClassA、SubClassA、TestType,代码如下:

ClassA

public class ClassA <K, V> { 
  protected Map<K, V> map;
    // ••• map 的 getter/setter 方 法 (略 )
}

SubClassA

public class SubClassA <T> extends ClassA<T,T> { 
  // ...... 
}

TestType

public class TestType {

  SubClassA<Long> sa = new SubClassA();

  public static void main(String[] args) throws NoSuchFieldException {
    Field f = ClassA.class.getDeclaredField("map");
    System.out.println(f.getGenericType());
    System.out.println(f.getGenericType() instanceof ParameterizedType);
    // 输出:
    // java.util.Map<K, V>
    // true

    // 解析SubA<Long>(ParameterizedType类 型)中的map字段,注意:ParameterizedTypelmpl是
    // 在 sun.reflect.generics.reflectiveObjects 包下的 ParameterizedType接口实 现

    Type type = TypeParameterResolver.resolveFieldType(
      f, ParameterizedTypelmpl.make(SubClassA.class, 
                                    new Type[]{Long.class}, TestType.class));

    //也可以使用下面的方式生成上述ParameterizedType对 象,
    // 并 调 用 TypeParameterResolver.resolveFieldType ()方 法 :
    //
    // TypeParameterResolver.resolveFieldType(f,
    // TestType.class.getDeclaredField("sa").getGenericType());
    System.out.println(type.getClass());
    // 输 出 :class TypeParameterResolver$ParameterizedTypelmpl

    // 注意,TypeParameterResolver$ParameterizedTypeImpl是ParameterizedType接口的实 现 
    ParameterizedType p = (ParameterizedType) type; 
    System.out.println(p.getRawType());
    // 输 出 :interface java.util.Map

    System.out.println(p.getOwnerType());
    // 输 出:null

    for (Type t : p .getActualTypeArguments()) {
      System.out.println(t);
    }
    //输 出:
    // class java.lang.Long
    // class java.lang.Long
  }
}

根据前面的Type接口的介绍,上例中ClassA.map字段声明的类型Map<K,V>是ParamelerizedType类型,resolveType()方法回调用resolvParameterizedType()方法进行解析。

首先介绍resolveParameterizedType()方法的参数:

  • 第一个参数是待解析的ParameterizedType类型
  • 第二个参数是解析操作的起始类型
  • 第三个参数为定义该字段或方法的类的Class对象

在该示例中第一个参数是Map<K,V>对应的ParameterizedType对象,第二个参数是TypeTest.SubA对应的ParameterizedType对象,第三个参数是ClassA(声明map字段的类)相应的Class对象。

继续分析scanSuperTypes()方法,该方法回递归整个继承结构并完成类型变量的解析。在该示例之中,第一个参数K对应的TypeVariable对象,第二个参数是TypeText.SubA对应的ParameterizedType对象,第三个参数是ClassA(声明map字段的类)对应的Class对象,第四个参数是SubClassA对应的Class对象,第五个参数是Class<T,T>对应的ParameterizedType对象。

scanSuperTypes()方法的具体实现如下:

private static Type scanSuperTypes(
  TypeVariable<?> typeVar, Type srcType, 
  Class<?> declaringClass, Class<?> clazz, Type superclass) {
    Type result = null;
    if (superclass instanceof ParameterizedType) {
      ParameterizedType parentAsType = (ParameterizedType) superclass;
      Class<?> parentAsClass = (Class<?>) parentAsType.getRawType();
      if (declaringClass == parentAsClass) {
        Type[] typeArgs = parentAsType.getActualTypeArguments();
        TypeVariable<?>[] declaredTypeVars = 
          declaringClass.getTypeParameters();
        for (int i = 0; i < declaredTypeVars.length; i++) {
          if (declaredTypeVars[i] == typeVar) {
            if (typeArgs[i] instanceof TypeVariable) {
              TypeVariable<?>[] typeParams = clazz.getTypeParameters();
              for (int j = 0; j < typeParams.length; j++) {
                if (typeParams[j] == typeArgs[i]) {
                  if (srcType instanceof ParameterizedType) {
                    result = ((ParameterizedType) srcType).getActualTypeArguments()[j];
                  }
                  break;
                }
              }
            } else {
              result = typeArgs[i];
            }
          }
        }
      } else if (declaringClass.isAssignableFrom(parentAsClass)) {
        result = resolveTypeVar(typeVar, parentAsType, declaringClass);
      }
    } else if (superclass instanceof Class) {
      if (declaringClass.isAssignableFrom((Class<?>) superclass)) {
        result = resolveTypeVar(typeVar, superclass, declaringClass);
      }
    }
    return result;
  }

下图展示了scanSuperTypes()方法解析类型变量的核心逻辑。

image-20200607094904927

ObjectFactory

MyBatis中很多模块会使用到ObjectFactory接口,该接口提供了多个create()方法的重载,通过这些create()方法可以创建指定类型的对象。ObjectFactory的定义如下:

public interface ObjectFactory {

  /**
   * Sets configuration properties.
   * 设置配置信息
   * @param properties configuration properties
   */
  void setProperties(Properties properties);

  /**
   * Creates a new object with default constructor. 
   * 通过无参构造器创建指定类的对象
   * @param type Object type
   * @return
   */
  <T> T create(Class<T> type);

  /**
   * Creates a new object with the specified constructor and params.
   * 根据参数列表,从指定类型中选择合适的构造器创建对象
   * @param type Object type
   * @param constructorArgTypes Constructor argument types
   * @param constructorArgs Constructor argument values
   * @return
   */
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   * 检测指定类型是否为集合类型,主要处理java.util.Collectiopn及其子类
   * 
   * @param type Object type
   * @return whether it is a collection or not
   * @since 3.1.0
   */
  <T> boolean isCollection(Class<T> type);

}

DefaultObjectFactoryMyBatis提供的ObjectFactory接口的唯一实 现 ,它 是一个 反射工厂 , 其 create()方法通过 调 用 instantiateClass()方法实 现 。

DefaultObjectFactory.instantiateClass()方法会 根据传 入的参 数 列表选 择 合适的构 造函数 实 例化对 象,具体 实 现 如下:

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null && !constructorArgs.isEmpty()) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }

除了使用MyBatis提供的DefaultObjectFactory实现,还可以在mybatis-config.xml配置文件中指定自定义的ObjectFactory接口实现累, 从而实现功能上的扩展。

Property工具类

org.apache.ibatis.reflection.property 包下,提供了 PropertyCopier、PropertyNamer、PropertyTokenizer 三个属性相关的工具类。

PropertyTokenizer

在使用MyBatis的过程中,经常会碰到一些属性表达式,例如,在查询用户(User)的订单(Order)的结果集如下表所示:

user_name order item1 item2
Mary 124640 iPhone 8 plus MacBook Pro
Lisa 46546 iPhone 11 Pro Mac Pro

对象模型如下:

image-20200607100948201

假设现在需要将结果集中的item1列雨用户第一个订单(Order)的第一条目(Item)的名称映射,item2与用户第一个订单的(Order)的第二条目(Item)的名称映射(这里仅仅是一个示例,在实际生产中很少这样的设计),我们可以得到下面的映射规则:

<resultMap id="rm4testProTool" type="User">
    <id column="id" property="id" />
    <result property="orders[0].items[0].name" column="iteml" /> 
    <result property="orders[0].items[1].name" column="item2" />
</resultMap>

在上面的例子中,orders[0].items[0].name这种由.[]组成的表达式是由PropertyTokenizer进行解析的。以下是此类的源码:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  // 当前表达式的名称
  private String name;

  // 当前表达式的索引名
  private final String indexedName;

  // 索引下标
  private String index;

  // 子表达式
  private final String children;

  public PropertyTokenizer(String fullname) {
    // 查找 . 的位置
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      // 初始化 name
      name = fullname.substring(0, delim);
      // 初始化children
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    // 初始化indexname
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
      // 初始化index
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }

  public String getName() {
    return name;
  }

  public String getIndex() {
    return index;
  }

  public String getIndexedName() {
    return indexedName;
  }

  public String getChildren() {
    return children;
  }

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

  // next方法中会创建PropertyTokenizer对象并解析children字段记录的子表达式
  @Override
  public PropertyTokenizer next() {
    return new PropertyTokenizer(children);
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
  }
}

PropertyTokenizer继承了Iterator接口,它可以迭代处理嵌套多层表达式。

next()方法中会创建PropertyTokenizer对象并解析children字段记录的子表达式。

继续使用订单示例进行说明,描述解析属性表达式orders[0].items[0].name的迭代过程:

image-20200607102830771

PropertyNamer

PropertyNamer是另一个工具类,提供了下列静态方法帮助完成方法名到属性名的转换,以及多种检测操作。

public final class PropertyNamer {

  private PropertyNamer() {
    // Prevent Instantiation of Static Class
  }

  // 将方法名转换成属性名
  public static String methodToProperty(String name) {
    if (name.startsWith("is")) {
      name = name.substring(2);
    } else if (name.startsWith("get") || name.startsWith("set")) {
      name = name.substring(3);
    } else {
      throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
    }

    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
    }

    return name;
  }

  public static boolean isProperty(String name) {
    return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
  }

  public static boolean isGetter(String name) {
    return name.startsWith("get") || name.startsWith("is");
  }

  public static boolean isSetter(String name) {
    return name.startsWith("set");
  }

}

PropertyCopier

PropertyCopier是一个属性拷贝的工具类,核心方法是copyBeanProperties()方法,主要实现相同类型的两个对象之间的属性值拷贝,具体如下:

public final class PropertyCopier {

  private PropertyCopier() {
    // Prevent Instantiation of Static Class
  }

  public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
    Class<?> parent = type;
    while (parent != null) {
      final Field[] fields = parent.getDeclaredFields();
      for(Field field : fields) {
        try {
          field.setAccessible(true);
          field.set(destinationBean, field.get(sourceBean));
        } catch (Exception e) {
          // Nothing useful to do, will only fail on final fields, which will be ignored.
        }
      }
      // 继续拷贝父类中定义的字段
      parent = parent.getSuperclass();
    }
  }

}

MetaClass

org.apache.ibatis.reflection.MetaClass ,类的元数据,基于 Reflector 和 PropertyTokenizer ,提供对指定类的各种骚操作。实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。

代码:

public class MetaClass {

  // 用于缓存Reflector对象
  private final ReflectorFactory reflectorFactory;
  // 创建MetaClass时会指定一个类,该Reflector对象会用于记录该类相关的元信息
  private final Reflector reflector;

  // MetaClass的构造方法是使用private修饰的
  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    // 创建Reflector对象
    this.reflector = reflectorFactory.findForClass(type);
  }

  // 使用静态方法创建MetaClass对象
  public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }

  public MetaClass metaClassForProperty(String name) {
    Class<?> propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType, reflectorFactory);
  }

  public String findProperty(String name) {
    // 委托给buildProperty()方法实现
    StringBuilder prop = buildProperty(name, new StringBuilder());
    return prop.length() > 0 ? prop.toString() : null;
  }

  public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
      name = name.replace("_", "");
    }
    return findProperty(name);
  }

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

  public String[] getSetterNames() {
    return reflector.getSetablePropertyNames();
  }

  public Class<?> getSetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaClass metaProp = metaClassForProperty(prop.getName());
      return metaProp.getSetterType(prop.getChildren());
    } else {
      return reflector.getSetterType(prop.getName());
    }
  }

  public Class<?> getGetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaClass metaProp = metaClassForProperty(prop);
      return metaProp.getGetterType(prop.getChildren());
    }
    // issue #506. Resolve the type inside a Collection Object
    return getGetterType(prop);
  }

  private MetaClass metaClassForProperty(PropertyTokenizer prop) {
    Class<?> propType = getGetterType(prop);
    return MetaClass.forClass(propType, reflectorFactory);
  }

  private Class<?> getGetterType(PropertyTokenizer prop) {
    Class<?> type = reflector.getGetterType(prop.getName());
    if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
      Type returnType = getGenericGetterType(prop.getName());
      if (returnType instanceof ParameterizedType) {
        Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
        if (actualTypeArguments != null && actualTypeArguments.length == 1) {
          returnType = actualTypeArguments[0];
          if (returnType instanceof Class) {
            type = (Class<?>) returnType;
          } else if (returnType instanceof ParameterizedType) {
            type = (Class<?>) ((ParameterizedType) returnType).getRawType();
          }
        }
      }
    }
    return type;
  }

  private Type getGenericGetterType(String propertyName) {
    try {
      Invoker invoker = reflector.getGetInvoker(propertyName);
      if (invoker instanceof MethodInvoker) {
        Field _method = MethodInvoker.class.getDeclaredField("method");
        _method.setAccessible(true);
        Method method = (Method) _method.get(invoker);
        return TypeParameterResolver.resolveReturnType(method, reflector.getType());
      } else if (invoker instanceof GetFieldInvoker) {
        Field _field = GetFieldInvoker.class.getDeclaredField("field");
        _field.setAccessible(true);
        Field field = (Field) _field.get(invoker);
        return TypeParameterResolver.resolveFieldType(field, reflector.getType());
      }
    } catch (NoSuchFieldException e) {
    } catch (IllegalAccessException e) {
    }
    return null;
  }

  public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      if (reflector.hasSetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop.getName());
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      return reflector.hasSetter(prop.getName());
    }
  }

  // 判断指定属性是否有 getting 方法
  public boolean hasGetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      if (reflector.hasGetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop);
        return metaProp.hasGetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      return reflector.hasGetter(prop.getName());
    }
  }

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

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

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

  public boolean hasDefaultConstructor() {
    return reflector.hasDefaultConstructor();
  }

}
  • MetaClass中比较重要的是findProperty(),它是通过调用MetaClass.buildProperty()方法实现的, 二buildProperty()方法会通过PropertyTokenizer解析复杂的属性表达式

ObjectWrapper

MetaClass是Mybatis对类级别的元信息的封装和处理,下面来看MyBatis对对象级别的元信息的处理。ObjectWrapper接口是对对象的包装,抽象了对象的属性信息,它定义了一系列查询对象属性信息的方法,以及更新属性的方法。

public interface ObjectWrapper {

  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应getter方法,
  // 如果封装的是集合类,则获取指定key或下标对应的value值
  Object get(PropertyTokenizer prop);

  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应的setter方法
  // 如果封装的是集合类,则设置指定key或下标对应的value值
  void set(PropertyTokenizer prop, Object value);

  // 查找属性表达式指定的属性,第二个参数表示是否忽略属性表达式中的下划线
  String findProperty(String name, boolean useCamelCaseMapping);

  String[] getGetterNames();

  String[] getSetterNames();

  Class<?> getSetterType(String name);

  Class<?> getGetterType(String name);

  boolean hasSetter(String name);

  boolean hasGetter(String name);

  // 为属性表达式的属性创建相应的MetaObject对象
  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

  boolean isCollection();

  void add(Object element);

  <E> void addAll(List<E> element);

}

ObjectWrapperFactory负责创建ObjectWrapper对象。

image-20200607105809062

DefaultObjectWrapperFactory实 现 了 ObjectWrapperFactory接口,但它 实 现 的 getWrapperFor() 方法始终 抛出异 常,hasWrapperFor()方法始终 返回false,所以该 实 现 实 际 上是不可用的。但是 与 ObjectFactory 类 似,我们 可以在 mybatis-config.xml 中配置自定义 的 ObjectWrapperFactory 实 现 类 进 行扩 展,在后面介绍 MyBatis初始化时 还 会 提到该 扩 展点。

BaseWrapper是一个实现了ObjectWrapper接口的抽象类,其中封装了MetaObject对象,并提供了三个方勇的方法提供其子类使用:

image-20200607110710849

BaseWrapper,resolveCollection()方法会 调 用 MetaObject.get Value()方法,它 会 解析属 性表达 式并 获 取指定的属 性。

BaseWrapper.getCollectionValue()方法和 setCollectionValue()方法会 解析属 性表达 式的索引 信息,然后获 取/设 置对 应 项 。

MetaObject

org.apache.ibatis.reflection.MetaObject ,对象元数据,提供了对象的属性值的获得和设置等等方法。 可以理解成,对 BaseWrapper 操作的进一步增强

ObjectWrapper提供了获取/设置对象中指定的属性值、检测getter/setter等常用功能,但是ObjectWrapper只是这些功能的最后一站,我们省略了对属性表达式解析过程的介绍,而该解析过程是在MetaObject中实现的。

MetaObject中字段的含义:

// 原始JavaBean对象
private final Object originalObject;

// 上文介绍的ObjectWrapper对象,其中封装了originalObject对象
private final ObjectWrapper objectWrapper;

// 负责实例化originalObject的工厂对象
private final ObjectFactory objectFactory;

// 负责创建ObjectWrapper的工厂对象
private final ObjectWrapperFactory objectWrapperFactory;

// 用于创建并缓存Reflector对象的工厂对象
private final ReflectorFactory reflectorFactory;

MetaObject的构造方法会根据传入的原始对象的类型以及ObjectFactory工厂的实现, 创建相应的ObjectWrapper对象:

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配合实现,例如MetaObject.getValue()/setValue()方法。

以下是getValue()的代码:

public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      // 根据PropertyTokenizer解析后指定的属性,创建相应的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);
    }
  }

类型转换

JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集获取数据的时候,则需要从JDBC类型转换成Java类型。

MyBatis使用类型转换处理器完车过了上述两种转换。

image-20200607224820211

在 MyBatis中使用JdbcType这 个 枚举 类 型代表JDBC中的数 据类 型,该 枚举 类 型中定义 了 TYPE_CODE字段,记 录 了 JDBC类 型在java.sql.Types中相应 的常量 编 码 ,并 通过 一个 静 态 集合codeLookup (HashMap<Integer,JdbcType>类 型)维 护 了常量编 码 与JdbcType之间的对应关系。

TypeHandler

MyBatis中所有的类 型转 换 器都继 承了 TypeHandler接口,在 TypeHandler接口中定义 了如 下四个 方法,这 四个 方法分为 两 类 :setParameter()方法负 责 将 数 据由JdbcType类 型转 换 成Java 类 型:getResult()方法及其重载 负 责 将 数 据由Java类 型转 换 成JdbcType类 型。

public interface TypeHandler<T> {

  //在通过 PreparedStatement为 SQL语 句绑 定参 数 时 ,会 将 数 据由JdbcType类 型转 换 成Java类 型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

//从 ResultSet中获 取数 据时 会 调 用此方法,会 将 数 据由Java类 型转 换 成JdbcType类 型
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

为了方便用户自定义TypeHandler实现,MyBatis提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类,其继承结构如下:

image-20200607225857225

在BaseTypeHandler中实现了TypeHandler.setParameter()方法和TypeHandler.getResult()方法,具体实现如下。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
                "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                "Try setting a different JdbcType for this parameter or a different configuration property. " +
                "Cause: " + e, e);
      }
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    T result;
    try {
      result = getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set.  Cause: " + e, e);
    }
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    T result;
    try {
      result = getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement.  Cause: " + e, e);
    }
    if (cs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

一般情况下, TypeHandler用于完成单个参数以及单个列值的类型转换, 如果存在多列值转换成一个Java对象的需求,应该优先考虑在使用映射文件中定义合适的映射规则()完成映射。

TypeHandlerRegistry

介绍完TypeHandler接口及其功能之后,MyBatis如何管理众多的TypeHanlder接口实现,如何知道何时使用哪个TypeHandler接口实现完成转换呢?

这个工作是由TypeHandlerRegistry完成的,在MyBatis初始化过程中,会为所有已知的TypeHanlder创建对象,并实现注册到TypeHandlerRegistry中, 由TypeHandlerRegistry负责管理这些TypeHandler对象。

TypeHandlerRegistry中核心字段的含义:

// 记录JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型
// 该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = 
  new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// 记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。
// 例如Java类型中的String可能转换成数据库的char、varchar等多种类型,所以存在一对多关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = 
  new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 记录了全部TypeHandler的类型以及该类型相应的TypeHandler对象
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = 
  new UnknownTypeHandler(this);

// 空TypeHandler集合的标识
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = 
  new HashMap<Class<?>, TypeHandler<?>>();

1. 注册TypeHandler对象

TypeHandlerRegistry.register()方法实 现 了注册 TypeHandler对 象的功能,该 注册 过 程会 向上 述四个 集合中添加TypeHandler对 象。register()方法有多个 重载 ,这 些重载 之间 的调 用关 系如图 。

image-20200607231443963

从 上图中可以看出,多数 register()方法最终 会 调 用重载(4) 完成注册 功能,先来看该方法的实现,其三个 参 数 分别 指定了 TypeHandler能够 处 理的Java类 型、Jdbc类 型以及 TypeHandler对 象。

重载(4)

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  if (javaType != null) {// 检测是否明确指定了TypeHanlder能够处理的Java类型
    // 获取指定java类型在TYPE_HANDLER_MAP集合中对应TypeHanlder集合
    Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
    if (map == null || map == NULL_TYPE_HANDLER_MAP) {
      // 创建新的TypeHandler集合,并添加到TYPE_HANDLER_MAP中
      map = new HashMap<JdbcType, TypeHandler<?>>();
      TYPE_HANDLER_MAP.put(javaType, map);
    }
    // 将TypeHandler对象注册到并添加到TYPE_HANDLER_MAP中
    map.put(jdbcType, handler);
  }
  // 向ALL_TYPE_HANDLERS_MAP集合注册TypeHandler类型和对应的TypeHanlder对象
  ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

在(1)〜(3)这 三个 register()方法重载 中会 尝 试 读 取TypeHandler类 中定义 的@MappedTypes 注解和@MappedJdbcTypes注解,@MappedTypes注解用于指明该 TypeHandler实 现 类 能够 处 理 的Java类 型的集合,@MappedJdbcTypes注解用于指明该 TypeHandler实 现 类 能够 处 理的JDBC 类 型集合。register()方法的重载(1)〜(3)的具体 实 现 如下:

重载(1)

// Only handler type
public void register(Class<?> typeHandlerClass) {
  boolean mappedTypeFound = false;
  MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
  if (mappedTypes != null) {
    for (Class<?> javaTypeClass : mappedTypes.value()) {
      register(javaTypeClass, typeHandlerClass);
      mappedTypeFound = true;
    }
  }
  if (!mappedTypeFound) {
    register(getInstance(null, typeHandlerClass));
  }
}

重载(2)

public <T> void register(TypeHandler<T> typeHandler) {
  boolean mappedTypeFound = false;
  MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
  if (mappedTypes != null) {
    for (Class<?> handledType : mappedTypes.value()) {
      register(handledType, typeHandler);
      mappedTypeFound = true;
    }
  }
  // @since 3.1.0 - try to auto-discover the mapped type
  if (!mappedTypeFound && typeHandler instanceof TypeReference) {
    try {
      TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
      register(typeReference.getRawType(), typeHandler);
      mappedTypeFound = true;
    } catch (Throwable t) {
      // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
    }
  }
  if (!mappedTypeFound) {
    register((Class<T>) null, typeHandler);
  }
}

重载(3)

private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
  MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
  if (mappedJdbcTypes != null) {
    for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
      register(javaType, handledJdbcType, typeHandler);
    }
    if (mappedJdbcTypes.includeNullJdbcType()) {
      register(javaType, null, typeHandler);
    }
  } else {
    register(javaType, null, typeHandler);
  }
}

上 述 全 部 的 register()方 法 重 载 都 是 在 向 TYPE_HANDLER_MAP集 合 和 ALL_TYPE_HANDLERS_MAP 集合注册 TypeHandler 对 象,而重载(5)是向 JDBC_TYPE_HANDLER_MAP 集合注册 TypeHandler对 象,其具体 实 现 如下:

重载(5)

public void register(JdbcType jdbcType, TypeHandler<?> handler) {
  JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}

TypeHandlerRegistry除了提供注册 单 个 TypeHandler的register()重载 ,还 可以扫 描整个 包下 的TypeHandler接口实 现 类 ,并 将 完成这 些TypeHandler实 现 类 的注册 。

重载(6)

public void register(String packageName) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  // 查找指定包下的接口实现类
  resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
  Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
  for (Class<?> type : handlerSet) {
    //Ignore inner classes and interfaces (including package-info.java) and abstract classes
    if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
      register(type);
    }
  }
}

2. 查找TypeHandler

TypeHandlerRegistry提供了查 找 TypeHandler对 象的功能。TypeHandlerRegistry.getTypeHandler()方法实 现 了从 上述四个 集合中获 取对 应 TypeHandler对 象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个 重载 ,这 些重 载 之间 的关 系如下图所示。

image-20200608163219733

经 过 一系列类 型转 换 之后,TypeHandlerRegistry.getTypeHandler()方法的多个 重载 都会 调 用 TypeHandlerRegistry.getTypeHandle(Type, JdbcType)这 个 重载 方法,它 会 根据指定的 Java 类 型和 JdbcType类 型查 找 相应 的TypeHandler对 象。

TypeAliasRegistry

在编 写 SQL语 句时 ,使用别 名可以方便理解以及维 护 ,例如表名或列名很 长 时 ,我们 一般 会 为 其设 计 易懂 易维 护 的别 名。MyBatis将 SQL语 句中别 名的概 念进 行了延伸和扩 展,MyBatis 可以为 一个 类 添加一个 别 名,之后就可以通过 别 名引用该 类 。

MyBatis通过 TypeAliasRegistry类 完成别 名注册 和管理的功能,TypeAliasRegistry的结 构 比 较 简 单 ,它 通过 TYPE_ALIASES字段(Map<String,Class<?>>类 型)管理别 名与 Java类 型之间 的对 应 关 系,通过 TypeAHasRegistiy.registerAlias()方法完成注册 别 名,该 方法的具体 实 现 如下:

public void registerAlias(String alias, Class<?> value) {
  if (alias == null) {
    throw new TypeException("The parameter alias cannot be null");
  }

  // 将别名转换成小写
  String key = alias.toLowerCase(Locale.ENGLISH);

  // 检测别名是否存在
  if (TYPE_ALIASES.containsKey(key) 
      && TYPE_ALIASES.get(key) != null 
      && !TYPE_ALIASES.get(key).equals(value)) {
    throw new TypeException("...");
  }
  TYPE_ALIASES.put(key, value);
}

TypeAliasRegistry的构 造方法中,默认 为 Java的基本类 型及其数 组 类 型、基本类 型的封 装 类 及其数组类型、Date、BigDecimal、Biglnteger、Map、HashMap、List、ArrayList、Collection、 Iterator、ResultSet等类 型添加了别名。

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》

MyBatis 《MyBatis技术内幕》 基础支持层

相关推荐



版权声明




留言区

文章目录