mybatis源碼- 反射模塊一(跟着MyBatis學反射):類級別信息的封裝

反射就是在運行的狀態中, 對於任意的一個實體類, 都能知道這個類的所有屬性和方法。 並將其封裝成一個個對象, 對通過這些對象來實現對應實體類的創建, 以及訪問該類的方法和屬性。

在我們創建了一個Java類之後, 編譯出的.class文件在虛擬機中加載, 都會在JVM中創建一個Class對象,通過該對象來創建這個類的所有對象。

在 Mybatis 中, 有對應的反射模塊, 本文就是探究 mybatis 是如何進行反射的。 mybatis 中的反射主要與JavaBean相關。

1 JavaBean 規範

JavaBean 具有如下特徵:

  1. 所有的屬性都是私有的(通過 getter和setter 訪問)
  2. 擁有公有的無參數構造函數
  3. 提供 setter/getter
  4. 實現 Serializable 接口

2 Reflector和ReflectorFactory

mybatis 這種框架, 出於性能等方面的考慮, 必然不是等到使用的時候再去解析XML/再去解析反射類。

mybatis 爲每一個類提供了反射器類(Reflector), 該類中存儲了反射需要使用的類的元信息。

2.1 Reflector 屬性

2.1.1 屬性

Reflector

從類的屬性中, 我們可以看出:

  1. 一個反射器(Reflector)對應着一個 Class對象。
  2. 記錄了默認構造函數
  3. 其餘的是屬性及其setter|getter相關

setter&getter

對於一個屬性(沒錯, 屬性, 只有有 setter|getter 才能被稱之爲屬性)

  1. 如果是可讀的(有getter方法)則Reflector會將其及其方法處理後放入對應的集合中;
  2. 如果是可寫的(有setter方法), 則Reflector會將其及其方法處理後放入對應的可寫相關的集合中。
  3. 最後使用 Map<String, String> caseInsensitivePropertyMap 來記錄所有的屬性。

2.1.2 Invoker 接口

在存儲方法的時候, Reflector 使用的是 Map<String, Invoker>。 而不是 Map<String, Method>

該接口的定義也很簡單

/**
 * Invoker: 與方法的 invoke 相關
 * @author Clinton Begin
 */
public interface Invoker {
  // 調用方法
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  // 獲取類型
  Class<?> getType();
}

定義了方法的調用和獲取類型。
Invoker層次

  • MethodInvoker: 方法的Invoker
  • GetFieldInvoker: 如果沒有setter, 則使用該方法, 通過Filed類直接設置成員變量的值
  • SetFieldInvoker: 如果沒有getter, 則使用該方法, 通過Field類直接讀取成員變量的值

通過該封裝之後, 本來需要聲明 Map<String, Method>Map<String, Field> 表示的, 只需要使用 Map<String, Invoker> 即可表示。

2.2 Reflector 對外提供的方法

Reflector 對外提供的方法主要與構造函數和屬性相關。

方法

構造函數:根據 Class 對象,設置 Reflector 相應的成員變量。

  public Reflector(Class<?> clazz) 

檢查是否擁有了訪問的權限:除了訪問公有的變量, 還能訪問 default , protected 和p rivate 變量

   public static boolean canControlMemberAccessible() 

查找是否有相應的屬性

  public String   findPropertyName(String name)

獲取默認的構造函數:說實話, 不清楚爲啥要有這個方法, 不是可以通過 Class.newInstance() 進行創建嗎?

  public Constructor<?> getDefaultConstructor()

getter相關的方法

  public String[] getGetablePropertyNames() // 獲取所有的可讀屬性
  public Invoker getGetInvoker(String propertyName)// 獲取所有可讀屬性的 Invoker
  public Class<?> getGetterType(String propertyName)// 獲取對應屬性的類型
  public boolean hasGetter(String propertyName)// 對應屬性是否有相應的getter

對應的也有 setter 相關的方法

  public String[] getSetablePropertyNames() // 獲取所有的可讀屬性
  public Invoker getSetInvoker(String propertyName)// 獲取所有可讀屬性的 Invoker
  public Class<?> getSetterType(String propertyName)// 獲取對應屬性的類型
  public boolean hasSetter(String propertyName)// 對應屬性是否有相應的 setter

2.3 Reflector 私有方法

2.3.1 方法相關

每個 Relector 對應緩存一個類的元反射信息, 通過 Map 進行緩存, 後續我們在使用時就不需要再去遍歷查找, 可通過鍵查找即可。

因此, 就涉及到幾個方法

獲取方法簽名: 根據函數名稱、參數和返回值類型來取得簽名, 保證方法的唯一性

 private String getSignature(Method method)

該方法獲取每個方法的簽名。 獲取得到的簽名

返回值類型#方法名:參數1,參數2,參數3…

很顯然, 簽名的目的是唯一性。 那使用語言本身的特性來保證唯一性是最好的:

  1. 方法名不一致, 則方法就不一致
  2. 返回值不一致或者不是其子類, 則方法不一致
  3. 參數數量, 參數類型順序不一致方法也會不一樣

因此, 以上的簽名方式可以保證方法的唯一性。

獲取類的所有方法

  private Method[] getClassMethods(Class<?> cls)

注意, 由於在獲取方法時, 通過調用當前類及其除 Object 之外的所有父類的 getDeclaredMethods 方法及 getInterfaces() 方法, 因此, 其獲取到的方法是該類及其父類的所有方法。

由此, 產生了一個問題, 如果子類重寫了父類中的方法, 如果返回值相同, 則可以通過鍵重複來去掉。 但是, 如果方法返回值是父類相同實體方法返回值類型的子類, 則就會導致兩個方法是同一個方法, 但是簽名不同。 因此, 需要解決此類衝突。

