天天看點

mybatis源碼學習------Reflector類Reflector簡介Reflector的字段Reflector的構造函數ReflectorFactory接口DefaultReflectorFactory

Reflector簡介

Reflector反射器,每個Reflector對象對應一個類,該對象會緩存反射操作所需要的類元資訊,便于後續反射操作。

Reflector的類注釋如下(難得作者心情好,寫了兩行注釋):

This class represents a cached set of class definition information that allows for easy mapping between property names and getter/setter methods.

此類表示一組緩存的類定義資訊,可輕松在屬性名稱和getter / setter方法之間進行映射

Reflector的字段

Reflector的構造函數會對所有字段進行初始化

因為mybatis是一個orm架構,故其對反射使用最多的場景就是将封裝了請求參數的pojo拼接到對應的sql語句中,或是将sql查詢出的結果集映射到對應的pojo對象中,是以調用pojo的getter和setter方法是最頻繁的。觀察Reflector類中定義的字段可以發現,Reflector類就是為了更好的服務于ORM映射的使用場景而設計。

private final Class<?> type;//Reflector對應的類
private final String[] readablePropertyNames;//可讀屬性的名稱集合
private final String[] writablePropertyNames;//可寫屬性的名稱集合
private final Map<String, Invoker> setMethods = new HashMap<>();//所有屬性的set方法
private final Map<String, Invoker> getMethods = new HashMap<>();//所有屬性的get方法
private final Map<String, Class<?>> setTypes = new HashMap<>();//所有set方法的類型
private final Map<String, Class<?>> getTypes = new HashMap<>();//所有get方法的類型
private Constructor<?> defaultConstructor;//預設的構造函數(空參構造)

private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();//不區分大小的屬性集合
           

Reflector的構造函數

構造函數為Reflector類的核心方法,Reflector的構造函數會對上述所有字段進行初始化,便于後續調用,是以本文重點分析構造函數中的邏輯。

構造函數的源碼如下:

public Reflector(Class<?> clazz) {
    type = clazz;
    addDefaultConstructor(clazz);
    addGetMethods(clazz);
    addSetMethods(clazz);
    addFields(clazz);
    readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }
           

因為構造函數中的邏輯較多,是以簡單梳理了如下思維導圖,可以對代碼的邏輯有一個大緻的印象,便于後續閱讀。

mybatis源碼學習------Reflector類Reflector簡介Reflector的字段Reflector的構造函數ReflectorFactory接口DefaultReflectorFactory

接下來開始詳細分析Reflector類的構造函數源碼

設定Class

在Reflector簡介的章節中說過,Reflector對象的作用是緩存反射操作所需要的類元資訊,便于後續反射操作調用。是以類和Reflector對象是一一對應的關系,故構造函數的第一行代碼就是初始化type字段。

addDefaultConstructor

addDefaultConstructor(clazz)方法的作用是找到該類的預設空參構造器。

源碼如下:

private void addDefaultConstructor(Class<?> clazz) {
    //擷取該類聲明的所有構造函數
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    //找到空參的構造器,并将其賦給defaultConstructor字段
    Arrays.stream(constructors)
        .filter(constructor -> constructor.getParameterTypes().length == 0)
        .findAny()
        .ifPresent(constructor -> this.defaultConstructor = constructor);
  }
           

對應非lamda代碼為(未測試,僅供參考):

private void addDefaultConstructor(Class<?> clazz) {
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) 
        if(constructor.getParameterTypes().length == 0){
            this.defaultConstructor = constructor;
    }
  }
           

代碼邏輯簡單,不多BB直接過。

addGetMethods

addGetMethods(clazz)方法的作用找到clazz對應的類中的所有

private void addGetMethods(Class<?> clazz) {
    //因為子類可能會重寫父類相同的方法,是以資料結構設計為name->List<Method>
    Map<String, List<Method>> conflictingGetters = new HashMap<>();
  	Method[] methods = getClassMethods(clazz);
    //這段lamda的意思是從數組中找到所有參數長度為0,且以get開頭的方法,并将其添加到addMethodConflict中
  	Arrays
      	.stream(methods)
      	.filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
    	.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    //解析沖突的get方法	
  	resolveGetterConflicts(conflictingetters);
}
           

getClassMethods

該方法的作用是從目前類開始,遞歸向上查找所有父類和接口中定義的方法

private Method[] getClassMethods(Class<?> clazz) {
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    //遞歸退出條件
    while (currentClass != null && currentClass != Object.class) {
      //将目前類中聲明的方法添加到Map集合uniqueMethods中
      addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
      //擷取目前類的實作的所有接口
      Class<?>[] interfaces = currentClass.getInterfaces();
      //周遊将目前類所實作的接口中聲明的方法添加到Map集合uniqueMethods中
      for (Class<?> anInterface : interfaces) {
        addUniqueMethods(uniqueMethods, anInterface.getMethods());
      }
      //擷取目前類的父類,繼續向上遞歸
      currentClass = currentClass.getSuperclass();
    }
    Collection<Method> methods = uniqueMethods.values();
    return methods.toArray(new Method[0]);
}
           

