一、序言
Mybatis作為ORM,實現了對象與關系數據庫間的映射。Mybatis中的映射包含兩個方面:
1.將對象中的值(parameterType所指定的對象)映射到具體的sql中,例如:
<insert id="insertAuthor" parameterType="domain.blog.Author"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert>
2.將查詢出來的結果填充到具體的對象屬性中(由resultMap/resultType指定),例如:
<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
在使用mybatis時這些傳值的對象基本上都是POJO,傳入的時候(從對象到sql)就是讀對象的屬性(調用對象的get/is方法),傳出的時候(從sql到對象)就是set對象的屬性(調用對象的set方法)。這兩種的實現主要是基於Java 的反射機制進行的,只是Mybatis為了更好的滿足自己的需要,結合自己的特點進行了二次封裝,本文將介紹mybatis的reflection包。為方便介紹,我們默認下面提到的對象都是POJO類型的。
二、各包介紹
從上面的截圖可以看到reflection包含了幾個子包和一些一級類,我們在介紹時就不按照包的順序進行介紹,而是按照相互間依賴關系進行介紹,首先介紹被依賴的包和類,而后介紹依賴其他包的包和類,這樣介紹的好處是比較容易理解,不需要進行“請見下文”。
2.1 property包
在序言中提到,mybatis中的映射主要就是操作pojo的屬性,我們首先來了解下reflection中的property子包的內容。
2.1.1 PropertyCopier類
顧名思義,這個類就是就是將一個對象的屬性值賦給另一個對象中對應的屬性。
public static void copyBeanProperties(Class<?> type, Object sourceObject,Object distinationObject){ Class<?> parentClass = type; while(parentClass != null){ try { Field[] fields = type.getDeclaredFields(); for (Field field : fields) { //因為getDeclaredFields函數返回的這個類中各種限定符的屬性,如果不設置accessible為true,在調用限定符是private的屬性時會報錯 field.setAccessible(true); field.set(distinationObject, field.get(sourceObject)); } } catch (Exception e) { // 如果發生異常,mybatis中的做法是不做任何的處理,具體的說明如下。但是為了調試用,自己添加的異常打印語句 // Nothing useful to do, will only fail on final fields, which will be ignored. System.out.println("PropertyCopier's copyBeanProperties is executed! the exception is "+e); } // 本類執行完成后,查看父類 parentClass = parentClass.getSuperclass(); } }
2.1.2 PropertyNamer類
這個類提供了幾個用來判斷屬性特征和從方面名稱中獲取屬性名稱的函數,我們首先來看判斷一個方法名稱是否是操作的一個屬性的方法,如注釋中所講的返回true並一定就是一個屬性。
/** * 根據傳入的參數判斷這個參數是不是應包含屬性 * 判斷的依據是這個參數是不是以get|set|is開頭的。但這個函數的判斷依據是比較簡單的,這一個必然條件。 * 也就是說如果這個函數返回false,則這個參數肯定部包含屬性;反之,如果這個函數返回true,則只能說明這個參數可能包含屬性 *2013-9-7 下午12:37:02 by 孫振超 *@param name *@return *boolean */ public static boolean isProperty(String name) { return name.startsWith("get") || name.startsWith("set")|| name.startsWith("is"); }
然后我們再看從方面名稱中獲取屬性名稱的函數:
public static String methodToProperty(String name) { //根據java常用語法規則將一個函數轉化為屬性,如果參數不符合java常用語法規則將會拋出ReflectionException if (name.startsWith("get") || name.startsWith("set")) { name = name.substring(3); }else if (name.startsWith("is")) { name = name.substring(2); }else{ throw new ReflectionException("paramter "+name+" cannot convert to a property, because it is not obey to the java base rule;"); } //對於這個判斷為什么這么寫,沒有徹底弄明白。也許是對於字符串長度大於1且全為大寫的數據不做處理吧 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; }
對於這個類中包含的其他兩個函數比較簡單,就不在這里羅列了,有興趣的讀者可以查看mybatis的源代碼。
2.1.3 PropertyTokenizer類
這個類是property包中的重量級類,該類會被reflection包中其他的類頻繁的引用到。這個類實現了Iterable和Iterator這兩個接口,但在使用時經常被用到的是Iterator接口中的hasNext這個函數。我們着重了解這個類的屬性和構造函數:
//包含四個屬性,比較簡單 private String name; private String index; private String indexedName; private String children; public PropertyTokenizer(String propertyName) { // 對參數進行第一次處理,通過“.”分隔符將propertyName分作兩部分 int delimiter = propertyName.indexOf("."); if (delimiter > -1) { name = propertyName.substring(0, delimiter); children = propertyName.substring(delimiter + 1); } else { name = propertyName; children = null; } indexedName = name; // 對name進行二次處理,去除“[...]”,並將方括號內的內容賦給index屬性,如果name屬性中包含“[]”的話 delimiter = propertyName.indexOf("["); if (delimiter > -1) { // 先取index內容再截取name更為方便些,要不然還需要一個臨時變量,需要三步才能實現 // 這里包含了一個前提:傳入的參數如果有有[,則必然存在],並且是屬性的最后一個字符 index = name.substring(delimiter + 1, name.length() - 1); name = name.substring(0, delimiter); } }
經常使用的hasNext函數實現比較簡單,就是判斷children屬性是不是為空:
public boolean hasNext() { // TODO Auto-generated method stub return children != null; }
2.2 Invoker包
這個包中對Java的反射調用進行了二次封裝,定義了一個Invoker接口和三個具體實現。我們首先來看Invoker接口:
2.2.1 Invoker接口
public interface Invoker { Object invoke(Object targetObject, Object[] args) throws InvocationTargetException,IllegalAccessException; Class<?> getType(); }
這個接口定義了兩個函數,invoke用來執行具體的調用,getType用來返回調用對象的具體的類型。
2.2.2 SetFieldInvoker和GetFieldInvoker類
這兩個類都實現了Invoker接口,都有一個類型為java.lang.reflect.Field的屬性,這個屬性在初始化時進行設置。
public GetFieldInvoker(Field field){ this.field = field; } public SetFieldInvoker(Field field) { this.field = field; }
getType函數返回的是Field的類型:
public Class<?> getType() { // TODO Auto-generated method stub return field.getType(); }
這兩個類最大的不同在於對invoke函數的實現上,一個是調用fieldd的set方法,一個是調用Field的get方法。
public Object invoke(Object targetObject, Object[] args) throws InvocationTargetException, IllegalAccessException { field.set(targetObject, args[0]); return null; } public Object invoke(Object targetObject, Object[] args) throws InvocationTargetException, IllegalAccessException { return field.get(targetObject); }
2.2.3 MethodInvoker類
這個類相對前面兩個類要復雜些,主要復雜的地方在於type的確定,這個type的確定是在構造函數中進行的,我們來看下具體的代碼:
//包含的兩個屬性 private Method method;//基礎屬性,必備 private Class<?> type; public MethodInvoker(Method method) { this.method = method; //method的類型不像Field的類型那樣,如果這個method有參數,就取第一個參數的類型;如果沒有參數就取這個method的返回值 if (method.getParameterTypes().length >= 1) { type = method.getParameterTypes()[0]; }else { type = method.getReturnType(); } }
2.3 factory包
該包中包含的內容比較少,一個接口,一個實現類。
2.3.1 ObjectFactory接口
POJO類在創建時通常也就兩類操作:1)初始化:分帶參數和不帶參數兩種 2)屬性賦值。因而ObjectFactory接口也包含了這樣的函數,同時考慮到mybatis配置時的特點,添加了一個額外的函數,具體如下:
public interface ObjectFactory { void setProperties(Properties properties); //利用默認構造函數創建對象 <T> T createObject(Class<T> type); //利用帶有參數的構造函數創建對象 <T> T createObject(Class<T> type,List<Class<?>> constructorArgTypes, List<Object> constructorArgs); <T> boolean isCollection(Class<T> type); }
2.3.2 DefaultObjectFactory接口
上面我們提到初始化對象時可以調用默認構造函數和帶有參數的構造函數,DefaultObjectFactory在實現時直接進行了二次包裝,將兩個函數的實現合二為一。
public <T> T createObject(Class<T> type) { return createObject(type,null,null); }
在面向對象的開發中我們會提倡面對接口而不是面向具體實現的編程原則,但是在創建對象時則必須指定一個具體的類,為了解決這個問題,mybatis對常用的集合超類指定了具體的實現類:
protected Class<?> resolveInterface(Class<?> type) { Class<?> classToCreate = type; //List if (type == List.class ||type==Iterable.class || type==Collection.class) { classToCreate = ArrayList.class; }else if (type == Map.class) {//Map classToCreate = HashMap.class; }else if (type == SortedSet.class) { classToCreate = SortedSet.class; }else if (type == Set.class) { classToCreate = Set.class; } return classToCreate; }
准備工作完成了,下面我們來了解下具體的創建過程,雖然有些復雜,但是對於了解java的反射機制和類安全會有幫助:
try { //如果參數值或者參數類型為空,則采用默認構造函數進行創建 if (constructorArgTypes == null || constructorArgs== null) { Constructor<T> constructor = type.getDeclaredConstructor(); if (!constructor.isAccessible()) { constructor.setAccessible(true);//必須設置為true,否則在下面的調用時會拋出IllegalAccessException } return constructor.newInstance(); } //對傳入的參數個數進行校驗,這個是我自己加的,源代碼中沒有 if(constructorArgs.size() != constructorArgTypes.size()){ throw new ReflectionException("the size of parameters is not equal to the propeties! constructorArgTypes:" +constructorArgTypes.size()+" ; constructorArgs:"+constructorArgs.size()); } //如果參數值或者參數類型不為空,則采用帶有參數的構造函數 Constructor<T> constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()])); if (!constructor.isAccessible()) { constructor.setAccessible(true); } return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()])); } catch (Exception e) {
}