myBatis原始碼解析-反射篇(4)

超人小冰發表於2020-08-11

前沿

前文分析了mybatis的日誌包,快取包,資料來源包。原始碼實在有點難頂,在分析反射包時,花費了較多時間。廢話不多說,開始原始碼之路。

反射包feflection在mybatis路徑如下:

 

 

 原始碼解析

1  property包-主要對類的屬性進行操作的工具包

1.1 PropertyCopier包利用反射類Filed進行屬性複製

// 該類作用將sourceBean與destinationBean相同屬性名的屬性進行值複製
public class PropertyCopier {
  // 屬性複製
  public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
    Class<?> parent = type;
    while (parent != null) {
      final Field[] fields = parent.getDeclaredFields(); // 獲取該類的所有屬性
      for(Field field : fields) {
        try {
          field.setAccessible(true); // 設定該屬性的訪問許可權(包括私有屬性)
          field.set(destinationBean, field.get(sourceBean)); // 此處呼叫2個方法,filed.get(objectA)  獲取objectA中的filed屬性值. filed.set(objectB,value) 將value值賦值給ObjectB的filed屬性
        } catch (Exception e) { // 異常直接忽略掉(對於非公共屬性直接忽略)
          // Nothing useful to do, will only fail on final fields, which will be ignored.
        }
      }
      parent = parent.getSuperclass(); // 獲取父類,迴圈複製父類屬性
    }
  }

}

該類主要功能是將sourceBean與destinationBean相同屬性名的屬性進行值複製,是一個屬性工具類。

1.2 PropertyNamer根據方法名獲取屬性名稱

public class PropertyNamer {
  // 獲取getxxx,isxxx,setxxx後的xxx屬性
  public static String methodToProperty(String name) {
    if (name.startsWith("is")) {
      name = name.substring(2);
    } else if (name.startsWith("get") || name.startsWith("set")) {
      name = name.substring(3);
    } else {
      throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
    }
    // 此處我們預設使用駝峰命名,如setName,那獲取的屬性名應為name而不是Name
    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
    }

    return name;
  }

該類主要作用是從set,is,get方法中獲取屬性,是一個屬性工具類。

1.3 PropertyTokenizer解析屬性集合,此處使用迭代器模式

  public PropertyTokenizer(String fullname) {
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      name = fullname.substring(0, delim); 
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }

此方法比較簡單,舉個例子。如我們要解析group[0].user[0].name這一串字元,那經過一次迭代獲取如下結構
children = user[0].name
indexedName = group[0]
index = 0
name = group
使用迭代器方法hasNext()判斷children是否為null,若不為null,則繼續解析。此方法比較重要,在後文中對關於複雜屬性的解析,都使用了此類,需要重要理解。

 2. Invoker包分析 - 主要對反射類Filed,Method方法進行封裝

 2.1 執行器介面(將設定屬性,獲取屬性,方法執行全都用Invoker進行封裝,充分體現了面向介面程式設計)

 public interface Invoker {
  // 執行方法
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;

  Class<?> getType();
}

提供對外通用介面,具體執行器需實現此介面。

2.2 獲取物件屬性的執行器

 public class GetFieldInvoker implements Invoker {
  
  public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
    return field.get(target); // 呼叫反射類Filed.get()方法,獲取物件屬性
  }

}

GetFieldInvoker內部封裝了Filed.get()方法獲取物件屬性。

2.3 設定物件屬性執行器

public class SetFieldInvoker implements Invoker {

  public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
    field.set(target, args[0]); // 呼叫Filed.set()方法,設定物件屬性
    return null;
  }
}

SetFieldInvoker內部封裝了Filed.set()方法設定物件屬性。

2.4 物件方法執行器

public class MethodInvoker implements Invoker {

  private Class<?> type;
  private Method method;

  public MethodInvoker(Method method) {
    this.method = method;

    if (method.getParameterTypes().length == 1) {
      type = method.getParameterTypes()[0]; // 獲得方法引數列表中的第一個引數型別
    } else {
      type = method.getReturnType();  // 否則獲取方法的返回型別
    }
  }

  public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
    return method.invoke(target, args); // 執行target的method方法
  }

  public Class<?> getType() {
    return type;
  }
}

MethodInvoker內部封裝了method.invoke來執行方法。

3  reflection包-此包中的類基本是增強類,提供對外開放的API

3.1 Reflector類-class類的增強類

public class Reflector {  // 反射器,class的增強類

