Spring依赖注入引发的桥接方法思考

Java中的桥接方法是在泛型擦除后,为了解决子类重写父类泛型方法时可能出现的问题而生成的。当子类指定泛型类型时,编译器会生成桥接方法以保持方法签名的一致性。桥接方法调用实际方法时会进行类型转换,确保安全。泛型的引入是为了在编译期检查类型安全,避免运行时的强制转换风险。而泛型擦除则意味着在字节码中不保留泛型信息,可能导致方法重载而非重写。桥接方法的存在确保了正确的行为。
摘要由CSDN通过智能技术生成

在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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值