如何桥接优化Java方法返回类型实现兼容性? - Gunnar


假设我们有一个 Java 库,它提供了一个公共类和方法,如下所示:

public class SomeService {

  public Number getSomeNumber() {
    return 42L;
  }
}

但过了一段时间,人们开始抱怨:Number他们宁愿使用更具体的返回类型而不是通用的返回类型,我们同意最初的 API 定义并不理想,我们改变了方法定义,现在返回Long。
但是在我们发布了具有该更改的库的 2.0 版本后不久,用户报告了一个新问题:升级到新版本后,他们在运行应用程序时突然出现以下错误:

java.lang.NoSuchMethodError: 'java.lang.Number dev.morling.demos.bridgemethods.SomeService.getSomeNumber()'
  at dev.morling.demos.bridgemethods.SomeClientTest.shouldReturn42(SomeClientTest.java:27)

当将方法返回类型从更改Number为Long时,我们做了一个破坏我们库的二进制兼容性的更改。JVM 正在寻找SomeService::getSomeNumber()返回的方法Number,但在我们服务的 2.0 版本的类文件中找不到它。
它还解释了为什么不是所有用户都报告了这个问题:那些在升级到 2.0 时重新编译自己的应用程序的人不会遇到任何问题,因为编译器只会使用新版本的方法并将该签名的调用放入任何调用者的类文件。只有那些没有重新编译代码的用户才会遇到问题,即更改实际上是源代码兼容的。
 
有一个工具正是用于此目的: Bridger
Bridger 允许您创建自己的桥接方法,使用 ASM 应用将方法转换为桥接方法所需的类文件转换。它带有一个 Maven 插件,用于将此转换步骤集成到您的构建过程中。下面是我们需要的插件配置:

<plugin>
  <groupId>org.jboss.bridger</groupId>
  <artifactId>bridger</artifactId>
  <version>1.5.Final</version>
  <executions>
    <execution>
      <id>weave</id>
      <phase>process-classes</phase> 
      <goals>
        <goal>transform</goal>
      </goals>
    </execution>
  </executions>
  <dependencies>
    <dependency> 
      <groupId>org.ow2.asm</groupId>
      <artifactId>asm</artifactId>
      <version>9.2</version>
    </dependency>
  </dependencies>
</plugin>

  • <phase>process-classes</phase> :将transform目标绑定到process-classes构建生命周期阶段,以便修改Java编译器生成的类
  • 使用最新版本的 ASM,所以我们可以使用 Java 17

有了插件,你可以像这样定义桥接方法,使用$$bridge名称后缀:
public class SomeService {

  /**
    * @hidden 
    */

  public Number getSomeNumber$$bridge() { 
    return getSomeNumber();
  }

  public Long getSomeNumber() {
    return 42L;
  }
}

  • 通过@hiddenJavaDoc 标记(在 Java 9 中添加),此方法将从为我们的库生成的 JavaDoc 中排除
  • public Number getSomeNumber$$bridge() 桥接方法;名称后缀Bridger 将被删除,即它会被命名getSomeNumber;它还将具有ACC_BRIDGE和ACC_SYNTHETIC修饰符

利用桥接方法,我们可以纠正 1.0 版 API 中的故障,并在我们库的新版本中改进方法返回类型,而不会破坏源代码或与现有用户的二进制兼容性。
通过@hiddenJavaDoc标签,我们的bridge方法的源代码不会出现在渲染文档中(这会比较混乱),并且在类文件中标记为合成桥方法,它也不会出现在在 IDE 中查看 JAR。
如果您想开始自己探索 Java 桥接方法,可以在此GitHub 存储库中找到示例的完整源代码。用于跟踪 API 更改和识别任何潜在破坏性更改的有用工具包括SigTest (例如,我们在 Bean 验证规范中使用此工具以确保向后兼容性)和Revapi (我们在 Debezium 中使用)。
其他桥架项目: