背景: 最近在看Spring Mvc
的源碼, 看到調(diào)用請(qǐng)求處理方法的過(guò)程中時(shí)看到最后調(diào)用的請(qǐng)求方法時(shí)拿取的是bridgeMethod
, 如下
// InvocableHandlerMethod#doInvoke(只保留關(guān)鍵代碼)
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
// 這里獲取請(qǐng)求處理方法(也就是我們?cè)赾ontroller中進(jìn)行對(duì)請(qǐng)求進(jìn)行處理的方法)
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
...
異常處理代碼
...
}
}
可以看到spring
是從HandlerMethod
(請(qǐng)求處理方法類(lèi), 每一個(gè)請(qǐng)求處理方法就是這個(gè)類(lèi)的實(shí)例)中調(diào)用getBridgedMethod
拿到真正的請(qǐng)求處理方法而不是通過(guò)getMethod
方法獲取, 那getBridgedMethod
返回的是什么呢? 看一下HandlerMethod
中getBridgedMethod
的具體實(shí)現(xiàn)
/**
* If the bean method is a bridge method, this method returns the bridged
* (user-defined) method. Otherwise it returns the same method as {@link #getMethod()}.
*/
protected Method getBridgedMethod() {
return this.bridgedMethod;
}
關(guān)鍵的地方在注釋中說(shuō)明了: 如果請(qǐng)求處理的方法是一個(gè)bridge method
那么就返回bridge method
, 否則返回值就和getMethod
一樣. 那么問(wèn)題就來(lái)了, 什么是bridge method?
1. bridge method
什么是bridge method
, 讓我們來(lái)看一下JSL
中對(duì)bridge method
的定義
也就是說(shuō)如果一個(gè)類(lèi)繼承了一個(gè)范型類(lèi)或者實(shí)現(xiàn)了一個(gè)范型接口, 那么編譯器在編譯這個(gè)類(lèi)的時(shí)候就會(huì)生成一個(gè)叫做橋接方法的混合方法(混合方法簡(jiǎn)單的說(shuō)就是由編譯器生成的方法, 方法上有synthetic
修飾符), 這個(gè)方法用于范型的類(lèi)型安全處理, 用戶(hù)一般不需要關(guān)心橋接方法. 更詳細(xì)的可以看JSL bridge method
2. 更直觀的了解bridge method
// 定義一個(gè)范型接口
public interface Parent<T> {
T bridgeMethod(T param);
}
// 定義一個(gè)類(lèi)實(shí)現(xiàn)范型接口
public class Child implements Parent<String> {
public String bridgeMethod(String param) {
return param;
}
}
// 測(cè)試方法
public class BridgeMethodTest {
public static void main(String[] args) throws Exception {
// 使用java的多態(tài)
Parent parent = new Child();
System.out.println(parent.bridgeMethod("abc123"));// 調(diào)用的是實(shí)際的方法
Class<? extends Parent> clz = parent.getClass();
Method method = clz.getMethod("bridgeMethod", Object.class); // 獲取橋接方法
System.out.println(method.isBridge()); // true
System.out.println(method.invoke(parent, "hello")); // 調(diào)用的是橋接方法
System.out.println(parent.bridgeMethod(new Object()));// 調(diào)用的是橋接方法, 會(huì)報(bào)ClassCastException: java.lang.Object cannot be cast to java.lang.String`錯(cuò)誤`
}
}
使用jclasslib
工具看一下編譯器為我們生成了什么了
這個(gè)是Parent.class
的字節(jié)碼, 可以看到范型T
被換成了Object
, 所以方法的簽名是
public abstract Object bridgeMethod(Object param)
這個(gè)是Child.class
的字節(jié)碼, 可以看到生成了一個(gè)bridgeMethod
的方法, 這個(gè)方法的簽名是
public String bridgeMethod(String param)
這個(gè)是Child.class
的字節(jié)碼, 可以看到還生成了一個(gè)bridgeMethod
的橋接方法, 這個(gè)方法的簽名是
public synthetic bridge Object bridgeMethod(Object param)
注: 也可以使用javap -c -v classFileName.class
打印類(lèi)似的信息, 但是使用jclasslib更加直觀方便
3. 編譯器生成bridge method
的意義
簡(jiǎn)單來(lái)說(shuō), 編譯器生成bridge method
的目的就是為了和jdk1.5
之前的字節(jié)碼兼容. 因?yàn)榉缎褪窃?code>jdk1.5之后才引入的. 在jdk1.5
之前例如集合的操作都是沒(méi)有范型支持的, 所以生成的字節(jié)碼中參數(shù)都是用Object
接收的, 所以也可以往集合中放入任意類(lèi)型的對(duì)象, 集合類(lèi)型的校驗(yàn)也被拖到運(yùn)行期.
但是在jdk1.5
之后引入了范型, 因此集合的內(nèi)容校驗(yàn)被提前到了編譯期, 但是為了兼容jdk1.5
之前的版本java
使用了范型擦除, 所以如果不生成橋接方法就和jdk1.5
之前的字節(jié)碼不兼容了.
上面可以看到在Parent.class
中, 由于范型擦除, class
文件中范型都是由Object
替代了. 所以如果子類(lèi)中要是不生成bridge method
那么子類(lèi)就沒(méi)有實(shí)現(xiàn)接口中的方法, 這個(gè)java
語(yǔ)義就不對(duì)了(雖然已經(jīng)生成class
文件了, 不會(huì)有編譯錯(cuò)誤)
4. 總結(jié)
因?yàn)?code>java要兼容之前的版本, 因此在一些步驟上要做一些trick操作, 但是這些trick操作又不能對(duì)用戶(hù)的使用產(chǎn)生困擾, 也就是說(shuō)這些操作對(duì)用戶(hù)應(yīng)該是透明的. 不得不說(shuō)java
真的很強(qiáng)大, 我們平時(shí)用的只不過(guò)是java
的冰山一角, 如果要更深入的了解java
語(yǔ)言本身還有很多有意思的功能或者知識(shí)點(diǎn)需要了解.