MyBatis源码解析(二) --- 反射模块Reflector

本文主要介绍MyBatis的反射模块是如何实现的。
在这里插入图片描述

DefaultReflectorFactory 源码分析

基础支持层反射模块Reflector/ReflectorFactory,DefaultReflectorFactory 用于创建 Reflector,同时兼有缓存的功能,它的源码如下。

public class DefaultReflectorFactory implements ReflectorFactory {

    private boolean classCacheEnabled = true;
    /** 目标类和反射器映射缓存 */
    private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();

    // 省略部分代码

    @Override
    public Reflector findForClass(Class<?> type) {
        // classCacheEnabled 默认为 true
        if (classCacheEnabled) {
            // 从缓存中获取 Reflector 对象
            Reflector cached = reflectorMap.get(type);
            // 缓存为空,则创建一个新的 Reflector 实例,并放入缓存中
            if (cached == null) {
                cached = new Reflector(type);
                // 将 <type, cached> 映射缓存到 map 中,方便下次取用
                reflectorMap.put(type, cached);
            }
            return cached;
        } else {
            // 创建一个新的 Reflector 实例
            return new Reflector(type);
        }
    }
}

如上,DefaultReflectorFactory 的findForClass方法逻辑不是很复杂,包含两个访存操作,和一个对象创建操作。代码注释的比较清楚了,就不多说了。接下来,来分析一下反射器 Reflector。

Reflector 源码分析

Reflector 这个类的用途主要是是通过反射获取目标类的 getter 方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。
我将会分析三部分逻辑,分别如下:

  1. Reflector 构造方法及成员变量分析
  2. getter 方法解析过程
  3. setter 方法解析过程

Reflector 构造方法及成员变量分析

Reflector 构造方法中包含了很多初始化逻辑,目标类的元信息解析过程也是在构造方法中完成的,这些元信息最终会被保存到 Reflector 的成员变量中。下面我们先来看看 Reflector 的构造方法和相关的成员变量定义,代码如下:

public class Reflector {

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

  private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

  public Reflector(Class<?> clazz) {
    type = clazz;
    // 解析目标类的默认构造方法,并赋值给 defaultConstructor 变量
    addDefaultConstructor(clazz);
     // 解析 getter 方法,并将解析结果放入 getMethods 中
    addGetMethods(clazz);
    // 解析 setter 方法,并将解析结果放入 setMethods 中
    addSetMethods(clazz);
     // 解析属性字段,并将解析结果添加到 setMethods 或 getMethods 中
    addFields(clazz);
     // 从 getMethods 映射中获取可读属性名数组
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
     // 从 setMethods 映射中获取可写属性名数组
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    // 将所有属性名的大写形式作为键,属性名作为值,存入到 caseInsensitivePropertyMap 中
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }
  }

如上,Reflector 的构造方法看起来略为复杂,不过好在一些比较复杂的逻辑都封装在了相应的方法中,这样整体的逻辑就比较清晰了。

getter 方法解析过程

getter 方法解析的逻辑被封装在了addGetMethods方法中,这个方法除了会解析形如getXXX的方法,同时也会解析isXXX方法。该方法的源码分析如下:

  private void addGetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
    // 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了
    Method[] methods = getClassMethods(cls);
    // getter 方法不应该有参数,若存在参数,则忽略当前方法
    for (Method method : methods) {
      if (method.getParameterTypes().length > 0) {
        continue;
      }
      String name = method.getName();
      // 过滤出以 get 或 is 开头的方法
      if ((name.startsWith("get") && name.length() > 3)
          || (name.startsWith("is") && name.length() > 2)) {
          // 将 getXXX 或 isXXX 等方法名转成相应的属性,比如 getName -> name
        name = PropertyNamer.methodToProperty(name);
        /*
             * 将冲突的方法添加到 conflictingGetters 中。考虑这样一种情况:
             * 
             * getTitle 和 isTitle 两个方法经过 methodToProperty 处理,
             * 均得到 name = title,这会导致冲突。
             *
             * 对于冲突的方法,这里先统一起存起来,后续再解决冲突
             */
        addMethodConflict(conflictingGetters, name, method);
      }
    }
    // 解决 getter 冲突
    resolveGetterConflicts(conflictingGetters);
  }

如上,addGetMethods 方法的执行流程如下:

  1. 获取当前类,接口,以及父类中的方法
  2. 遍历上一步获取的方法数组,并过滤出以get和is开头的方法
  3. 将方法名转换成相应的属性名
  4. 将属性名和方法对象添加到冲突集合中
  5. 解决冲突

