提要
spring在ioc容器中进行反射调用的时候,例如@Controller的路由过程,这部分源码中常常涉及一个Bridge method的概念, 那么这个
桥接方法到底是什么?有什么作用?为什么离不开这个bridge方法?什么时候生成这个方法。what,how,why,when
一. 预备知识
1.1 泛型是什么?
泛型的英文名为generics, 广泛,通用的意思。泛型就是 对代码中的类型进行参数化校验。
将类中的类型也进行参数化,类比方法和方法的入参,泛型便是一个类的入参。
1.2 泛型产生的原因?
在没泛型之前,假如有一个接口,参数类型在具体的实现中都不一样,在代码实现的调用过程中需要使用这个接口进行调用,那么这个类型就需要在运行时进行强转才能获取具体的参数对象。在jdk1.5以后,在编码的时候就可以通过泛型指定这个接口对应的参数类型是什么。
1.3 泛型擦除是什么?
泛型擦除指的就是,代码中指定了泛型的参数类型, 但是在实际运行过程中这个泛型指定的参数依然是一个Object。
例如List<String>、 Interf<Integer>
, 这样的泛型限定, 在运行期中限定的类型都会变成Object,而非String,Integer等具体类型, 也就是泛型的校验只存在编译期。运行期便没有了泛型的限定。
二. 泛型的类型
泛型的类型有三种
- 泛型类
- 泛型接口
- 泛型方法
2.1 泛型类与泛型接口
1
2
3
4
5
public class a<T>{
T t;
}
public interface inter<T>{}
2.2 泛型方法
1
2
3
4
public class generic{
public <T> void method(T t){}
public <T> T method1(T t){}
}
泛型方法与泛型类和接口不太一样, 类型参数<T>
写在返回值前面,限定这个方法为泛型方法,后面的方法入参中的T为参数化类型。
2.3 泛型类与泛型方法共存
1
2
3
4
5
6
public class Generic<T>{
public void method(T t){} //普通方法,类型化参数T由泛型类的类型参数进行限定。
public <T> void method1(T t){} //泛型方法,类型化参数T以自己的类型参数`<T>`定义为准。
}
上面的method是一个普通方法, 参数化类型T由泛型类中指定
上面的method1是一个泛型方法,参数化类型T有自己定义。
1
2
3
Generic<String> g = new Generic();
g.method("string"); //普通方法,泛型类指定参数类型
g.method1(100); //泛型方法,入参类型由自己指定,这里我们指定了一个int类型
上面的例子中泛型类中的限定参数是String, 而传递给泛型方法的是一个Integer类型, 两者不相干。
三. 泛型通配符
先看一段代码
1
2
3
4
5
class pub{}
class sub extent pub{}
List<sub> ls = new ArrayList<>();
List<pub> lp = ls;
上面的这段代码在编译期便会报错,虽然pub和sub有父子关系,但是在泛型中List<sub>
和 List<pub>
并没有父子关系,为了描述泛型中的父子关系,引入了泛型通配符,指定泛型上下限。
3.1 通配符类型
- <?> 表示无类型的通配符
- <? extends xxx> 表示有上限的通配符
- <? super xxx> 表示有下限的通配符
3.2 无类型通配符
? 表示无类型通配符, 使用无类型通配符限定类型的时候,泛型处理逻辑中一定是与具体类型无关的。
1
2
3
List<?> a = new ArrayList<>();
a.add("lll"); //这里编译器不会通过
a.size(); //这里与类型无关的操作就可以通过
3.3 上限通配符
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SuperClass<T extends String> {
T obj;
T method(T param){return null;};
public static void main(String[] args) throws NoSuchFieldException {
System.out.println(SuperClass.class.getDeclaredField("obj").getType());
// 输出class java.lang.String
for(Method m:SuperClass.class.getDeclaredMethods()){
System.out.println(m.toString());
//输出 java.lang.String com.lml.test.generics.SuperClass.method(java.lang.String)
}
}
}
使用了上限通配符后,泛型在经过编译器类型擦除以后,参数类型会被替换成上限类型,而不是Object,所以在使用反射操作编译出来的字节码的时候,加入要用反射调用的时候找的方法前面就是getDeclareMethod("method", String.class)
而不是 getDeclareMethod("method", Object.class)
1
2
3
4
5
6
7
8
9
10
public class SuperClass<T extends String> {
T obj;
T method(T param){return null;};
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
SuperClass<String> a = new SubClass();
a.method("haha");
}
}
反编译字节码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/javap -c com.lml.test.generics.SuperClass
Compiled from "SuperClass.java"
public class com.lml.test.generics.SuperClass<T extends java.lang.String> {
T obj;
public com.lml.test.generics.SuperClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
T method(T);
Code:
0: aconst_null
1: areturn
public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException, java.lang.NoSuchMethodException;
Code:
0: new #2 // class com/lml/test/generics/SubClass
3: dup
4: invokespecial #3 // Method com/lml/test/generics/SubClass."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String haha
11: invokevirtual #5 // Method method:(Ljava/lang/String;)Ljava/lang/String;
14: pop
15: return
}
四. 桥接方法是什么?
上面知道1.5以后有了泛型,并且泛型在编译后还有泛型擦除的特性,在1.5以前编译出来的集合(list,set)是没有类型的,同时1.5以前的类,加入子类的参数类型不一样的话也是通过在父类里面声明类型为Object,子类里面再强转实现特异类型。
想象一下,假如1.5的代码跑在1.8的jdk上面, 假如没有类型擦除,1.8的jdk在运行时严格按照泛型类型进行检查的话, 那么1.5编译出来的List的代码在1.8上将不可运行。
1
2
3
4
//这段代码在1.5以上的jre上要可以正常运行
ArrayList list = new ArrayList<>();
list.add(100);
list.add("String");
所以类型擦除是为了兼容1.5以前jdk编译的代码直接跑在1.8上,而桥接方法是为了在类型擦除以后代码方法能继续运行下去,编译器为涉及到泛型入参的方法自动生成的一个方法,这个桥接方法做的事情就是把这个的方法中的参数类型擦除,在字节码中生成一个泛型类型擦除的方法签名。
因此桥接方法只存在于声明了具体的泛型参数类型的类或方法中。
4.1 桥接方法有什么作用
泛型父类对象引用了一个声明了具体泛型类型的子类实现的时候,在调用某个包含泛型类型的方法的时候,由于类型擦除的原因,父类对象的方法签名的泛型类型是Object或者是声明的泛型上限的。
而声明了具体的泛型类型的子类中, 方法签名是具体的参数类型的。
当一个父类对象引用一个子类对象的时候,用这个父类引用去调用一个涉及泛型入参的时候,会使用父类的方法签名去子类实现找相同的方法签名,但是子类对象中由于已经声明了泛型类型,所以Override的方法签名的入参是具体类型的, 这个时候就需要桥接方法,生成一个和父类类型擦除后一样的方法签名,表明自己遵循java的多态,Override了父类的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SuperClass<T> {
T obj;
T method(T param){return null;};
}
public class SubClass extends SuperClass<String> {
@Override
public String method(String param) {
return param;
}
}
public static void main(String[] args){
SuperClass s = new SubClass(); //这里的s持有的方法签名是父类的方法签名,他不知道子类实现具体的泛型类型是什么
s.method("hah"); //正常调用调用的方法签名是 public java.lang.Object method(java.lang.Object)Ljava.lang.Object;
s.method(1111); //调用的方法签名跟上面的一样, 但是在子类生成的桥接方法中进行强转的时候就会报错。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/javap -c com.lml.test.generics.SubClass
Compiled from "SubClass.java"
public class com.lml.test.generics.SubClass extends com.lml.test.generics.SuperClass<java.lang.String> {
public com.lml.test.generics.SubClass();
Code:
0: aload_0
1: invokespecial #1 // Method com/lml/test/generics/SuperClass."<init>":()V
4: return
public java.lang.String method(java.lang.String);
Code:
0: aload_1
1: areturn
public java.lang.Object method(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #2 // class java/lang/String
5: invokevirtual #3 // Method method:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
}
参考: