如何橋接優化Java方法返回型別實現相容性? - Gunnar

banq發表於2021-12-02

假設我們有一個 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 中使用)。

其他橋架專案:

 

相關文章