MyBatis 源码–反射工具包

gomkiri 发布于 25 天前 68 次阅读


AI 摘要

MyBatis如何巧妙封装反射?Reflector类统一处理泛型、Record和普通POJO,通过递归获取类方法并解决getter冲突,让复杂反射逻辑变得简单高效。

*声明,本文章的参考
Mybatis 3.5.19 源码
技术文章摘抄-深入剖析 MyBatis 核心原理-完

Relefctor

MyBatis 为了降低反射的使用门槛,封了一个反射专用的工具包,位与 org.apache.ibatis.reflectio 中。

这个包中有一个非常重要的实现类 Relefctor ,他记录了一个 type 应对的所有反射可以得到的信息:

而获取这些信息的逻辑都被放到 Reflector(Type type) 的构造方法中:

  public Reflector(Type type) {
    // 将原始类型做备份
    this.type = type;
    if (type instanceof ParameterizedType) {
      // 如果是一个参数化的类型(泛型),值获取他的原始类型(避免泛型擦除带来的影响)
      this.clazz = (Class<?>) ((ParameterizedType) type).getRawType();
    } else {
      // 否则直接将类型转换为Class
      this.clazz = (Class<?>) type;
    }
    // 通过反射获取默认构造器并保存
    addDefaultConstructor(clazz);
    // 获取类的所有方法(包括私有方法)并保存
    Method[] classMethods = getClassMethods(clazz);
    if (isRecord(clazz)) {
      // 如果是一个Record类,只处理getter方法即可
      addRecordGetMethods(classMethods);
    } else {
      // 获取普通 pojo 的 getter 、 setter 方法和字段
      addGetMethods(classMethods);
      addSetMethods(classMethods);
      addFields(clazz);
    }
    // 将可读和可写属性名称保存到数组中,并构建一个大小写不敏感的属性名称映射
    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);
    }
  }
Java

其中的两个 if 线代表了两个特殊情况:参数化对象和 Record 对象。

getClassMethods 方法中,进行了一个递归的操作,从下往上寻找最新的方法实现,之类没有实现的话,就用父类的:

  private Method[] getClassMethods(Class<?> clazz) {
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    while (currentClass != null && currentClass != Object.class) {
      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());
      }
      // 继续往父类查找,直到找到 Object 类为止
      currentClass = currentClass.getSuperclass();
    }

    Collection<Method> methods = uniqueMethods.values();

    return methods.toArray(new Method[0]);
  }
  
    private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
    for (Method currentMethod : methods) {
      if (!currentMethod.isBridge()) {
        // 根据方法生成自己的签名
        String signature = getSignature(currentMethod);
        // check to see if the method is already known
        // if it is known, then an extended class must have
        // overridden a method
        // 依旧遍历了子类方法但是确实没有这个方法,那就用父类的
        if (!uniqueMethods.containsKey(signature)) {
          uniqueMethods.put(signature, currentMethod);
        }
      }
    }
  }
  
  // 生成签名的方法
  private String getSignature(Method method) {
    StringBuilder sb = new StringBuilder();
    Class<?> returnType = method.getReturnType();
    // 返回值类型+#
    sb.append(returnType.getName()).append('#');
    // 方法名
    sb.append(method.getName());
    Class<?>[] parameters = method.getParameterTypes();
    // :参数名,参数名
    for (int i = 0; i < parameters.length; i++) {
      sb.append(i == 0 ? ':' : ',').append(parameters[i].getName());
    }
    return sb.toString();
  }
Java

由于记录了签名中包含了详细的参数名,所以也不会将重载的方法给混在一起。

