Reflector、ReflectorFactory的今生

一、Reflector简介


Reflector是Mybatis中反射模块的基础,每个Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的类的元信息。ReflectorFactory接口主要是为了实现对Reflector对象的创建缓存,Mybatis为ReflectorFactory接口提供了一个默认实现类DefaultReflectorFactory(如果想扩展实现ReflectorFactory接口即可),其中findForClass()方法实现会为指定的Class创建Reflector对象,并将Reflector 对象缓存到reflectorMap中。

二、Reflector类源码

1、字段

  // 对应的Class类型
  private final Class<?> type;
  // 可读属性的名称集合,可读属性就是存在相应getter方法的属性,初始值为空数纽
  private final String[] readablePropertyNames;
  // 可写属性的名称集合,可写属性就是存在相应setter方法的属性,初始值为空数纽
  private final String[] writeablePropertyNames;
  // 属性相应的setter方法,key是属性名称,value是Invoker对象,它是对setter方法对应Method对象的封装
  private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
  // 属性相应的getter方法集合, key是属性名称, value也是Invoker对象
  private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
  // 属性相应的setter方法的参数值类型, key是属性名称, value是setter方法的参数类型
  private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
  // 属性相应的getter方法的返回位类型, key是属性名称, value是getter方法的返回类型
  private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
  // 默认构造方法
  private Constructor<?> defaultConstructor;
  // 所有属性名称的集合
  private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

2、构造方法

在构造函数中,主要实现了对类的元信息进行解析和缓存,即初始化相关字段数据。下面分别分析数据初始化的过程。其中,type字段就是class。

  /**
   * 解析指定的Class对象,填充相应的属性
   * @param clazz
   */
  public Reflector(Class<?> clazz) {
    type = clazz;//初始化type字段
    // 查找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[getMethods.keySet().size()]);
    writablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    // 初始化caseinsensitivePropertyMap集合,其中记录了所有大写格式的属性名称
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

3、addDefaultConstructor()初始化默认构造方法

查找clazz的默认构造方法,即无参构造方法,具体实现是通过反射遍历所有构造方法,
然后通过过滤查询到默认构造函数,并赋值给defaultConstructor字段。

  private void addDefaultConstructor(Class<?> clazz) {
    // 获取自己的构造方法
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    // 找到无参构造函数,作为默认构造函数
    Arrays.stream(constructors)
      .filter(constructor -> constructor.getParameterTypes().length == 0)
      .findAny().ifPresent(constructor -> this.defaultConstructor = constructor);
  }

4、addGetMethods()

分析addGetMethods()方法,代码如下。其中,包含了getClassMethods()方法,主要用来获取当前类以及其父类中定义的所有方法的唯一签名以及相应的Method对象;addMethodConflict()方法,主要用来获取字段名和字段名对应的getter方法的映射集合,并把结果保存到conflictingGetters变量中;resolveGetterConflicts()方法,主要用来处理重复的方法。注意:一个key会对应多个method的原因是:当子类覆盖了父类的getter方法且返回值发生变化时,会产生两个签名不同的方法。

  private void addGetMethods(Class<?> clazz) {
    Map<String, List<Method>> conflictingGetters = new HashMap<>();
    // 获得当前类中的所有方法,包括继承来的和实现的接口方法
    Method[] methods = getClassMethods(clazz);
    /**
     * 从所有方法中筛选出get方法
     * 规则是使用PropertyNamer的isGetter方法来判断是否是get方法:方法没有参数并且方法的名称以get或者是is开头
     * 然后使用PropertyNamer通过方法名来确定对应的属性,因为获取的方法中可能存在同名的重写与被重写方法,
     * 故先解决冲突,于是把所有的getter属性方法添加到临时conflictingGetters冲突集合待解决
     */
    Arrays.stream(methods)
      .filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
      .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    // 解决一个属性对应多个getter的问题
    resolveGetterConflicts(conflictingGetters);
  }

5、getClassMethods()方法

getClassMethods()方法,通过while循环,加载当前类及其所有父类中对应的Method对象,并把这些Method对象存储到了uniqueMethods对象中,其中唯一签名作为key,Method对象作为value值,即用来获取当前类以及其父类中定义的所有方法的唯一签名以及相应的Method对象。
 

  private Method[] getClassMethods(Class<?> clazz) {
    // 记录指定类中定义的全部方法的唯一签名以及对应的Method对象
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    while (currentClass != null && currentClass != Object.class) {
      // 记录currentClass这个类中定义的全部方法(uniqueMethods填充变更)
      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类终止循环迭代(获取父类,继续while循环)
      currentClass = currentClass.getSuperclass();
    }
    // 获取所有的method对象,并转换成数组返回
    Collection<Method> methods = uniqueMethods.values();

    return methods.toArray(new Method[0]);
  }

6、addUniqueMethods()方法

addUniqueMethods()方法,主要是为第二个参数, methods数组中的每个Method对象生成唯一签名,井记录到uniqueMethods变量中,根据生成的签名判断,如果已经添加过,就不再重复添加。

注意:虽然添加了重复判断,但是还是可能出现重复记录,因为当子类覆盖了父类的getter方法且返回值发生变化时,会产生两个签名不同的同名方法。

  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
        // 检测是否在子类中已经添加过该方法,如果在子类中已经添加过, 则表示子类覆盖了该方法,
        // 无须再向uniqueMethods集合中添加该方法了
        if (!uniqueMethods.containsKey(signature)) {
          uniqueMethods.put(signature, currentMethod);
        }
      }
    }
  }

