Mybatis 源码学习-反射工具(Reflector)

Posted 凉茶方便面

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 源码学习-反射工具(Reflector)相关的知识,希望对你有一定的参考价值。

历史文章 :
Mybatis 源码学习(1)-解析器模块


由于 JDK 提供的反射机制过于复杂,因此 Mybatis 对常用的反射机制做了封装,以简化反射 API,这部分封装代码在 org.apache.ibatis.reflection 包中。

Reflector

Reflector 主要用于解析一个 Class 对象,将其类型、构造函数、set方法、get 方法做解析,方便随时使用。这里的 get/set 方法解析,只根据方法进行解析,而不关心其中是否存在对应的 filed,如:getA()、setA(),即使没有对应的字段 a,也会认为对象存在属性 a。

字段说明

Reflector 解析后的字段包括:

// 对应的 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<>();
// 属性相应 的 getter 方法集合,key 是属性名称,value 也是 Invoker 对象
private final Map<String, Invoker> getMethods = new HashMap<>();

// 记录了属性相应的 setter 方法的参数值类型(第一个参数的类型),
// key 是属性名称,value 是 setter 方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
// 记录了属性相应的 getter 方法的返回位类型,
// key是属性名称,value 是 getter 方法的返回位类型 
private final Map<String, Class<?>> getTypes = new HashMap<>();

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

// 记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

Reflector 的核心处理过程都在它的构造器中实现,通过 java 的反射机制,构造对应字段的信息,解析过程如下。

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

  // 根据 getMethods/setMethods 集合,初始化可读 /写属性的名称集合 
  readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
  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.addDefaultConstructor

addDefaultConstructor 方法比较简单,就是简单的通过 clazz.getDeclaredConstructors() ,然后遍历所有构造器,选取参数个数为0的构造器为默认构造器。

Reflector.addGetMethods

addGetMethods 的处理过程分为三步:

  • 根据类的继承关系,依次向上层遍历,解析构造方法签名与 Method 的对应关系 Map;
  • 逐个解析每个 getter 方法的名称,并构建 getter 方法名与 getter 方法关联的 conflictingGetters(Map<String, List<Method>>) 对象;
  • 按照父子类继承关系,解析每个方法的冲突列表,以子类的 getter 方法为准。

比如,对于父类 A 和子类 SubA,都有 getNames 方法,但是父类的返回值是 List<String>,子类的返回值 ArrayList<String>,此时解析到的方法名称是 java.util.List#getNamesjava.util.ArrayList#getNames,而我们需要的是子类的方法而不是父类的方法,即我们需要的是 java.util.ArrayList#getNames

1 解析签名和 Method

// 解析 Method 方法
private Method[] getClassMethods(Class<?> cls) 
  // 用于记录指定类中定义的全部方法的唯一签名以及对应的 Method 对象
  Map<String, Method> uniqueMethods = new HashMap<String, Method>();
  Class<?> currentClass = cls;
  while (currentClass != null && currentClass != Object.class) 
    addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());

    // 查看接口中的定义,避免抽象方法
    Class<?>[] interfaces = currentClass.getInterfaces();
    for (Class<?> anInterface : interfaces) 
      addUniqueMethods(uniqueMethods, anInterface.getMethods());
    

    // 继续向父类进行遍历
    currentClass = currentClass.getSuperclass();
  

  Collection<Method> methods = uniqueMethods.values();
  // 转换成 Methods 数纽返回
  return methods.toArray(new Method[methods.size()]);


// 生成方法的签名
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) 
  for (Method currentMethod : methods) 
    if (!currentMethod.isBridge()) 
      // 通过 Reflector.getSignature () 方法得到的方法签名是:
      // 返回值类型#方法名称:参数类型列表。
      // 例如,Reflector.getSignature(Method)方法的唯一签名是:
      // java.lang.String#getSignature:java.lang.reflect.Method
      // 通过 Reflector.getSignature() 方法得到的方法签名是全局唯一的,
      // 可以作为该方法的唯一标识
      String signature = getSignature(currentMethod);
      // 检测是否在子类中已经添加过该方法,如果在子类中已经添加过,
      // 无须再向 uniqueMethods 集合中添加该方法了 
      if (!uniqueMethods.containsKey(signature)) 
        if (canAccessPrivateMethods()) 
          try 
            currentMethod.setAccessible(true);
           catch (Exception e) 
          
        
        // 记录该签名和方法的对应关系
        uniqueMethods.put(signature, currentMethod);
      
    
  

2 将 getter 方法名与对应 Method 关联起来

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