得到所有的方法后,还需要区分去其中的 getter 和 setter 方法,addGetMethods:

  private void addGetMethods(Method[] methods) {
    Map<String, List<Method>> conflictingGetters = new HashMap<>();
    /**
     * 1. 过滤出所有的 getter 方法(参数个数为0,方法名以 get 或 is 开头)
     * 2. mothodToProerty 剥离方法名的 get/is 前缀,并将首字母小写,得到属性名
     * 3. 将属性名和方法列表添加到冲突的 getter 方法列表中
     *  3.1 为什么要有冲突的 getter 方法列表? a. 子类在重新父类的 getter 方法时,可能会改变返回值类型(协变返回类型),导致父类和子类的 getter 方法存在冲突; b. Record 类中,所有的组件方法都是 getter 方法,且方法名与属性名相同,也会导致冲突 c. Boolean 类型的 getter 方法可能存在 getXxx 和 isXxx 两种形式,也会导致冲突
     */
    Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
        .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    // 解决 getter 方法的冲突,确定最终的 getter 方法,并将其添加到 getMethods 和 getTypes 中
    resolveGetterConflicts(conflictingGetters);
  }
  
  // 看一下用于获取属性名字的 mothodToProperty 方法:
  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;
  }
  
  // 真正用于解决冲突的 resolveGetterConflicts 方法:
  private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
    // 遍历所有方法名
    for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
      Method winner = null;
      String propName = entry.getKey();
      boolean isAmbiguous = false;
      for (Method candidate : entry.getValue()) {
        if (winner == null) {
          winner = candidate;
          continue;
        }
        // 比较两个方法的返回值类型,并选择实现上更加具体的那个作为 winner,如果两个方法的返回值类型不兼容,则将 isAmbiguous 设置为 true,表示存在歧义
        Class<?> winnerType = winner.getReturnType();
        Class<?> candidateType = candidate.getReturnType();
        if (candidateType.equals(winnerType)) {
          if (!boolean.class.equals(candidateType)) {
            isAmbiguous = true;
            break;
          }
          if (candidate.getName().startsWith("is")) {
            winner = candidate;
          }
        } else if (candidateType.isAssignableFrom(winnerType)) {
          // OK getter type is descendant
        } else if (winnerType.isAssignableFrom(candidateType)) {
          winner = candidate;
        } else {
          isAmbiguous = true;
          break;
        }
      }
      // 创建并添加对应方法的 invoke 对象,并将方法的返回值类型保存到 getTypes 中
      addGetMethod(propName, winner, isAmbiguous);
    }
  }
  
  
  private void addGetMethod(String name, Method method, boolean isAmbiguous) {
    MethodInvoker invoker = isAmbiguous ? new AmbiguousMethodInvoker(method, MessageFormat.format(
        "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
        name, method.getDeclaringClass().getName())) : new MethodInvoker(method);
    getMethods.put(name, invoker);
    Type returnType = TypeParameterResolver.resolveReturnType(method, type);
    getTypes.put(name, Map.entry(returnType, typeToClass(returnType)));
  }
Java

addGetMothods 方法不同的是, addsetMothods 方法解决冲突是直接获取到 getMothods 对应的字段的 getter 方法的返回值类型,然后做匹配。

Invoker

在上面的 addGetMethod 方法中,我们可以看到一个 MethodInvoker 的类,这是 MyBatis 为了方便调用 Invoke 又针对方法和字段写了工具类:

看一看 MethodInvoker 都帮我们做了什么:

public class MethodInvoker implements Invoker {

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

  public MethodInvoker(Method method) {
    this.method = method;

    if (method.getParameterTypes().length == 1) {
      type = method.getParameterTypes()[0];
    } else {
      type = method.getReturnType();
    }
  }

  @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);
      }
      throw e;
    }
  }

  @Override
  public Class<?> getType() {
    return type;
  }
}
Java

可以看到,在 invoke 方法中,先尝试直接调用 invoke 一次,如果失败了就添加私有方法的访问权限再试一次,否则就报错,也就是说这个包装方法可以自动帮我们处理静态方法(其实在另外两个 GetfieldInvokerSetfieldInvoker 中也是一样的处理方式)。

而 AmbiguousMethodInvoker 要更加的特殊,直接抛出异常:

public class AmbiguousMethodInvoker extends MethodInvoker {
  private final String exceptionMessage;

  public AmbiguousMethodInvoker(Method method, String exceptionMessage) {
    super(method);
    this.exceptionMessage = exceptionMessage;
  }

  @Override
  public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
    throw new ReflectionException(exceptionMessage);
  }
}
Java

ObjtctFactory

Objectfactory 接口提供了根据 class 类和构造器参数去创建对象实例的功能,在执行完 SQL 语句将结果向 Java 对象进行转换时就经常需要用到这个他的默认实现 DefaultObjectFactory ,其核心实现:

if(constructorArgTypes == null || constructorArgs == null){
  constructor = type.getDeclaredConstructor();
  return construtor.newInstance();
}else{
  constructor = type.getDeclardConstructor(constructorArgTypes.toArray(new Class[0]));
  return contructor.newInstance(constructorArgs.toArray(new object[0]));
}
Java

当然源码中的代码会更加的健壮。

ReflectorFactory

ReflectorFactory 不仅为我们提供了创建 Reflector 的能力,还通过一个内置的 reflectorMap 为我们缓存起来了创建过的 Reflector ,我们可以通过 setClassCacheEnable 来设置是否开支缓存,默认为开启:

  @Override
  public Reflector findForClass(Type type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    }
    return new Reflector(type);
  }
Java

PropertyUtils