  private static boolean classCacheEnabled = true;
  private static final String[] EMPTY_STRING_ARRAY = new String[0];
  // 相當於快取工廠,此處使用REFLECTOR_MAP目的是個人理解是因為Reflect的API很耗資源,所以用REFLECTOR_MAP將要反射的類及增強類放置在一起,以後使用時可以直接取不需要重複新建class的增強類了
  private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Reflector>();

  private Class<?> type; // 該類的類資訊
  private String[] readablePropertyNames = EMPTY_STRING_ARRAY; // 可讀屬性
  private String[] writeablePropertyNames = EMPTY_STRING_ARRAY; // 可寫屬性
  private Map<String, Invoker> setMethods = new HashMap<String, Invoker>(); // set方法
  private Map<String, Invoker> getMethods = new HashMap<String, Invoker>(); // get方法
  private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>(); // setxxx中xxx型別
  private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>(); // getxxx中的xxx型別
  private Constructor<?> defaultConstructor; // 該類的預設建構函式

  private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

  ......
}

檢視Reflector的基本屬性,主要對一個類按照反射包括的資料結構(方法,屬性,構造方法)進行解析。如User類中有age,name屬性,且有get,set方法,那木在初始化時會將這些屬性和屬性,方法等解析出來。注意此類有一個靜態列表,用於存放已解析好的類。用於當做快取使用。檢視構造方法驗證。

private Reflector(Class<?> clazz) {  // 建構函式初始化後設資料資訊
    type = clazz;
    addDefaultConstructor(clazz); // 新增建構函式
    addGetMethods(clazz); // 新增類的get方法
    addSetMethods(clazz); // 新增類的set方法
    addFields(clazz); // 新增屬性
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); // 獲取getxxx中xxx集合
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); // 獲取setxxx中xxx集合
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

分析較簡單的新增建構函式方法,後面的新增get,set方法較為複雜,限於篇幅,就略過了。

private void addDefaultConstructor(Class<?> clazz) { // 新增反射類後設資料的預設建構函式
    Constructor<?>[] consts = clazz.getDeclaredConstructors(); // 獲取所有建構函式
    for (Constructor<?> constructor : consts) {
      if (constructor.getParameterTypes().length == 0) { // 找到無參建構函式,即預設建構函式
        if (canAccessPrivateMethods()) { // 若是private,則變為可寫
          try {
            constructor.setAccessible(true);
          } catch (Exception e) {
            // Ignored. This is only a final precaution, nothing we can do.
          }
        }
        if (constructor.isAccessible()) {
          this.defaultConstructor = constructor; // 設定預設建構函式
        }
      }
    }
  }

3.2 MetaClass類-Reflector類的增強類

該類主要對於複雜的語句進行拆解。如果需分析一個xxx欄位在某個類中是否存在set方法,
對於一般的如age單屬性,可以直接呼叫Reflector.hasSetter("age")來判斷。但如果對於group.user.age這個多屬性,需分析group中是否有user的set方法,如果有,則繼續分析在user物件中是否存在age的set方法。該實現主要是基於上文分析的PropertyTokenizer類與Reflector類。下文分析MetaClass中重寫的hasSetter方法來驗證。

 // 判斷name是否在物件中存在set方法
  public boolean hasSetter(String name) {  
    PropertyTokenizer prop = new PropertyTokenizer(name); // 對複雜語句進行拆解
    if (prop.hasNext()) { // 若是複雜語句
      if (reflector.hasSetter(prop.getName())) {  // 第一層解析的物件屬性有get方法
        MetaClass metaProp = metaClassForProperty(prop.getName());  // 獲取getxxx中xxx的型別,構建成一個MetaClass物件,方便遞迴
        return metaProp.hasSetter(prop.getChildren()); // 遞迴操作
      } else { // 如有一層沒有get方法,就直接返回false
        return false;
      }
    } else {
      return reflector.hasSetter(prop.getName()); // 簡單語句直接呼叫reflector的方法
    }
  }

3.3 MetaObject類-對外提供的類

MetaObject類裡面存放了真正的物件。前文所分析的都是些靜態物件,沒有真正涉及到例項物件。分析MetaObject對外介面,其實都是內部呼叫了ObjectWrapper的方法。分析ObjectWrapper很簡單,此處結合一個簡單demo,來理解MetaObject的作用。

class User{
    private String name;
    private String age;
    // ..... 省略getName,setName,getAge,setAge方法
}