addUniqueMethods

添加methods數組中的所有方法到map中,其中key是目前方法的簽名,mybatis自定義了方法簽名的格式,用于區分重寫方法和重載方法,自定義方法簽名的格式為:

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

addUniqueMethods方法的邏輯較為簡單,不做分析。不過需要注意的是方法并沒有将橋接方法添加到map中,關于什麼是橋接方法,可以參考文章

[]: https://www.zhihu.com/question/54895701/answer/141623158 “Java反射中method.isBridge()由來,含義和使用場景?”

private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
    for (Method currentMethod : methods) {
      if (!currentMethod.isBridge()) {
        String signature = getSignature(currentMethod);
        if (!uniqueMethods.containsKey(signature)) {
          uniqueMethods.put(signature, currentMethod);
        }
      }
    }
  }
           

addMethodConflict

1、調用isValidPropertyName方法判斷目前屬性名稱是否是合法的屬性名稱,比如不能是以$開頭,屬性名不等于serialVersionUID等等

2、将目前方法添加到conflictingMethods中

private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
  if (isValidPropertyName(name)) {
    List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
    list.add(method);
  }
}
           

resolveGetterConflicts

周遊每個屬性,查找其最比對的方法。因為子類可以覆寫父類的方法,是以一個屬性,可能對應多個 getting 方法。

winner表示勝者,candidate表示候選人,代碼邏輯不複雜,直接看注釋即可。

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;//是否有二義性
    for (Method candidate : entry.getValue()) {
      //初始情況,直接指派
      if (winner == null) {
        winner = candidate;
        continue;
      }
      Class<?> winnerType = winner.getReturnType();
      Class<?> candidateType = candidate.getReturnType();
      if (candidateType.equals(winnerType)) {
        //如果候選者的傳回值類型和勝者的傳回值類型相同,且不是布爾類型,則表示出現了二義性。
        //也就是說在前面的addUniqueMethods()方法中并沒有對這兩個方法進行合并,這種情況是不允許的。
        //是以将isAmbiguous标志位置為True,在稍後的代碼邏輯中會抛出對應的異常(ReflectionException)		//終止程式運作
        if (!boolean.class.equals(candidateType)) {
          isAmbiguous = true;
          break;
        } else if (candidate.getName().startsWith("is")) {
          //如果是boolean類型的話,屬性名為is開頭的優先級更高
          winner = candidate;
        }
          //下面這段不了解的話可以看下面的例子
      } else if (candidateType.isAssignableFrom(winnerType)) {
          //如果此時candidateType是winnerType的父類,則此時不作操作
      } else if (winnerType.isAssignableFrom(candidateType)) {
          //如果此時winnerType是candidateType的父類,則candidate更适合
        winner = candidate;
      } else {
        isAmbiguous = true;
        break;
      }
    }
    addGetMethod(propName, winner, isAmbiguous);
  }
}
           

舉例

public class Father<T> {
  private T nickname;
  
  public T getNickname() {
    return nickname;
  }

  public void setNickname(T nickname) {
    this.nickname = nickname;
  }
}

public class Son extends Father<String>{
  private String nickname;

  @Override
  public String getNickname() {
    return nickname;
  }
    
  @Override
  public void setNickname(String nickname) {
    this.nickname = nickname;
  }
}
           

以上述兩個類為例,Father類的nickname字段的類型為Object(泛型擦除後),而Son類的nickname字段的類型為String。在出現這個情況時,上述代碼會選擇子類的該方法。

讀者可debug下面的測試類進行調試

@Test
  void testCreateReflactor(){
    ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    Reflector reflector = reflectorFactory.findForClass(Son.class);
    Assertions.assertEquals(String.class, reflector.getGetterType("nickname"));
  }
           

addGetMethod

最後會調用addGetMethod(String name, Method method, boolean isAmbiguous)方法将選擇出的get方法添加到目前Reflector對象的getMethods和getTypes屬性中。

代碼中的MethodInvoker和TypeParameterResolver.resolveReturnType(method, type)方法會在後續文章中進行詳解

private void addGetMethod(String name, Method method, boolean isAmbiguous) {
  //當isAmbiguous為true,即出現二義性時,後續代碼會抛出ReflectionException異常,終止程式運作
  MethodInvoker invoker = isAmbiguous
    ? new AmbiguousMethodInvoker(method, MessageFormat.format(
    "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
    name, method.getDeclaringClass().getName()))
    : new MethodInvoker(method);
  //添加到getMethods屬性中
  getMethods.put(name, invoker);
  //解析get方法的傳回值類型
  Type returnType = TypeParameterResolver.resolveReturnType(method, type);
  //添加到getTypes屬性中
  getTypes.put(name, typeToClass(returnType));
}
           

addGetMethods方法邏輯梳理

