• 细讲java 桥接


    1.桥接方法简介

    桥接方法是jdk1.5引入泛型后,为使java泛型方法生成的字节码与jdk1.5版本之前的字节码兼容由编译器自动生成的。

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

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

    当子类继承父类(继承接口)实现抽象泛型方法的时候,编译器会为子类自动生成桥接方法

    1. class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#父类
    2. public abstract class SuperClass {
    3. public abstract T get(T t) ;
    4. }
    5. #子类
    6. public class SubClass extends SuperClass {
    7. @Override
    8. public String get(String s) {
    9. return s;
    10. }
    11. }
  • 使用 javap -v SubClass.class 命令查看类SubClass的字节码:

    1. "prettyprint hljs yaml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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
    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."":()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
    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;
    33. #21 = Utf8 SourceFile
    34. #22 = Utf8 SubClass.java
    35. #23 = NameAndType #6:#7 // "":()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."":()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;
    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. class="prettyprint hljs typescript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public String get(String s) {
  2. return s;
  3. }
  4. #桥接方法
  5. public Object get(Object s) {
  6. return get((String) s);
  7. }

泛型-类型擦除

  1. class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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. class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">{
  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."":()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;
  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. class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Slf4j
  2. public class SubClass extends SuperClass {
  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. }

输出如下:

  • 相关阅读:
    LeetCode220912_102、除法求值
    JAVA注解说明:
    汽车信息安全--如何理解TrustZone(2)
    一晚上做了一个xpath终结者:xpath-helper-plus
    基于Java+SpringBoot+MyBatis的高铁/火车售票/订票系统
    Mojo 摸脚语言,似乎已经可以安装
    VUE 项目 自动按需导入
    机器学习中的线性代数
    06 ts扩展知识
    jQuery获取表单的值val()
  • 原文地址:https://blog.csdn.net/weixin_62421895/article/details/125998187