@Test
  public void shouldGetAndSetField() {
    User user = new User();
    MetaObject meta = SystemMetaObject.forObject(user);  // 利用例項物件構建一個MetaObject物件
    meta.setValue("name", "xiabing");  // 呼叫metaObject.setValue方法,實際呼叫的是objectWrapper方法
    assertEquals("xiabing", meta.getValue("name")); // 比較,獲取name的屬性
  }

MetaObject是對外提供api的類。要了解具體的實現,還需繼續分析下文的wrapper包。

4. wrapper包-物件裝飾包

以下為個人理解:使用wrapper包來封裝物件,對外開放統一的set,get方法。比如我們使用一個User物件,要設定名稱則需呼叫user.setName("haha")方法,設定年齡需呼叫user.setAge("10")。這樣對於mybatis可能不太友好,於是使用了wrapper類。只需wrapper.set("name","haha"),wrapper.set("age","10")這樣統一的介面方式就能設定屬性了。看起來確實簡潔許多。

4.1 ObjectWrapper介面,提供基本對外開放的介面

public interface ObjectWrapper { //物件裝飾類

  Object get(PropertyTokenizer prop); // 獲得屬性

  void set(PropertyTokenizer prop, Object value); // 設定屬性

  String findProperty(String name, boolean useCamelCaseMapping); //查詢屬性

  String[] getGetterNames(); // 獲取getXXX中xxx的集合

  String[] getSetterNames();  // 獲取setXXX中的xxx的集合

  Class<?> getSetterType(String name); // 根據xxx獲取setxxx中xxx的型別

  Class<?> getGetterType(String name); // 根據xxx獲取getxxx中xxx的型別

  boolean hasSetter(String name); // 查詢是否存在setxxx方法

  boolean hasGetter(String name); // 查詢是否存在getxxx方法

  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory); // 例項化屬性的值
  
  boolean isCollection(); // 該物件是否是集合
  
  public void add(Object element);
  
  public <E> void addAll(List<E> element);

}

4.2 BaseWrapper抽象類-提供集合屬性的方法

此類教簡單,暫且忽略掉。

4.3 BeanWrapper - 真實執行方法的類

此類是反射的關鍵,是真正呼叫反射執行get,set,method方法的類。分析其基本屬性,注意繼承了BaseWrapper,而BaseWrapper中也有一個屬性是MetaObject,當時說了MetaObject真正呼叫方法的是BaseWrapper類,可見,兩個物件是一一對應關係。

public class BeanWrapper extends BaseWrapper { // bean的封裝類

  private Object object; // 真實物件
  private MetaClass metaClass; // 該物件的反射類的增強類

分析get屬性方法方法

  public Object get(PropertyTokenizer prop) { 
    if (prop.getIndex() != null) { // 分析PropertyTokenizer可知,若index不為null,則代表是集合物件,限於篇幅,小夥伴可自行分析
      Object collection = resolveCollection(prop, object);
      return getCollectionValue(prop, collection);
    } else {
      return getBeanProperty(prop, object); // 呼叫內部方法
    }
  }

   private Object getBeanProperty(PropertyTokenizer prop, Object object) {
    try {
      Invoker method = metaClass.getGetInvoker(prop.getName()); // 根據屬性拿到getFiledInvoker執行器
      try {
        return method.invoke(object, NO_ARGUMENTS); // 呼叫getFiledInvoker中的方法
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable t) {
      throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);
    }
  }

上面用到了Invoke的方法,Invoke分析見上文。可知面向介面程式設計的優越性,將getFiled,setFiled,method全都封裝成了invoke類。

分析set屬性方法

public void set(PropertyTokenizer prop, Object value) {
    if (prop.getIndex() != null) { // 分析PropertyTokenizer可知,若index不為null,則代表是集合物件,限
      Object collection = resolveCollection(prop, object);
      setCollectionValue(prop, collection, value);
    } else {
      setBeanProperty(prop, object, value); //呼叫內部方法
    }
  }
  
  private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
      Invoker method = metaClass.getSetInvoker(prop.getName()); // 拿到setFiledInvoker執行器
      Object[] params = {value}; // 獲取引數
      try {
        method.invoke(object, params); // 執行setFiledInvoker方法,實際呼叫Filed.set()方法設定屬性
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }

總結

由於以前對反射基礎不夠紮實,導致在分析反射包的時候,過程很不順利,不過還好堅持下來了。在過程中,發現mybatis的功能架構清晰明瞭,給了我以後程式設計的靈感。如果解釋有誤的還請歡迎評論。任重而道遠,如果覺得不錯,還請看官點個小讚了。

相關文章