mybatis源碼學習------Reflector類Reflector簡介Reflector的字段Reflector的構造函數ReflectorFactory接口DefaultReflectorFactory

addSetMethods

addSetMethods方法的邏輯與addGetMethods方法的邏輯相似,差異點在于resolveSetterConflicts方法,即如何選擇出合适的set方法,是以本節直接分析resolveSetterConflicts方法即可,其餘部分略略略。。。

private void addSetMethods(Class<?> clazz) {
  Map<String, List<Method>> conflictingSetters = new HashMap<>();
  Method[] methods = getClassMethods(clazz);
  Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
    .forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
  resolveSetterConflicts(conflictingSetters);
}
           

resolveSetterConflicts

該方法借助了getTypes和getMethods進行判斷,邏輯簡單,直接看注釋就好

private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {
      String propName = entry.getKey();
      List<Method> setters = entry.getValue();
      Class<?> getterType = getTypes.get(propName);
      boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
      boolean isSetterAmbiguous = false;
      Method match = null;
      for (Method setter : setters) {
        //getter方法傳回的類型和目前setter方法的入參類型相同,即找到最合适的set方法
        if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
          match = setter;
          break;
        }
        //沒有找到對應的get方法,且set方法不存在二義性,處理隻有set方法沒有get方法的情況
        if (!isSetterAmbiguous) {
          match = pickBetterSetter(match, setter, propName);
          isSetterAmbiguous = match == null;
        }
      }
      //如果找到合适的set方法,則将其添加到setMethods和setTypes屬性中
      if (match != null) {
        addSetMethod(propName, match);
      }
    }
  }
           

pickBetterSetter

與resolveGetterConflicts方法相關的邏輯大同小異

private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    if (setter1 == null) {
      return setter2;
    }
    Class<?> paramType1 = setter1.getParameterTypes()[0];
    Class<?> paramType2 = setter2.getParameterTypes()[0];
    //兩個類型進行比較取子類
    if (paramType1.isAssignableFrom(paramType2)) {
      return setter2;
    } else if (paramType2.isAssignableFrom(paramType1)) {
      return setter1;
    }
    MethodInvoker invoker = new AmbiguousMethodInvoker(setter1,
      MessageFormat.format(
        "Ambiguous setters defined for property ''{0}'' in class ''{1}'' with types ''{2}'' and ''{3}''.",
        property, setter2.getDeclaringClass().getName(), paramType1.getName(), paramType2.getName()));
    setMethods.put(property, invoker);
    Type[] paramTypes = TypeParameterResolver.resolveParamTypes(setter1, type);
    setTypes.put(property, typeToClass(paramTypes[0]));
    return null;
  }
           

addFields

遞歸添加類及其父類中定義的字段,關于字段和屬性的差別可以參考這篇文章

private void addFields(Class<?> clazz) {
  //擷取clazz聲明的所有字段
  Field[] fields = clazz.getDeclaredFields();
  for (Field field : fields) {
    if (!setMethods.containsKey(field.getName())) {
      int modifiers = field.getModifiers();//擷取字段的修飾符
      //不是final,也不是static的将會被添加到setMethods和setTypes字段中
      if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
        addSetField(field);
      }
    }
    //添加到getMethods和getTypes字段中
    if (!getMethods.containsKey(field.getName())) {
      addGetField(field);
    }
  }
  if (clazz.getSuperclass() != null) {
    addFields(clazz.getSuperclass());
  }
}
           

readablePropertyNames和writablePropertyNames屬性的初始化

readablePropertyNames數組中儲存着所有可讀屬性和字段的名稱,即定義了get方法的字段名

writablePropertyNames數組中儲存着所有可寫屬性和字段的名稱,即定義了set方法的字段名

ReflectorFactory接口

通過上面的介紹可以知道,在mybatis中,一個類就會對應一個Reflector對象。假設有100個類,就需要有100個對應的Reflector對象,如何管理這些Reflector對象是需要考慮的問題,于是mybatis的作者設計了ReflectorFactory接口并對其進行了預設實作,便于上層代碼使用。

ReflectorFactory為Reflector的工廠接口,用于建立和緩存 Reflector 對象,其接口定義如下:

public interface ReflectorFactory {
 	//判斷是否允許對reflector進行緩存
	boolean isClassCacheEnabled();
	//設定是否允許對reflector進行緩存
  	void setClassCacheEnabled(boolean classCacheEnabled);
	//擷取type對應的Reflector對象
  	Reflector findForClass(Class<?> type);
}
           

DefaultReflectorFactory

ReflectorFactory接口隻有一個實作類,即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;
  }
  //如果允許緩存則直接在類中定義的ConcurrentHashMap中擷取
  //如果不允許緩存則每次都會建立新的Reflector對象
  @Override
  public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
      //如果reflectorMap中沒有對應的Reflector對象,則會建立一個對象并将其放在reflectorMap中
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
      return new Reflector(type);
    }
  }
}
           

繼續閱讀