Mybatis源码分析(5)之mybatis中的MetaClass的作用

一、前言

        在之前的配置文件解析过程中,我们有看到MetaClass这个类,这里我们花一章来分析MetaClass的源码来进而分析这个类的作用。

二、源码分析

        2.1MetaClass类源码分析

        元信息类MetaClass的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass方法进行创建。它的创建逻辑如下:

public class MetaClass {
  //Reflector 的工厂类,兼有缓存 Reflector 对象的功能
  private final ReflectorFactory reflectorFactory;
  // 反射器,用于解析和存储目标类中的元信息
  private final Reflector reflector;

  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
  }

  public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }
  // ......省略其他代码
}

       上面出现了两个新的类ReflectorFactory和Reflector,这个两个新的类我们先放一放,后面再具体看这个两个类的作用,我们在配置文件解析 settings有看过这样子的调用,如下:

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

        我们接下来看看metaClass的hasSetter方法的源码。

  public boolean hasSetter(String name) {
    // 属性分词器,用于解析属性名
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // hasNext 返回 true,则表明 name 是一个复合属性,后面会进行分析
    if (prop.hasNext()) {
      // 调用 reflector 的 hasSetter 方法
      if (reflector.hasSetter(prop.getName())) {
        // 为属性创建创建 MetaClass
        MetaClass metaProp = metaClassForProperty(prop.getName());
        // 再次调用 hasSetter
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      // 调用 reflector 的 hasSetter 方法
      return reflector.hasSetter(prop.getName());
    }
  }

        从上面的代码中,我们可以看出 MetaClass 中的 hasSetter 方法最终调用了 Reflector 的 hasSetter 方法。关于 Reflector 的 hasSetter 方法。

        下面单独分析一下这几个类的逻辑,首先是ReflectorFactory。ReflectorFactory 是一个接口,MyBatis 中目前只有一个实现类DefaultReflectorFactory,它的分析如下:

2.2DefaultReflectorFactory源码分析

 DefaultReflectorFactory 用于创建 Reflector,同时兼有缓存的功能,它的源码如下。

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

    public DefaultReflectorFactory() {
    }

    @Override
    public boolean isClassCacheEnabled() {
        return classCacheEnabled;
    }

    @Override
    public void setClassCacheEnabled(boolean classCacheEnabled) {
        this.classCacheEnabled = classCacheEnabled;
    }

    @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。

2.3Reflector源码分析

  我们来看一下 Reflector 的源码。Reflector 这个类的用途主要是是通过反射获取目标类的 getter 方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。Reflector 本身代码比较多,这里不能一一分析。本小节,我将会分析三部分逻辑,分别如下:

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

        接下来我们将进行分析:

2.3.1 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;

    // 用于保存大写属性名与属性名之间的映射,比如 <NAME, name>
    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 的构造方法看起来略为复杂,不过好在一些比较复杂的逻辑都封装在了相应的方法中,这样整体的逻辑就比较清晰了。Reflector 构造方法所做的事情均已进行了注释,大家对照着注释先看一下。相关方法的细节待会会进行分析。看完构造方法,下面我来通过表格的形式,列举一下 Reflector 部分成员变量的用途。如下:

变量名类型用途
readablePropertyNamesString[]可读属性名称数组,用于保存 getter 方法对应的属性名称
writeablePropertyNamesString[]可写属性名称数组,用于保存 setter 方法对应的属性名称
setMethodsMap<String, Invoker>用于保存属性名称到 Invoke 的映射。setter 方法会被封装到 MethodInvoker 对象中,Invoke 实现类比较简单,大家自行分析
getMethodsMap<String, Invoker>用于保存属性名称到 Invoke 的映射。同上,getter 方法也会被封装到 MethodInvoker 对象中
setTypesMap<String, Class<?>>用于保存 setter 对应的属性名与参数类型的映射
getTypesMap<String, Class<?>>用于保存 getter 对应的属性名与返回值类型的映射
caseInsensitivePropertyMapMap<String, String>用于保存大写属性名与属性名之间的映射,比如 <NAME, name>

  2.3.2addGetMethods解析

  getter方法的逻辑都封装在了addGetMethods中,接下来我们看下这个方法的源码信息:

    /**
     * 执行流程如下:
     * 1.获取当前类,接口,以及父类中的方法
     * 2.遍历上一步获取的方法数组,并过滤出以get和is开头的方法
     * 3.将方法名转换成相应的属性名
     * 4.将属性名和方法对象添加到冲突集合中
     * 5.解决冲突
     * @param cls
     * @author maoqichuan
     * @date 2021/12/28 10:16
     */
    private void addGetMethods(Class<?> cls) {
        Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
        // 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了
        Method[] methods = getClassMethods(cls);
        for (Method method : methods) {
            // getter 方法不应该有参数,若存在参数,则忽略当前方法
            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);
    }

  它的执行流程如下:

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

         前面几步的代码还是比较简单的,我们就来看下他是如何解决冲突的,源代码如下:

    /**
     * 解决冲突,解决的规则如下:
     * 1.冲突方法的返回值类型具有继承关系,子类返回值对应的方法被认为是更合适的选择
     * 2.冲突方法的返回值类型相同,如果返回值类型为boolean,那么以is开头的方法则是更合适的方法
     * 3.冲突方法的返回值类型相同,但返回值类型非boolean,此时出现歧义,抛出异常
     * 4.冲突方法的返回值类型不相关,无法确定哪个是更好的选择,此时直接抛异常
     *
     * @param conflictingGetters
     * @author maoqichuan
     * @date 2021/12/28 10:19
     */
    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)) {
                    // OK getter type is descendant

                    /*
                     * 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.");
                }
            }
            addGetMethod(propName, winner);
        }
    }

        其实他解决冲突方法的规则如下:

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

        2.3.3addSetMethods源码解析

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

    /**
     * 解析setter方法
     * @param cls
     * @author maoqichuan
     * @date 2021/12/28 10:31
     */
    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 冲突resolveSetterConflicts源码分析如下:

    /**
     * 解决setter方法冲突
     *
     * @param conflictingSetters
     * @author maoqichuan
     * @date 2021/12/28 11:06
     */
    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) {
                        // there could still be the 'best match'
                        match = null;
                        exception = e;
                    }
                }
            }

            // 若 match 为空,表示没找到更为合适的方法,此时抛出异常
            if (match == null) {
                throw exception;
            } else {
                // 将筛选出的方法放入 setMethods 中,并将方法参数值添加到 setTypes 中
                addSetMethod(propName, match);
            }
        }
    }

    /**
     * 从两个 setter 方法中选择一个更为合适方法,规则如下
     * 1.冲突方法的参数类型与 getter 的返回类型一致,则认为是最好的选择
     * 2.冲突方法的参数类型具有继承关系,子类参数对应的方法被认为是更合适的选择
     * 3.冲突方法的参数类型不相关,无法确定哪个是更好的选择,此时直接抛异常
     * @param setter1
     * @param setter2
     * @param property
     * @return java.lang.reflect.Method
     * @author maoqichuan
     * @date 2021/12/28 11:09
     */
    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() + "'.");
    }

        2.4PropertyTokenizer属性分词器分析

        对于较为复杂的属性,需要进行进一步解析才能使用。那什么样的属性是复杂属性呢?来看个测试代码就知道了。