前四步比较简单,大家看一下就能懂。在这几步中,第5步逻辑比较复杂,这一步逻辑我们重点关注一下。

/** 添加属性名和方法对象到冲突集合中 */
 private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
    List<Method> list = conflictingMethods.get(name);
    if (list == null) {
      list = new ArrayList<Method>();
      conflictingMethods.put(name, list);
    }
    list.add(method);
  }

/** 解决冲突 */
 private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
    for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
      Method winner = null;
      String propName = entry.getKey();
      for (Method candidate : entry.getValue()) {
        if (winner == null) {
          winner = candidate;
          continue;
        }
        // 获取返回值类型
        Class<?> winnerType = winner.getReturnType();
        Class<?> candidateType = candidate.getReturnType();

          /* 
             * 两个方法的返回值类型一致,若两个方法返回值类型均为 boolean,则选取 isXXX 方法
             * 为 winner。否则无法决定哪个方法更为合适,只能抛出异常
             */
        if (candidateType.equals(winnerType)) {
          if (!boolean.class.equals(candidateType)) {
            throw new ReflectionException(
                "Illegal overloaded getter method with ambiguous type for property "
                    + propName + " in class " + winner.getDeclaringClass()
                    + ". This breaks the JavaBeans specification and can cause unpredictable results.");
            /*
                 * 如果方法返回值类型为 boolean,且方法名以 "is" 开头,
                 * 则认为候选方法 candidate 更为合适
                 */
          } else if (candidate.getName().startsWith("is")) {
            winner = candidate;
          }
            /*
             * winnerType 是 candidateType 的子类,类型上更为具体,
             * 则认为当前的 winner 仍是合适的,无需做什么事情
             */
        } else if (candidateType.isAssignableFrom(winnerType)) {
         /*
             * candidateType 是 winnerType 的子类,此时认为 candidate 方法更为合适,
             * 故将 winner 更新为 candidate
             */
        } else if (winnerType.isAssignableFrom(candidateType)) {
          winner = candidate;
        } else {
          throw new ReflectionException(
              "Illegal overloaded getter method with ambiguous type for property "
                  + propName + " in class " + winner.getDeclaringClass()
                  + ". This breaks the JavaBeans specification and can cause unpredictable results.");
        }
      }
      // 将筛选出的方法添加到 getMethods 中,并将方法返回值添加到 getTypes 中
      addGetMethod(propName, winner);
    }
  }


private void addGetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
        getMethods.put(name, new MethodInvoker(method));
        // 解析返回值类型
        Type returnType = TypeParameterResolver.resolveReturnType(method, type);
        // 将返回值类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes 中
        getTypes.put(name, typeToClass(returnType));
    }
}

上就是解除冲突的过程,代码有点长,不太容易看懂。这里大家只要记住解决冲突的规则即可理解上面代码的逻辑。相关规则如下:

  1. 冲突方法的返回值类型具有继承关系,子类返回值对应的方法被认为是更合适的选择
  2. 冲突方法的返回值类型相同,如果返回值类型为boolean,那么以is开头的方法则是更合适的方法
  3. 冲突方法的返回值类型相同,但返回值类型非boolean,此时出现歧义,抛出异常
  4. 冲突方法的返回值类型不相关,无法确定哪个是更好的选择,此时直接抛异常

分析完 getter 方法的解析过程,下面继续分析 setter 方法的解析过程。

setter 方法解析过程

与 getter 方法解析过程相比,setter 方法的解析过程与此有一定的区别。主要体现在冲突出现的原因,以及冲突的解决方法上。那下面,我们深入源码来找出两者之间的区别。

private void addSetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
    // 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
        String name = method.getName();
        // 过滤出 setter 方法,且方法仅有一个参数
        if (name.startsWith("set") && name.length() > 3) {
            if (method.getParameterTypes().length == 1) {
                name = PropertyNamer.methodToProperty(name);
                /*
                 * setter 方法发生冲突原因是:可能存在重载情况,比如:
                 *     void setSex(int sex);
                 *     void setSex(SexEnum sex);
                 */
                addMethodConflict(conflictingSetters, name, method);
            }
        }
    }
    // 解决 setter 冲突
    resolveSetterConflicts(conflictingSetters);
}

