java 桥接方法

打印 上一主题 下一主题

主题 926|帖子 926|积分 2778

1.桥接方法简介

桥接方法是jdk1.5引入泛型后,为使java泛型方法生成的字节码与jdk1.5版本之前的字节码兼容由编译器自动生成的。
可用method.isBridge() 判断method是否是桥接方法,在生成的字节码中会有flags标记 ACC_BRIDGE, ACC_SYNTHETIC ,根据来自深入理解java虚拟机的一张访问标志图可以看到 ACC_BRIDGE表示方法是由编译器产生的桥接方法,ACC_SYNTHETIC表示方法由编译器自动产生不属于源码。

2. 什么时候会生成桥接方法

当子类继承父类(继承接口)实现抽象泛型方法的时候,编译器会为子类自动生成桥接方法
  1. #父类
  2. public abstract class SuperClass<T> {
  3.   public abstract T get(T t) ;
  4. }
  5. #子类
  6. public class SubClass extends SuperClass<String> {
  7.   @Override
  8.   public String get(String s) {
  9.     return s;
  10.   }
  11. }
复制代码
使用javap -v SubClass.class命令查看类SubClass的字节码:
  1. Classfile /Users/xudong/project-maven/test/person-study/dubbo-provider/target/classes/com/monian/dubbo/provider/study/generic/SubClass.class
  2.   Last modified 2022年7月25日; size 777 bytes
  3.   MD5 checksum 1328a7043cde4b809a156e7a239335a6
  4.   Compiled from "SubClass.java"
  5. public class com.monian.dubbo.provider.study.generic.SubClass extends com.monian.dubbo.provider.study.generic.SuperClass<java.lang.String>
  6.   minor version: 0
  7.   major version: 52
  8.   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  9.   this_class: #4                          // com/monian/dubbo/provider/study/generic/SubClass
  10.   super_class: #5                         // com/monian/dubbo/provider/study/generic/SuperClass
  11.   interfaces: 0, fields: 0, methods: 3, attributes: 2
  12. Constant pool:
  13.    #1 = Methodref          #5.#23         // com/monian/dubbo/provider/study/generic/SuperClass."<init>":()V
  14.    #2 = Class              #24            // java/lang/String
  15.    #3 = Methodref          #4.#25         // com/monian/dubbo/provider/study/generic/SubClass.get:(Ljava/lang/String;)Ljava/lang/String;
  16.    #4 = Class              #26            // com/monian/dubbo/provider/study/generic/SubClass
  17.    #5 = Class              #27            // com/monian/dubbo/provider/study/generic/SuperClass
  18.    #6 = Utf8               <init>
  19.    #7 = Utf8               ()V
  20.    #8 = Utf8               Code
  21.    #9 = Utf8               LineNumberTable
  22.   #10 = Utf8               LocalVariableTable
  23.   #11 = Utf8               this
  24.   #12 = Utf8               Lcom/monian/dubbo/provider/study/generic/SubClass;
  25.   #13 = Utf8               get
  26.   #14 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  27.   #15 = Utf8               s
  28.   #16 = Utf8               Ljava/lang/String;
  29.   #17 = Utf8               MethodParameters
  30.   #18 = Utf8               (Ljava/lang/Object;)Ljava/lang/Object;
  31.   #19 = Utf8               Signature
  32.   #20 = Utf8               Lcom/monian/dubbo/provider/study/generic/SuperClass<Ljava/lang/String;>;
  33.   #21 = Utf8               SourceFile
  34.   #22 = Utf8               SubClass.java
  35.   #23 = NameAndType        #6:#7          // "<init>":()V
  36.   #24 = Utf8               java/lang/String
  37.   #25 = NameAndType        #13:#14        // get:(Ljava/lang/String;)Ljava/lang/String;
  38.   #26 = Utf8               com/monian/dubbo/provider/study/generic/SubClass
  39.   #27 = Utf8               com/monian/dubbo/provider/study/generic/SuperClass
  40. {
  41.   public com.monian.dubbo.provider.study.generic.SubClass();
  42.     descriptor: ()V
  43.     flags: (0x0001) ACC_PUBLIC
  44.     Code:
  45.       stack=1, locals=1, args_size=1
  46.          0: aload_0
  47.          1: invokespecial #1                  // Method com/monian/dubbo/provider/study/generic/SuperClass."<init>":()V
  48.          4: return
  49.       LineNumberTable:
  50.         line 7: 0
  51.       LocalVariableTable:
  52.         Start  Length  Slot  Name   Signature
  53.             0       5     0  this   Lcom/monian/dubbo/provider/study/generic/SubClass;
  54.   public java.lang.String get(java.lang.String);
  55.     descriptor: (Ljava/lang/String;)Ljava/lang/String;
  56.     flags: (0x0001) ACC_PUBLIC
  57.     Code:
  58.       stack=1, locals=2, args_size=2
  59.          0: aload_1
  60.          1: areturn
  61.       LineNumberTable:
  62.         line 11: 0
  63.       LocalVariableTable:
  64.         Start  Length  Slot  Name   Signature
  65.             0       2     0  this   Lcom/monian/dubbo/provider/study/generic/SubClass;
  66.             0       2     1     s   Ljava/lang/String;
  67.     MethodParameters:
  68.       Name                           Flags
  69.       s
  70.   public java.lang.Object get(java.lang.Object);
  71.     descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
  72.     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
  73.     Code:
  74.       stack=2, locals=2, args_size=2
  75.          0: aload_0
  76.          1: aload_1
  77.          2: checkcast     #2                  // class java/lang/String
  78.          5: invokevirtual #3                  // Method get:(Ljava/lang/String;)Ljava/lang/String;
  79.          8: areturn
  80.       LineNumberTable:
  81.         line 7: 0
  82.       LocalVariableTable:
  83.         Start  Length  Slot  Name   Signature
  84.             0       9     0  this   Lcom/monian/dubbo/provider/study/generic/SubClass;
  85.     MethodParameters:
  86.       Name                           Flags
  87.       s                              synthetic
  88. }
  89. Signature: #20                          // Lcom/monian/dubbo/provider/study/generic/SuperClass<Ljava/lang/String;>;
  90. SourceFile: "SubClass.java"