在 property 包下提供了三个相对独立的静态类,分别有不同的作用:

  • PropertyTokenizer
    对由 . / [] 构成的字符串表达式进行解析。解析带 .的表达式时,可以通过迭代器遍历整个表达式,其中 name 表示当前两个 . 之前的字符,children 是未遍历的字符串。
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 = 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);
    }
  }
  @Override
  public boolean hasNext() {
    return children != null;
  }

  @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.");
  }
}
Java
  • Propertynamer
    主要是对参数名称进行处理,其中的 methodToProperty 上面已经用到了,就是推断方法对应的源参数,另外的还有:是否是 getter / setter。
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 isGetter(name) || isSetter(name);
  }

  public static boolean isGetter(String name) {
    return name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2;
  }

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

}
Java
  • PropertCopier
    通过反射的方式对 javaBean 的属性进行复制
  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 {
          try {
            field.set(destinationBean, field.get(sourceBean));
          } catch (IllegalAccessException e) {
            if (!Reflector.canControlMemberAccessible()) {
              throw e;
            }
            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();
    }
  }
  
Java

MetaClass

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

这个类主要是提供一些类级别的工具,比如 是否有 setter ,setter / getter 的名字,getter 的返回类型等等。

同时 MetaClass 中的方法大量依赖的 PropertyTokenizer 的迭代器模式。

MetaObject & ObjectWrapper

上面的 ObjectFactory 为类型转换提供了类型构造的能力,而这两个类则是提供了赋值和获取值的能力。

MetaObject 是其他方法调用的入口,常用的方法有三个

  • 获取 MetaObject对象的静态方法:static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory)
  • 获取值:Object getValue(String name)
  • 修改值:void setValue(String name, Object value)

forObject :

  public static MetaObject forObject(Object object, ObjectFactory objectFactory,
      ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    if (object == null) {
      return SystemMetaObject.NULL_META_OBJECT;
    }
    return new MetaObject(object, objectFactory, objectWrapperFactory, 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);
    }
  }
Java

从上面的构造方法可以看出,创建 MetaObject 时会跟根据不同的条件,创建不同的 Wrapper ,Wrapper 间的关系如下:

以 metaObject 中的 objectwrapper 是 BeseWrapper 为例,可以看一下调用 getVlaue 方法时的时序图:

sequenceDiagram
    autonumber
    actor Caller as 调用方
    participant MetaObject as MetaObject
    participant PropertyTokenizer as PropertyTokenizer
    participant Wrapper as ObjectWrapper (具体实现类)
    participant BaseWrapper as BaseWrapper (抽象父类)
    participant MetaValue as MetaObject (子属性的MetaObject)
    Caller->>MetaObject: getValue(name)
    activate MetaObject
    
    MetaObject->>PropertyTokenizer: new PropertyTokenizer(name)
    activate PropertyTokenizer
    Note right of PropertyTokenizer: 解析属性名,拆解出当前名称(name)、<br>索引(index)和子属性(children)
    PropertyTokenizer-->>MetaObject: prop
    deactivate PropertyTokenizer
    
    MetaObject->>Wrapper: get(prop)
    activate Wrapper
    
    alt prop.hasNext() (存在子属性,如 "user.name")
        Wrapper->>BaseWrapper: getChildValue(prop)
        activate BaseWrapper
        BaseWrapper->>MetaObject: metaObjectForProperty(prop.getIndexedName())
        activate MetaObject
        Note right of MetaObject: 内部获取当前属性值,<br>并包装为新的 MetaObject 返回
        MetaObject-->>BaseWrapper: metaValue
        deactivate MetaObject
        
        alt metaValue == SystemMetaObject.NULL_META_OBJECT
            BaseWrapper-->>Wrapper: null
        else metaValue 有效
            BaseWrapper->>MetaValue: getValue(prop.getChildren())
            activate MetaValue
            Note right of MetaValue: 递归调用子 MetaObject <br>去解析剩余的属性路径
            MetaValue-->>BaseWrapper: value
            deactivate MetaValue
            BaseWrapper-->>Wrapper: value
        end
        deactivate BaseWrapper
        
    else !prop.hasNext() (无子属性,说明是最后一层)
    
        alt prop.getIndex() != null (具有索引,如 "orders[0]")
            Wrapper->>BaseWrapper: resolveCollection(prop, object)
            activate BaseWrapper
            Note right of BaseWrapper: 确定当前集合对象
            BaseWrapper-->>Wrapper: collection
            deactivate BaseWrapper
            
            Wrapper->>BaseWrapper: getCollectionValue(prop, collection)
            activate BaseWrapper
            Note right of BaseWrapper: 判断是 Map, List 还是 Array,<br>根据 prop.getIndex() 提取对应的元素值
            BaseWrapper-->>Wrapper: value
            deactivate BaseWrapper
            
        else 没有索引 (普通单一属性,如 "age")
            Note right of Wrapper: 例如 BeanWrapper 会通过反射<br>直接调用 getter 方法获取值
            Wrapper->>Wrapper: 通过反射/Map的Get获取最终值
        end
        
    end
    
    Wrapper-->>MetaObject: 返回获取到的属性值
    deactivate Wrapper
    
    MetaObject-->>Caller: 返回获取到的属性值
    deactivate MetaObject