7、getSignature()方法

生成方法的签名,得到的方法签名是全局唯一的,可以作为该方法的唯一标识, 格式:返回值类型#方法名:参数类型列表;以private String girl(String boy, Integer age)为例,方法的唯一签名是:java.lang.String#girl:java.lang.String,java.lang.Integer

  private String getSignature(Method method) {
    // 以private String girl(String boy, Integer age)为例
    StringBuilder sb = new StringBuilder(); // 用于拼接签名
    Class<?> returnType = method.getReturnType(); // 获取方法返回类型class
    System.out.println("######"+returnType);// 打印如:class java.lang.String
    if (returnType != null) {
      sb.append(returnType.getName()).append('#'); // 返回类型# 如:java.lang.String#
    }
    sb.append(method.getName()); // 返回类型#方法名 如:java.lang.String#girl
    Class<?>[] parameters = method.getParameterTypes(); // 方法入参类型数组class
    for (int i = 0; i < parameters.length; i++) {
      // 多个入参用“,”隔开,如:java.lang.String#girl:java.lang.String,java.lang.Integer
      sb.append(i == 0 ? ':' : ',').append(parameters[i].getName());
    }
    System.out.println(">>>>>>>"+sb.toString());
    return sb.toString();
  }

8、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;
      // 遍历当前属性的冲突方法 winner赢家 candidate候选人
      for (Method candidate : entry.getValue()) {
        if (winner == null) {
          winner = candidate;// 第一个时,默认是winner,跳过当前循环继续和后面的比较
          continue;
        }
        // 分别获得当前获胜方法的返回值类型和当前遍历到的方法的返回值类型
        Class<?> winnerType = winner.getReturnType();
        Class<?> candidateType = candidate.getReturnType();
        if (candidateType.equals(winnerType)) {
          // 返回类型相同,并且返回值类型不为boolean,那么标记为歧义
          if (!boolean.class.equals(candidateType)) {
            isAmbiguous = true;
            break;
          } else if (candidate.getName().startsWith("is")) {
            // 如果返回值类型是boolean,那么方法名以is开头的胜出
            winner = candidate;
          }
        } else if (candidateType.isAssignableFrom(winnerType)) {
          // 当前遍历到的方法的返回值是当前获胜者返回值类型的子类,获胜者不变
          // OK getter type is descendant getter 类型是后代
        } else if (winnerType.isAssignableFrom(candidateType)) {
          winner = candidate;
        } else {
          // 和上面的情况相反
          isAmbiguous = true;
          break;
        }
      }
      addGetMethod(propName, winner, isAmbiguous);
    }
  }

9、resolveSetterConflicts()方法