public class MetaClassTest {

    private class Author {
        private Integer id;
        private String name;
        private Integer age;
        /** 一个作者对应多篇文章 */
        private Article[] articles;

        // 省略 getter/setter
    }

    private class Article {
        private Integer id;
        private String title;
        private String content;
        /** 一篇文章对应一个作者 */
        private Author author;

        // 省略 getter/setter
    }

    
    public void testHasSetter() {
        // 为 Author 创建元信息对象
        MetaClass authorMeta = MetaClass.forClass(Author.class, new DefaultReflectorFactory());
        System.out.println("------------☆ Author ☆------------");
        System.out.println("id -> " + authorMeta.hasSetter("id"));
        System.out.println("name -> " + authorMeta.hasSetter("name"));
        System.out.println("age -> " + authorMeta.hasSetter("age"));
        // 检测 Author 中是否包含 Article[] 的 setter
        System.out.println("articles -> " + authorMeta.hasSetter("articles"));
        System.out.println("articles[] -> " + authorMeta.hasSetter("articles[]"));
        System.out.println("title -> " + authorMeta.hasSetter("title"));

        // 为 Article 创建元信息对象
        MetaClass articleMeta = MetaClass.forClass(Article.class, new DefaultReflectorFactory());
        System.out.println("\n------------☆ Article ☆------------");
        System.out.println("id -> " + articleMeta.hasSetter("id"));
        System.out.println("title -> " + articleMeta.hasSetter("title"));
        System.out.println("content -> " + articleMeta.hasSetter("content"));
        // 下面两个均为复杂属性,分别检测 Article 类中的 Author 类是否包含 id 和 name 的 setter 方法
        System.out.println("author.id -> " + articleMeta.hasSetter("author.id"));
        System.out.println("author.name -> " + articleMeta.hasSetter("author.name"));
    }
}

        接下来我们对PropertyTokenizer的源码进行分析:

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

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

  @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 的源码分析,注释的比较多,应该分析清楚了。大家如果看懂了上面的分析,那么可以自行举例进行测试,以加深理解。

        本节为了分析 MetaClass 的 hasSetter 方法,把这个方法涉及到的源码均分析了一遍。其实,如果想简单点分析,我可以直接把 MetaClass 当成一个黑盒,然后用一句话告诉大家 hasSetter 方法有什么用即可

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值