解決方法衝突:getter方法衝突解決

  private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters)

到了此步驟的時候, 屬性已經以 propName->List<Method> 的形式存在於內存中。 此時, 需要Map<String, List>轉換爲Map<String, Invoker>

該方法只需要明白下面兩個條件就能很清晰了:

  1. 返回值類型不同

返回值類型不同, 則哪個方法的返回值是另一個方法返回值類型子類, 就把 propName 指向該方法包裝成的 Invoker。 這個很好理解, 畢竟重新(override)重寫時, 重寫方法的返回值類型可以是被重寫方法的子類。

  1. 返回值類型相同

按理來說不會出現這種情況, 因爲在獲取方法的時候已經使用簽名去除掉了, 因此此時可以拋出異常。 但是有一種特殊的情況(這個卡了我一段時間):

public boolean isBool() {return true;}// 方法1
public boolean getBool() {return false;}// 方法2

以上情況在 JavaBean 規範中是允許的(但是, 其實方法2幾乎大家都不會這麼用)。 因此, mybatis 通過以下的方式進行了過濾。

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

也就是說, mybatis 承認的是方法1這種。 方法2的忽略掉。

解決方法衝突:setter方法衝突解決

剛開始, 我也想不明白, 爲什麼setter也會出現衝突。畢竟沒有返回值類型, 也沒有上面的 boolean 特殊情況。 最後發現了, 還有一個情況, 泛型!!

 void setId(T id);// 父類
  public void setId(String id) {// 子類
    // Do nothing
  }
  

顯然, 遇到此類情況, 顯然, 子類中的方法纔是我們想要的:

    if (paramType1.isAssignableFrom(paramType2)) {
      return setter2;
    } else if (paramType2.isAssignableFrom(paramType1)) {
      return setter1;
    }

參數中, 父類方法泛型經過類型擦除後, 變成了 Object。 因此, 通過以上的方法, 那個是子類, 我們就獲取哪一個。

3 ReflectorFactory

看名稱, 工廠方法, 是爲了創建和緩存 Reflector 的。
ReflectorFactory

只有三個方法: 是否緩存, 設置要不要緩存, 根據類型查找 Reflector 對象(找不到則創建)。

其與 Reflector 的關係
關係

mybatis 爲我們提供了該方法的默認實現 DefaultReflectorFactory。 該類的實現很簡單, 就是通過ConcurrentMap<Class<?>, Reflector>Reflector進行緩存。

4 MetaClass

MetaClass 通過與屬性工具類的結合, 實現了對複雜表達式的解析,實現了獲取指定描述信息的功能。

4.1 成員變量

MetaClass成員

MetaClass 有兩個成員變量, 分別是 ReflectorFactoryReflector

4.2 創建

MetaClass 的構造函數是私有的。

  /**
   * MetaClass 構造函數
   */
  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
  }

但是, 其提供了兩個創建的方法。 這兩個方法也是通過該方法進行創建對象的。 該方法通過 Class 對象進行了 Reflector 對象的創建, 並賦值給成員變量。

  /**
   * 跟上面的是一樣的
   */
  public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }

通過屬性進行創建

  /**
   * 通過屬性名稱, 獲取屬性的 MetaClass
   */
  public MetaClass metaClassForProperty(String name) {
    Class<?> propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType, reflectorFactory);
  }

4.3 方法

該類中, 最重要的方法是:

    /**
     * 解析屬性表達式
     * 會去尋找reflector中是否有對應的的屬性
     * @param name
     * @param builder
     * @return
     */
  private StringBuilder buildProperty(String name, StringBuilder builder) {
    // 解析屬性表達式
    PropertyTokenizer prop = new PropertyTokenizer(name);
    // 是否有子表達式
    if (prop.hasNext()) {
      // 查找對應的屬性
      String propertyName = reflector.findPropertyName(prop.getName());
      if (propertyName != null) {
        // 追加屬性名
        builder.append(propertyName);
        builder.append(".");
        // 創建對應的 MetaClass 對象
        MetaClass metaProp = metaClassForProperty(propertyName);
        // 解析子表達式, 遞歸
        metaProp.buildProperty(prop.getChildren(), builder);
      }
    } else {
      // 根據名稱查找屬性
      String propertyName = reflector.findPropertyName(name);
      if (propertyName != null) {
        builder.append(propertyName);
      }
    }
    return builder;
  }

理解了這個方法(遞歸, 該類中有很多類似的), 就可以很好的對這個類進行理解, 以查找(richType.richProperty)爲例:

  1. 通過 PropertyTokenizer 對表達式進行解析, 得到當前的 name=richType, children=richProperty
  2. 從 reflector 中查找該 richType 屬性
  3. 將 richType 添加到 builder 中
  4. 使用 metaClassForProperty 創建 richType 的 MetaClass
  5. 遞歸調用自身來處理子表達式

退出的條件就是沒有子表達式。 這個就是爲了, 我們類中有成員變量是類, 我們可以通過其找到他們的所有類及其屬性。

注意, 在此過程中, ReflectorFactory 一直是同一個, 而其內部緩存了多個 Reflector 對象。

5 總結

類的關係:

關係

Reflector 實現了實體類元信息的封裝, 但對類中的成員變量是類的情況沒有進行處理。 而 MetaClass 通過 ReflectorFactory 類型的成員變量, 實現了實體類中成員變量是類情況的處理。從而結合屬性工具類實現了對複雜表達式的處理。


一起學 mybatis

你想不想來學習 mybatis? 學習其使用和源碼呢?那麼, 在博客園關注我吧!!

我自己打算把這個源碼系列更新完畢, 同時會更新相應的註釋。快去我的github star 吧!!
項目

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章