myBatis源碼解析-反射篇(4)

前沿編程

前文分析了mybatis的日誌包,緩存包,數據源包。源碼實在有點難頂,在分析反射包時,花費了較多時間。廢話很少說,開始源碼之路。api

反射包feflection在mybatis路徑以下:緩存

 

 

 源碼解析數據結構

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

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相同屬性名的屬性進行值複製,是一個屬性工具類。app

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解析屬性集合,此處使用迭代器模式this

  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的功能架構清晰明瞭,給了我之後編程的靈感。若是解釋有誤的還請歡迎評論。任重而道遠,若是以爲不錯,還請看官點個小讚了。

相關文章
相關標籤/搜索