在Spring寻找注入点的时候,有一个findBridgedMethod,寻找桥接方法?那什么桥接方法是什么呢?
先来看一个产生桥接方法的场景:一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法.
代码案例:
public interface IService<T> {
void setHandler(T handler);
}
@Service
public class AService implements IService<AHandler> {
private Handler handler;
@Autowired
public void setHandler(AHandler handler) {
this.handler = handler;
}
}
我们使用IDEA自带的工具查看AService的字节码(view->Show Bytecode)
① 我们发现字节码文件中,有俩个setHandler方法。第二个setHandler前面有个synthetic bridge,这就是我们所说的桥接方法
② 这俩个方法都有@Autowired注解,这意味着有俩个注入点,这样不对,所以Spring提供了一个BridgeMethodResolver,来处理这种情况
③ 桥接方法调用了实际的方法
桥接方法调用实际方法时,会进行强制类型转换,上面桥接方法的字节码转换过来就是:
@Autowired
public void setHandler(Object handler) {
setHandler((AHandler))handler);
}
也就是说,桥接方法其实就是将Object强转后调用了我们实际的方法,来看看下面的测试代码:
public class BridgeMethodTest {
public static void main(String[] args) {
IService service = new AService();
service.setHandler(new AHandler()); //调用的是实际方法
service.setHandler(new Object()); //调用的是桥接方法。桥接方法会进行类型强制转换后,调用实际方法,所以这里会报转换异常
}
}
现在我们知道了桥接方法是什么,那为什么需要桥接方法?这就需要我们去了解一下泛型以及泛型擦除了。
泛型
为什么需要泛型
先来看一个简单的代码:
public static void main(String[] args) {
ArrayList al=new ArrayList();
al.add("Hello World");
al.add(1001);
String str=(String)al.get(1);
System.out.println(str);
}
一个集合是可以存任意类型数据的,所以在早期没有泛型时,我们在遍历一个集合时,拿到一个Object对象往往还要进行强制转换,但有强转失败的风险,而且这个风险是在程序运行时才会被发现。
为此,在JDK1.5引入了泛型的机制,通过泛型可以统一集合中的数据类型。编译器借助泛型,可以在编译期间,就替我们发现类型不符的问题。
泛型擦除
JAVA的泛型机制基本上都是在编译器层面实现的,在生成字节码的过程中,泛型的信息会被去除,我们称这个过程为泛型擦除。
下面我们来看一个例子:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> arrayList3=new ArrayList<>();
arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
for (int i=0;i<arrayList3.size();i++) {
System.out.println(arrayList3.get(i));
}
}
这里我们通过反射,强行往一个泛型为Integer的集合里塞了一个字符串。之所以可以这么做,是因为ArrayList在被编译成字节码时,泛型的信息已经被擦除。
擦除前 擦除后
boolean add(E e) ==> boolean add(Object e)
在清楚了泛型擦除后,我们在来看看为什么需要桥接方法:
public interface IService<T> {
void setHandler(T handler);
}
@Service
public class AService implements IService<AHandler> {
private Handler handler;
@Autowired
public void setHandler(AHandler handler) {
this.handler = handler;
}
}
根据泛型擦除,IService字节码对应的代码应该长这样:
public interface IService {
void setHandler(Object handler);
}
发现问题了没有,我们原意是AService重写父类的setHandler方法,在泛型擦除后,变成了重载父类的setHandler方法了。因此为了兼容类似的问题,编译器会为AService生成一个桥接方法:
public class AService implements IService<AHandler> {
private Handler handler;
@Autowired
public void setHandler(AHandler handler) {
this.handler = handler;
}
// 编译器为我们生成的桥接方法。重写了父类的方法,并调用实际的方法
@Autowired
public void setHandler(Object handler) {
setHandler((AHandler)handler);
}
}
参考:
java中什么是bridge method(桥接方法)_痴人说梦-CSDN博客_桥接方法
面试题系列:用了这么多年的 Java 泛型,我竟然只知道它的皮毛 - 跟着Mic学架构 - 博客园 (cnblogs.com)