从上面的代码和注释中,我们可知道 setter 方法之间出现冲突的原因。即方法存在重载,方法重载导致methodToProperty方法解析出的属性名完全一致。而 getter 方法之间出现冲突的原因是getXXX和isXXX对应的属性名一致。既然冲突发生了,要进行调停,那接下来继续来看看调停冲突的逻辑。

private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    for (String propName : conflictingSetters.keySet()) {
        List<Method> setters = conflictingSetters.get(propName);
        /*
         * 获取 getter 方法的返回值类型,由于 getter 方法不存在重载的情况,
         * 所以可以用它的返回值类型反推哪个 setter 的更为合适
         */
        Class<?> getterType = getTypes.get(propName);
        Method match = null;
        ReflectionException exception = null;
        for (Method setter : setters) {
            // 获取参数类型
            Class<?> paramType = setter.getParameterTypes()[0];
            if (paramType.equals(getterType)) {
                // 参数类型和返回类型一致,则认为是最好的选择,并结束循环
                match = setter;
                break;
            }
            if (exception == null) {
                try {
                    // 选择一个更为合适的方法
                    match = pickBetterSetter(match, setter, propName);
                } catch (ReflectionException e) {
                    match = null;
                    exception = e;
                }
            }
        }
        // 若 match 为空,表示没找到更为合适的方法,此时抛出异常
        if (match == null) {
            throw exception;
        } else {
            // 将筛选出的方法放入 setMethods 中,并将方法参数值添加到 setTypes 中
            addSetMethod(propName, match);
        }
    }
}

/** 从两个 setter 方法中选择一个更为合适方法 */
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    if (setter1 == null) {
        return setter2;
    }
    Class<?> paramType1 = setter1.getParameterTypes()[0];
    Class<?> paramType2 = setter2.getParameterTypes()[0];

    // 如果参数2可赋值给参数1,即参数2是参数1的子类,则认为参数2对应的 setter 方法更为合适
    if (paramType1.isAssignableFrom(paramType2)) {
        return setter2;
        
    // 这里和上面情况相反
    } else if (paramType2.isAssignableFrom(paramType1)) {
        return setter1;
    }
    
    // 两种参数类型不相关,这里抛出异常
    throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
        + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
        + paramType2.getName() + "'.");
}

private void addSetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
        setMethods.put(name, new MethodInvoker(method));
        // 解析参数类型列表
        Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
        // 将参数类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes
        setTypes.put(name, typeToClass(paramTypes[0]));
    }
}

关于 setter 方法冲突的解析规则,这里也总结一下吧。如下:

  1. 冲突方法的参数类型与 getter 的返回类型一致,则认为是最好的选择
  2. 冲突方法的参数类型与 getter 的返回类型一致,则认为是最好的选择
  3. 冲突方法的参数类型不相关,无法确定哪个是更好的选择,此时直接抛异常

到此关于 setter 方法的解析过程就说完了。我在前面说过 MetaClass 的hasSetter最终调用了 Refactor 的hasSetter方法,那么现在是时候分析 Refactor 的hasSetter方法了。代码如下如下:

public boolean hasSetter(String propertyName) {
    return setMethods.keySet().contains(propertyName);
}

PropertyTokenizer 源码分析

对于较为复杂的属性,需要进行进一步解析才能使用。那什么样的属性是复杂属性呢? 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) {
            /*
             * 以点位为界,进行分割。比如:
             *    fullname = www.coolblog.xyz
             *
             * 以第一个点为分界符:
             *    name = www
             *    children = coolblog.xyz
             */ 
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
            // fullname 中不存在字符 '.'
            name = fullname;
            children = null;
        }
        indexedName = name;
        // 检测传入的参数中是否包含字符 '['
        delim = name.indexOf('[');
        if (delim > -1) {
            /*
             * 获取中括号里的内容,比如:
             *   1. 对于数组或List集合:[] 中的内容为数组下标,
             *      比如 fullname = articles[1],index = 1
             *   2. 对于Map:[] 中的内容为键,
             *      比如 fullname = xxxMap[keyName],index = keyName
             *
             * 关于 index 属性的用法,可以参考 BaseWrapper 的 getCollectionValue 方法
             */
            index = name.substring(delim + 1, name.length() - 1);

            // 获取分解符前面的内容,比如 fullname = articles[1],name = articles
            name = name.substring(0, delim);
        }
    }

    // 省略 getter

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

    @Override
    public PropertyTokenizer next() {
        // 对 children 进行再次切分,用于解析多重复合属性
        return new PropertyTokenizer(children);
    }

    // 省略部分方法
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值