3 解决父子类方法冲突

private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) 
  // 遍历 conflictingGetters 集合
  for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) 
    Method winner = null;
    String propName = entry.getKey();
    // 遍历  key 对应的 List
    for (Method candidate : entry.getValue()) 
      // 第一次找到该 key,直接返回
      if (winner == null) 
        winner = candidate;
        continue;
      
      // 多次找到该 key 对应的 Method
      Class<?> winnerType = winner.getReturnType();
      Class<?> candidateType = candidate.getReturnType();
      // 没有使用多态,直接子类重写父类方法
      if (candidateType.equals(winnerType)) 
        // 当某字段存在多个 getter 方法,则可能是 get/is 方法并存
        // 并存时,则必需是 boolean 值(否则不符合 JavaBean 规范),
        // 即非 boolean 值不允许用 isXXX 方法,
        // 因此,此处检查返回值类型,必需是 boolean 值
        if (!boolean.class.equals(candidateType)) 
          throw new ReflectionException();
         else if (candidate.getName().startsWith("is")) 
          // 如果存在 isXXX 方法,以 is 方法为准
          // 否则即可认为使用第一个找到的方法
          winner = candidate;
        
       else if (candidateType.isAssignableFrom(winnerType)) 
        // winnerType 是更具体的类,是 candidateType 的子类
       else if (winnerType.isAssignableFrom(candidateType)) 
        // candidateType 更具体,需返回子类
        winner = candidate;
       else 
        // 其他均是异常
        throw new ReflectionException();
      
    
    addGetMethod(propName, winner);
  


// 继续完成对 getMethods 和 getTypes 的填充
private void addGetMethod(String name, Method method) 
  // 检测属性名是否合法(非$开头,非 class,非序列化 id)
  if (isValidPropertyName(name)) 
    // 将属性名以及对应的 MethodInvoker 对象添加到 getMethods 集合 
    getMethods.put(name, new MethodInvoker(method));
    // 获取返回值的 Type
    Type returnType = TypeParameterResolver.resolveReturnType(method, type);
    // 将属性名称及其 getter 方法的返回值类型添加到 getTypes 集合中
    getTypes.put(name, typeToClass(returnType));
  

addGetMethods 的整体处理过程如下:

private void addGetMethods(Class<?> cls) 
  Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
  // 步骤 1,解析类及类的父接口的方法
  Method[] methods = getClassMethods(cls);
  // 步骤 2,查找 getter 方法,并与对应 Method 关联到 conflictingGetters 中
  for (Method method : methods) 
    if (method.getParameterTypes().length > 0) 
      continue;
    
    String name = method.getName();
    // JavaBean 规范的取值方法只有 getter/is,且比前缀长
    if ((name.startsWith("get") && name.length() > 3)
        || (name.startsWith("is") && name.length() > 2)) 
      // 解析对应属性名称
      name = PropertyNamer.methodToProperty(name);
      // 将属性名与对应的 Method 记录到 conflictingGetters 中
      addMethodConflict(conflictingGetters, name, method);
    
  
  // 步骤 3,对 conflictingGetters 集合解决冲突 
  resolveGetterConflicts(conflictingGetters);

Reflector.addSetMethods

addSetMethods 的逻辑和 addGetMethods 的过程类似,但是它们的处理冲突的逻辑不一样。

private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) 
  for (String propName : conflictingSetters.keySet()) 
    List<Method> setters = conflictingSetters.get(propName);
    // 检查是否有 getter 方法
    Class<?> getterType = getTypes.get(propName);
    Method match = null;
    ReflectionException exception = null;
    // 遍历所有的 setter
    for (Method setter : setters) 
      // 只关心第一个参数
      Class<?> paramType = setter.getParameterTypes()[0];
      // 直接返回与 getter 方法参数类型相同的方法
      if (paramType.equals(getterType)) 
        // should be the best match
        match = setter;
        break;
      
      if (exception == null) 
        try 
          // 无异常时,返回最佳匹配
          // 最佳匹配的逻辑是选取 match 和 setter 中更具体的那个类
          // 如 List 和 ArrayList,返回 ArrayList
          match = pickBetterSetter(match, setter, propName);
         catch (ReflectionException e) 
          match = null;
          exception = e;
        
      
    
    if (match == null) 
      throw exception;
     else 
      addSetMethod(propName, match);
    
  

Reflector.addFields

addFields 会把所有的无 getter/setter 的字段添加到 setMethods、setTypes、getMethods、getTypes中。

