反射就是在運行的狀態中, 對於任意的一個實體類, 都能知道這個類的所有屬性和方法。 並將其封裝成一個個對象, 對通過這些對象來實現對應實體類的創建, 以及訪問該類的方法和屬性。
在我們創建了一個Java類之後, 編譯出的.class文件在虛擬機中加載, 都會在JVM中創建一個Class對象,通過該對象來創建這個類的所有對象。
在 Mybatis 中, 有對應的反射模塊, 本文就是探究 mybatis 是如何進行反射的。 mybatis 中的反射主要與JavaBean相關。
1 JavaBean 規範
JavaBean 具有如下特徵:
- 所有的屬性都是私有的(通過 getter和setter 訪問)
- 擁有公有的無參數構造函數
- 提供 setter/getter
- 實現 Serializable 接口
2 Reflector和ReflectorFactory
mybatis 這種框架, 出於性能等方面的考慮, 必然不是等到使用的時候再去解析XML/再去解析反射類。
mybatis 爲每一個類提供了反射器類(Reflector
), 該類中存儲了反射需要使用的類的元信息。
2.1 Reflector 屬性
2.1.1 屬性
從類的屬性中, 我們可以看出:
- 一個反射器(
Reflector
)對應着一個Class
對象。 - 記錄了默認構造函數
- 其餘的是屬性及其setter|getter相關
對於一個屬性(沒錯, 屬性, 只有有 setter|getter 才能被稱之爲屬性)
- 如果是可讀的(有getter方法)則
Reflector
會將其及其方法處理後放入對應的集合中; - 如果是可寫的(有setter方法), 則
Reflector
會將其及其方法處理後放入對應的可寫相關的集合中。 - 最後使用 Map<String, String> caseInsensitivePropertyMap 來記錄所有的屬性。
2.1.2 Invoker 接口
在存儲方法的時候, Reflector
使用的是 Map<String, Invoker>
。 而不是 Map<String, Method>
。
該接口的定義也很簡單
/**
* Invoker: 與方法的 invoke 相關
* @author Clinton Begin
*/
public interface Invoker {
// 調用方法
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
// 獲取類型
Class<?> getType();
}
定義了方法的調用和獲取類型。
- MethodInvoker: 方法的Invoker
- GetFieldInvoker: 如果沒有setter, 則使用該方法, 通過Filed類直接設置成員變量的值
- SetFieldInvoker: 如果沒有getter, 則使用該方法, 通過Field類直接讀取成員變量的值
通過該封裝之後, 本來需要聲明 Map<String, Method>
和 Map<String, Field>
表示的, 只需要使用 Map<String, Invoker>
即可表示。
2.2 Reflector 對外提供的方法
Reflector 對外提供的方法主要與構造函數和屬性相關。
構造函數:根據 Class
對象,設置 Reflector
相應的成員變量。
public Reflector(Class<?> clazz)
檢查是否擁有了訪問的權限:除了訪問公有的變量, 還能訪問 default , protected 和p rivate 變量
public static boolean canControlMemberAccessible()
查找是否有相應的屬性
public String findPropertyName(String name)
獲取默認的構造函數:說實話, 不清楚爲啥要有這個方法, 不是可以通過 Class.newInstance() 進行創建嗎?
public Constructor<?> getDefaultConstructor()
getter相關的方法
public String[] getGetablePropertyNames() // 獲取所有的可讀屬性
public Invoker getGetInvoker(String propertyName)// 獲取所有可讀屬性的 Invoker
public Class<?> getGetterType(String propertyName)// 獲取對應屬性的類型
public boolean hasGetter(String propertyName)// 對應屬性是否有相應的getter
對應的也有 setter 相關的方法
public String[] getSetablePropertyNames() // 獲取所有的可讀屬性
public Invoker getSetInvoker(String propertyName)// 獲取所有可讀屬性的 Invoker
public Class<?> getSetterType(String propertyName)// 獲取對應屬性的類型
public boolean hasSetter(String propertyName)// 對應屬性是否有相應的 setter
2.3 Reflector 私有方法
2.3.1 方法相關
每個 Relector
對應緩存一個類的元反射信息, 通過 Map
進行緩存, 後續我們在使用時就不需要再去遍歷查找, 可通過鍵查找即可。
因此, 就涉及到幾個方法
獲取方法簽名: 根據函數名稱、參數和返回值類型來取得簽名, 保證方法的唯一性
private String getSignature(Method method)
該方法獲取每個方法的簽名。 獲取得到的簽名
返回值類型#方法名:參數1,參數2,參數3…
很顯然, 簽名的目的是唯一性。 那使用語言本身的特性來保證唯一性是最好的:
- 方法名不一致, 則方法就不一致
- 返回值不一致或者不是其子類, 則方法不一致
- 參數數量, 參數類型順序不一致方法也會不一樣
因此, 以上的簽名方式可以保證方法的唯一性。
獲取類的所有方法
private Method[] getClassMethods(Class<?> cls)
注意, 由於在獲取方法時, 通過調用當前類及其除 Object
之外的所有父類的 getDeclaredMethods 方法及 getInterfaces() 方法, 因此, 其獲取到的方法是該類及其父類的所有方法。
由此, 產生了一個問題, 如果子類重寫了父類中的方法, 如果返回值相同, 則可以通過鍵重複來去掉。 但是, 如果方法返回值是父類相同實體方法返回值類型的子類, 則就會導致兩個方法是同一個方法, 但是簽名不同。 因此, 需要解決此類衝突。
解決方法衝突:getter方法衝突解決
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters)
到了此步驟的時候, 屬性已經以 propName->List<Method> 的形式存在於內存中。 此時, 需要Map<String, List>
轉換爲Map<String, Invoker>
。
該方法只需要明白下面兩個條件就能很清晰了:
- 返回值類型不同
返回值類型不同, 則哪個方法的返回值是另一個方法返回值類型子類, 就把 propName 指向該方法包裝成的 Invoker
。 這個很好理解, 畢竟重新(override)重寫時, 重寫方法的返回值類型可以是被重寫方法的子類。
- 返回值類型相同
按理來說不會出現這種情況, 因爲在獲取方法的時候已經使用簽名去除掉了, 因此此時可以拋出異常。 但是有一種特殊的情況(這個卡了我一段時間):
public boolean isBool() {return true;}// 方法1
public boolean getBool() {return false;}// 方法2
以上情況在 JavaBean 規範中是允許的(但是, 其實方法2幾乎大家都不會這麼用)。 因此, mybatis 通過以下的方式進行了過濾。
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
也就是說, mybatis 承認的是方法1這種。 方法2的忽略掉。
解決方法衝突:setter方法衝突解決
剛開始, 我也想不明白, 爲什麼setter也會出現衝突。畢竟沒有返回值類型, 也沒有上面的 boolean 特殊情況。 最後發現了, 還有一個情況, 泛型!!
void setId(T id);// 父類
public void setId(String id) {// 子類
// Do nothing
}
顯然, 遇到此類情況, 顯然, 子類中的方法纔是我們想要的:
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
參數中, 父類方法泛型經過類型擦除後, 變成了 Object
。 因此, 通過以上的方法, 那個是子類, 我們就獲取哪一個。
3 ReflectorFactory
看名稱, 工廠方法, 是爲了創建和緩存 Reflector
的。
只有三個方法: 是否緩存, 設置要不要緩存, 根據類型查找 Reflector
對象(找不到則創建)。
其與 Reflector
的關係
mybatis 爲我們提供了該方法的默認實現 DefaultReflectorFactory
。 該類的實現很簡單, 就是通過ConcurrentMap<Class<?>, Reflector>
對 Reflector
進行緩存。
4 MetaClass
MetaClass
通過與屬性工具類的結合, 實現了對複雜表達式的解析,實現了獲取指定描述信息的功能。
4.1 成員變量
MetaClass
有兩個成員變量, 分別是 ReflectorFactory
和 Reflector
。
4.2 創建
MetaClass
的構造函數是私有的。
/**
* MetaClass 構造函數
*/
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
但是, 其提供了兩個創建的方法。 這兩個方法也是通過該方法進行創建對象的。 該方法通過 Class
對象進行了 Reflector
對象的創建, 並賦值給成員變量。
/**
* 跟上面的是一樣的
*/
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
通過屬性進行創建
/**
* 通過屬性名稱, 獲取屬性的 MetaClass
*/
public MetaClass metaClassForProperty(String name) {
Class<?> propType = reflector.getGetterType(name);
return MetaClass.forClass(propType, reflectorFactory);
}
4.3 方法
該類中, 最重要的方法是:
/**
* 解析屬性表達式
* 會去尋找reflector中是否有對應的的屬性
* @param name
* @param builder
* @return
*/
private StringBuilder buildProperty(String name, StringBuilder builder) {
// 解析屬性表達式
PropertyTokenizer prop = new PropertyTokenizer(name);
// 是否有子表達式
if (prop.hasNext()) {
// 查找對應的屬性
String propertyName = reflector.findPropertyName(prop.getName());
if (propertyName != null) {
// 追加屬性名
builder.append(propertyName);
builder.append(".");
// 創建對應的 MetaClass 對象
MetaClass metaProp = metaClassForProperty(propertyName);
// 解析子表達式, 遞歸
metaProp.buildProperty(prop.getChildren(), builder);
}
} else {
// 根據名稱查找屬性
String propertyName = reflector.findPropertyName(name);
if (propertyName != null) {
builder.append(propertyName);
}
}
return builder;
}
理解了這個方法(遞歸, 該類中有很多類似的), 就可以很好的對這個類進行理解, 以查找(richType.richProperty)爲例:
- 通過 PropertyTokenizer 對表達式進行解析, 得到當前的 name=richType, children=richProperty
- 從 reflector 中查找該 richType 屬性
- 將 richType 添加到 builder 中
- 使用 metaClassForProperty 創建 richType 的
MetaClass
。 - 遞歸調用自身來處理子表達式
退出的條件就是沒有子表達式。 這個就是爲了, 我們類中有成員變量是類, 我們可以通過其找到他們的所有類及其屬性。
注意, 在此過程中, ReflectorFactory
一直是同一個, 而其內部緩存了多個 Reflector
對象。
5 總結
類的關係:
Reflector
實現了實體類元信息的封裝, 但對類中的成員變量是類的情況沒有進行處理。 而 MetaClass
通過 ReflectorFactory
類型的成員變量, 實現了實體類中成員變量是類情況的處理。從而結合屬性工具類實現了對複雜表達式的處理。
一起學 mybatis
你想不想來學習 mybatis? 學習其使用和源碼呢?那麼, 在博客園關注我吧!!
我自己打算把這個源碼系列更新完畢, 同時會更新相應的註釋。快去我的github star 吧!!