MyBatis源碼分析-1-基礎支持層-反射模塊-Reflector/ReflectorFactory

本文主要介紹MyBatis的反射模塊是如何實現的。apache

MyBatis 反射的核心類Reflector,下面我先說明它的構造函數和成員變量。具體方法下面詳解。緩存

org.apache.ibatis.reflection.Reflector
public class Reflector {

  private final Class<?> type; //對應的Class 類型
  //可讀屬性的名稱集合,可讀屬性就是存在相應getter 方法的屬性,初始值爲空數紐
  private final String[] readablePropertyNames;
  //可寫屬性的名稱集合,可寫屬性就是存在相應setter 方法的屬性,初始值爲空數紐
  private final String[] writeablePropertyNames;
  //記錄了屬性相應的setter 方法, key 是屬性名稱, value 是Invoker 對象,它是對setter 方法對應
  private final Map<String, Invoker> setMethods = new HashMap<>();
  //記錄了屬性相應的getter 方法, key 是屬性名稱, value 是Invoker 對象,它是對setter 方法對應
  private final Map<String, Invoker> getMethods = new HashMap<>();
  //記錄了屬性相應的setter 方法的參數值類型, ke y 是屬性名稱, 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<>();

  public Reflector(Class<?> clazz) {
    type = clazz;
    //查找clazz的無參構造方法,經過反射遍歷全部構造方法,找到構造參數集合長度爲0的。
    addDefaultConstructor(clazz);
    //處理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合
    addGetMethods(clazz);
    //處理clazz 中的set ter 方法,填充setMethods 集合和set Types 集合
    addSetMethods(clazz);
    //處理沒有get/set的方法字段
    addFields(clazz);
    //初始化可讀寫的名稱集合
    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);
    }
  }
    。。。。。。。。。。。。具體代碼先忽略,經過構造函數的調用慢慢滲透。
}

1:addDefaultConstructor() // 查找clazz的無參構造方法,經過反射遍歷全部構造方法,找到構造參數集合長度爲0的
主要實現的思想是,經過 clazz.getDeclaredConstructors();獲取全部構造方法集合,而後循環遍歷 判斷參數長度爲0的,而且構造函數權限可控制的設爲默認構造方法。mybatis

private void addDefaultConstructor(Class<?> clazz) {
  Constructor<?>[] consts = clazz.getDeclaredConstructors();
  for (Constructor<?> constructor : consts) {
    if (constructor.getParameterTypes().length == 0) {
    //判斷反射對象的控制權限 爲true是可控制
      if (canControlMemberAccessible()) {
        try {
        //設置Accessible爲true後,反射能夠訪問私有變量。
          constructor.setAccessible(true);
        } catch (Exception e) {
          // Ignored. This is only a final precaution, nothing we can do.
        }
      }
      if (constructor.isAccessible()) {
        this.defaultConstructor = constructor;
      }
    }
  }
}

2:addGetMethods(clazz)// 處理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合ide

private void addGetMethods(Class<?> cls) {
  Map<String, List<Method>> conflictingGetters = new HashMap<>();
  //獲取當前類以及父類中定義的全部方法的惟一簽名以及相應的Method對象。
  Method[] methods = getClassMethods(cls);
  for (Method method : methods) {
    if (method.getParameterTypes().length > 0) {
      continue;
    }
    String name = method.getName();
    //判斷若是方法明是以get開頭而且方法名長度大於3 或者 方法名是以is開頭而且長度大於2
    if ((name.startsWith("get") && name.length() > 3)
        || (name.startsWith("is") && name.length() > 2)) {
      //將方法名截取,若是是is從第二位截取,若是是get或者set從第三位開始截取
      name = PropertyNamer.methodToProperty(name);
       
       //addMethodConflict 方法內部很簡單隻有兩行代碼:
       //1:List<Method> list=conflictingGetters.computeIfAbsent(name,K->new ArrayList<>());  這句話的意思是,在conflictingGetters 的Map中 若是key中存在name,name什麼都不作,將value返回,若是name不存在,則返回一個新的ArrayList.
       //2:list.add(method); 將方法對象添加到list對象中。
      addMethodConflict(conflictingGetters, name, method);
    }
  }
  resolveGetterConflicts(conflictingGetters);
}

    2-1:getClassMethods(cls);//獲取當前類以及父類中定義的全部方法的惟一簽名以及相應的Method對象。函數