resolveSetterConflicts()方法,用来把上一步addMethodConflict()方法产生的conflictingGetters数据中重复的Method对象进行处理,变成一个字段对应一个Method对象形式的数据,然后通过addGetMethod()方法把对应key和value存到变量getMethods和getTypes中。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Reflector是一款比较强大的反编译工具,相信很多朋友都用过它,但reflector本身有很多局限性,比如只能一个一个的查看方法等,但 幸好reflector支持插件功能目前网上有很多reflector的插件,本人找取了两个应用范围很广,并且广受好评的插件: Reflector.FileDisassembler和Reflector.FileGenerator和Reflector进行了整合,放在了一个压 缩包中,大家可以直接解压缩后就开始使用,其中FileGenerator在网上没有现成dll,本人编译后将其取出,放在了压缩包中,一直在苦苦找寻的51aspx.com 点击下载Reflector集成压缩包 本压缩包中Reflector.exe已经升级为最新的5.0版本,具体的使用方法不多说了,只简单介绍一下本压缩包的 使用步骤: 先下载本压缩包并解压缩,运行其中的Reflector.exe(主程序),初次运行会弹出错误提示对话框,因为引用的两个插件DLL是按照我本地实际情况 配置的,所以你还需要根据你的实际情况来从新配置一下,方法很简单,我们拿引用FileGenerator.dll来做一个范例, 首先介绍一下 Filegenerator,FileGenerator插件的作用是:根据dll文件,把里面的源文件导出成文件,导出来的文件除了没有注释,变量名也 变了,其它的可谓是没有差别。对于一些比较好的控件,如果不是开源的,完全可以导出成文件,然后自己加上注释,少许修改,很好的利用起来。(不开源的 dll,用起来也不放心啊51aspx) 具体的初始化设置方法:点击View->Add-Ins...,弹出一个窗口,然后点击Add->选择文件夹里面的:"FileGenerator.dll",点击close. 然后回到Reflector窗口,Tool->Generator File(s)... 右边就出现了插件的窗口,选中左边的dll文件,点击右边的导出文件,源代码就全部导出来了! 另一个插件Reflector.FileDisassembler.dll设置方法也和这个一样,另外再提供一些Reflector的相关插件下载, Reflector是一款比较强大的反编译工具,相信很多朋友都用过它,但reflector本身有很多局限性,比如只能一个一个的查看方法等,但 幸好reflector支持插件功能目前网上有很多reflector的插件,本人找取了两个应用范围很广,并且广受好评的插件: Reflector.FileDisassembler和Reflector.FileGenerator和Reflector进行了整合,放在了一个压 缩包中,大家可以直接解压缩后就开始使用,其中FileGenerator在网上没有现成dll,本人编译后将其取出,放在了压缩包中,一直在苦苦找寻的 朋友这次可以拿来直接用了 点击下载Reflector集成压缩包 本压缩包中Reflector.exe已经升级为最新的5.0版本,具体的使用方法不多说了,只简单介绍一下本压缩包的使用步骤: 先 下载本压缩包并解压缩,运行其中的Reflector.exe(主程序),初次运行会弹出错误提示对话框,因为引用的两个插件DLL是按照我本地实际情况 配置的,所以你还需要根据你的实际情况来从新配置一下,方法很简单,我们拿引用FileGenerator.dll来做一个范例, 首先介绍一下 Filegenerator,FileGenerator插件的作用是:根据dll文件,把里面的源文件导出成文件,导出来的文件除了没有注释,变量名也 变了,其它的可谓是没有差别。对于一些比较好的控件,如果不是开源的,完全可以导出成文件,然后自己加上注释,少许修改,很好的利用起来。(不开源的 dll,用起来也不放心啊) 具体的初始化设置方法:点击View->Add-Ins...,弹出一个窗口,然后点击Add->选择文件夹里面的:"FileGenerator.dll",点击close. 然后回到Reflector窗口,Tool->Generator File(s)... 右边就出现了插件的窗口,选中左边的dll文件,点击右边的导出文件,源代码就全部导出来了! 另一个插件Reflector.FileDisassembler.dll设置方法也和这个一样,另外再提供一些Reflector的相关插件下载, 下载地址:http://bbs.51aspx.com/showtopic-764.html Reflector.FileDisassembler This add-in can be used to dump the disassembler output to fil

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡布奇诺-海晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值