前沿
前文分析了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的功能架構清晰明瞭,給了我以後程式設計的靈感。如果解釋有誤的還請歡迎評論。任重而道遠,如果覺得不錯,還請看官點個小讚了。