private Method[] getClassMethods(Class<?> cls) {
  Map<String, Method> uniqueMethods = new HashMap<>();
  Class<?> currentClass = cls;
  while (currentClass != null && currentClass != Object.class) {
    //currentClass.getDeclaredMethods(),獲取當前類的全部方法
    //addUniqueMethods 爲每一個方法生成惟一簽名,並記錄到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());
    }

    currentClass = currentClass.getSuperclass();
  }

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

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

        2-1-1: addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); 爲每一個方法生成惟一簽名,並記錄到uniqueMethods集合中 this

private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
  for (Method currentMethod : methods) {
    //判斷是否是橋接方法, 橋接方法是 JDK 1.5 引入泛型後,爲了使Java的泛型方法生成的字節碼和 1.5 版本前的字節碼相兼容,由編譯器自動生成的方法 
    if (!currentMethod.isBridge()) {
      //獲取簽名 
      // 簽名格式爲:方法返回參數#方法名:參數名     ps:多個參數用,分割  簽名樣例:String#getName:User
      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
      //若是簽名存在,則不作處理,表示子類已經覆蓋了該方法。
      //若是簽名不存在,則將簽名做爲Key,Method做爲value 添加到uniqueMethods中
      if (!uniqueMethods.containsKey(signature)) {
        if (canControlMemberAccessible()) {
          try {
            currentMethod.setAccessible(true);
          } catch (Exception e) {
            // Ignored. This is only a final precaution, nothing we can do.
          }
        }

        uniqueMethods.put(signature, currentMethod);
      }
    }
  }
}

    2-2: resolveGetterConflicts(conflictingGetters);;//在2-1中返回的方法可能存在,兩個相同的方法名稱,由於當子類實現父類方法時且參數不一樣,此時2-1生成的簽名是不一樣的生成簽名的規則是 方法返回值#方法名#參數名,那麼就會返回兩個相同的方法名。 resolveGetterConflicts方法會對這種覆寫的狀況進行處理,同時將處理後的getter方法記錄到getMethods集合中,將其返回值類型填充到getTypes集合中。 內部實現主要是兩個for循環,循環比較方法名稱相同的狀況下,返回值不一樣的狀況下,拿第二個當最終想要的Method。spa

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();
      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.");
        } else 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 {
        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);
  }
}

總結一下addGetMethods(clazz)方法 addSetMethods(clazz)大體相同:xml

首先建立:對象

Map<String, List<Method>> conflictingGetters = new HashMap<>();

1:獲取子類和父類的全部方法。 獲取方法是 先生成惟一簽名,惟一簽名規則是方法返回值#方法名:方法參數1,方法參數2 。 根據簽名做爲key,method對象做爲value生成Map,經過簽名進行過濾,將此M,ap轉換爲List返回。接口

2:循環遍歷Map,找到符合條件的方法名,is開頭或者get開頭的,將方法名截取,截取後的方法名做爲key,List<Method>做爲value,放入到conflictingGetters中。

3:因爲子類存在實現父類方法,且返回值不一樣的狀況,致使用一方法名 可能有不一樣的Method ,第三步 resolveGetterConflicts方法會對這種覆寫的狀況進行處理,同時將處理後的getter方法記錄到getMethods集合中,將其返回值類型填充到getTypes集合中。

 

Reflector Factory 接口主要實現了對Reflector 對象的建立和緩存,有三個方法:該接口定義以下:

public interface ReflectorFactory {

  boolean isClassCacheEnabled(); //檢測該ReflectorFactory對象是否會緩存Reflector對象

  void setClassCacheEnabled(boolean classCacheEnabled);//設置是否緩存Reflector對象

  Reflector findForClass(Class<?> type); //建立指定class對應的Reflector對象
}

Reflector Factory的實現是DefaultReflectorFactory,具體實現以下:

public class DefaultReflectorFactory implements ReflectorFactory {
  private boolean classCacheEnabled = true;
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

  public DefaultReflectorFactory() {
  }

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

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

  @Override
  public Reflector findForClass(Class<?> type) {
    //若是開啓緩存,Reflector對象從ConcurrentMap<Class<?>, Reflector> 取出。
    if (classCacheEnabled) {
            // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {//沒開啓緩存,從新建立。
      return new Reflector(type);
    }
  }

}

DefaultReflectorFactory 的緩存是經過ConcurrentMap來實現的,若是開啓了緩存,那麼就從ConcurrentMap取Reflector,若是沒有開啓,就新建Reflector.

除了使用MyBatis 提供的DefaultReflectorFactory 實現,咱們還能夠在mybatis-config .xml中配置自定義的ReflectorFactory 實現類,從而實現功能上的擴展。

相關文章
相關標籤/搜索