private void addFields(Class<?> clazz) 
  // 获取 clazz 中定义的全部字段
  Field[] fields = clazz.getDeclaredFields();
  for (Field field : fields) 
    if (canAccessPrivateMethods()) 
      try 
        field.setAccessible(true);
       catch (Exception e) 
      
    
    if (field.isAccessible()) 
      // 当 setMethods 集合不包含同名属性时,
      // 将其记录到 setMethods 集合和 setTypes 集合
      if (!setMethods.containsKey(field.getName())) 
        int modifiers = field.getModifiers();
        // 过滤掉 static && final 修饰的字段,因为 final 可以经过反射设值
        if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) 
          // addSetField 方法的功能是填充 setMethods 集合和 setTypes 集合
          addSetField(field);
        
      
      // 当 getMethods 集合中不包含同名属性时,
      // 将其记录到 getMethods 集合和 getTypes 集合
      if (!getMethods.containsKey(field.getName())) 
        // addGetField 方法的功能是填充 getMethods 集合和 getTypes 集合 
        addGetField(field);
      
    
  
  if (clazz.getSuperclass() != null) 
    // 继续处理父类中定义的字段
    addFields(clazz.getSuperclass());
  

Reflector中提供了多个 get*()方法用于读取上述集合生成的信息。

Invoker

在 addSetField、addGetField、addGetMethod、addSetMethods中,会将 setter/getter 对应的 Method 对象或者字段对应的 Field 包装成 Invoker 对象。

public interface Invoker 
  // 调用获取指定字段的值或执行指定的方法
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  // 返回属性相应的类型
  Class<?> getType();

其继承结构如下:

GetFieldInvoker/SetFieldInvoker 是对 Field 对象的封装,分别通过调用 Field.get()/set() 作为其对应的实现。MethodInvoker 是对 Method 对象的封装,通过调用 Method.invoke() 方法作为其实现。

ReflectorFactory

ReflectorFactory 接口提供了对 Reflector 对象的创建和缓存功能,其定义如下:

public interface ReflectorFactory 
  // 检测该 ReflectorFactory 对象是否会缓存 Reflector 对象 
  boolean isClassCacheEnabled();
  // 设置缓存 Reflector 对象的标记
  void setClassCacheEnabled(boolean classCacheEnabled);
  // 创建指定 Class 对应的 Reflector 对象
  Reflector findForClass(Class<?> type);

Mybatis 提供了 ReflectorFactory 的默认实现 DefaultReflectorFactory,它内部包含了一个 Reflector 相关的 ConcurrentMap 作为缓存,并且无过期时间设定。DefaultReflectorFactory 还提供了 findForClass 方法的实现,为尚未加入缓存的 Class 创建 Reflector 对象。

public class DefaultReflectorFactory implements ReflectorFactory 
  // 该字段决定是否开启对 Reflector 对象的缓存 
  private boolean classCacheEnabled = true;
  // 使用 ConcurrentMap 集合实现对 Reflector 对象的缓存 
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();

  // 外部获取缓存标记
  public boolean isClassCacheEnabled() 
    return classCacheEnabled;
  

  // 外部设置缓存标记
  public void setClassCacheEnabled(boolean classCacheEnabled) 
    this.classCacheEnabled = classCacheEnabled;
  

  public Reflector findForClass(Class<?> type) 
    if (classCacheEnabled)  // 检测是否开启缓存
      Reflector cached = reflectorMap.get(type);
      if (cached == null) 
        cached = new Reflector(type); // 创建 Reflector 对象
        reflectorMap.put(type, cached); // 放入 ConcurrentMap 中缓存
      
      return cached;
     else 
      return new Reflector(type); // 未开启缓存,则直接创建并返回Reflector对象
    
  

总结

Reflector 工具集主要用于 JavaBean 解析,分别处理类型上的方法和字段,并且通过 ReflectorFactory 对外提供 Reflector 的缓存,方便 JavaBean 对象和数据库字段进行映射。


参考文档:《Mybatis 技术内幕》

本文的基本脉络参考自《Mybatis 技术内幕》,编写文章的原因是希望能够系统地学习

以上是关于Mybatis 源码学习-反射工具(Reflector)的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis 源码学习-反射工具(Reflector)

Mybatis 源码学习-反射工具(MetaClass)

Mybatis 源码学习-反射工具(TypeParameterResolver)

Mybatis 源码学习-反射工具(ObjectWrapper & MetaObject)

Mybatis 源码学习-反射工具(ObjectWrapper & MetaObject)

反射随笔:反射包的结构