复制代码
可以看到字节码中有两个get方法,第二个方法参数和返回值类型都是java.lang.Object 并且可以看到flags有相应标志ACC_BRIDGE, ACC_SYNTHETIC说明此方法就是有编译器自动生成的桥接方法。再看code属性:
aload_0:把this变量装载到操作数栈中
aload_1:把方法变量s装载到操作数栈中
checkcast # 2:校验栈顶变量s是否为java.lang.String类型
invokevirtual # 3: 调用方法 public String get(String s)
areturn: 返回结果 
根据上述code解释可以看出编译器生成的桥接方法为这个样子的,桥接方法实际上调用了实际的泛型方法
  1. public String get(String s) {
  2. return s;
  3. }
  4. #桥接方法
  5. public Object get(Object s) {
  6.   return get((String) s);
  7. }
复制代码
 
泛型-类型擦除
  1. public class SubClass extends SuperClass<String> {
  2.   @Override
  3.   public String get(String s) {
  4.     return s;
  5.   }
  6.   public static void main(String[] args) {
  7.     SuperClass subClass = new SubClass();
  8.     Object s = "hello world";
  9.     System.out.println(subClass.get(s));
  10.   }
  11. }
复制代码
java的泛型在运行时会进行泛型擦除替换成非泛型上边界,java虚拟机无法知道准确的类型。 上述代码能编译通过并且会调用子类SubClass的桥接方法由桥接方法再去调用实际泛型方法。如果定义为SuperClass subClass = new SubClass(); 那么get方法入参只能为String变量,因为编译器在编译期间会进行类型校验,不符合类型将直接报编译失败。
3. 为什么生成泛型方法
  1. {
  2.   public com.monian.dubbo.provider.study.generic.SuperClass();
  3.     descriptor: ()V
  4.     flags: (0x0001) ACC_PUBLIC
  5.     Code:
  6.       stack=1, locals=1, args_size=1
  7.          0: aload_0
  8.          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
  9.          4: return
  10.       LineNumberTable:
  11.         line 7: 0
  12.       LocalVariableTable:
  13.         Start  Length  Slot  Name   Signature
  14.             0       5     0  this   Lcom/monian/dubbo/provider/study/generic/SuperClass;
  15.       LocalVariableTypeTable:
  16.         Start  Length  Slot  Name   Signature
  17.             0       5     0  this   Lcom/monian/dubbo/provider/study/generic/SuperClass<TT;>;
  18.   public abstract T get(T);
  19.     descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
  20.     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
  21.     MethodParameters:
  22.       Name                           Flags
  23.       t
  24.     Signature: #18                          // (TT;)TT;
  25. }
复制代码
为了能够正确的编译,可以看到源码中父类SuperClass get方法参数类型为T(T t),而在字节码层面可以看到,经过编译后,get方法入参和返回值类型都为Object。
可以想象一下,如果没有编译器自动生成的桥接方法,那么编译是不会通过的。父类SubClass get方法经过编译后入参和返回值类型都为Object,而子类get方法入参和返回值类型为String,子类并没有重写父类的get方法(重写:访问的方法的实现过程进行重新编写, 返回值和形参都不能改变)。所有编译器需要生成一个桥接方法,Object get(Object) 就可以编译通过了。
 
4. 根据桥接方法获取实际泛型方法 

主要借助Spring的BridgeMethodResolver#findBridgedMethod找到被桥接的方法,原理是首先找到类声明的所有方法,找到与桥接方法简单名称和方法参数数量相同的候选方法,若只要一个则直接返回,若有多个则循环判断方法参数类型是否相同或者候选方法都有相同的方法签名则从其中任选一个方法作为被桥接的方法。
  1. @Slf4j
  2. public class SubClass extends SuperClass<String> {
  3.   @Override
  4.   public String get(String s) {
  5.     return s;
  6.   }
  7.   public static void main(String[] args) throws Exception {
  8.     SubClass subClass = new SubClass();
  9.     Method bridgeMethod = subClass.getClass().getDeclaredMethod("get", Object.class);
  10.     log.info("bridgeMethod is bridge:" + bridgeMethod.isBridge());
  11.     log.info("bridgeMethod:" + bridgeMethod.toString());
  12.     // 实际泛型方法
  13.     Method actualMethod = subClass.getClass().getDeclaredMethod("get", String.class);
  14.     log.info("actualMethod:" + actualMethod.toString());
  15.     // 通过spring #BridgeMethodResolver由桥接方法获取到实际泛型方法
  16.     Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(bridgeMethod);
  17.     log.info("bridgedMethod:" + bridgedMethod.toString());
  18.   }
  19. }
复制代码
输出如下:
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表