1. 概述

OpenRewrite 是一个用于 Java 和其他源代码重构的生态系统。在实际开发中,我们经常需要:

  • 升级依赖到最新版本
  • 应用安全补丁
  • 淘汰废弃 API
  • 技术栈迁移(例如 JUnit 迁移到 AssertJ

OpenRewrite 能帮我们自动化处理这些任务。本文将基于 Spring PetClinic 项目,演示 OpenRewrite 的核心用法。

2. OpenRewrite 基础

2.1 常见重构类型

OpenRewrite 支持以下典型升级场景:

  • 语言升级:Java 8 → Java 11+
  • 框架升级:Spring Boot、Hibernate 等框架版本迁移
  • 依赖迁移:处理破坏性更新的库版本升级
  • 安全补丁:替换有漏洞的方法/库
  • 自定义转换:业务逻辑或基础设施特定改造

2.2 核心机制:Recipe

OpenRewrite 的核心是通过 Recipe(配方) 自动重构代码。特点:

  • 内置大量常用 Recipe
  • 社区贡献广泛
  • 用 Java 编写
  • 通过 Maven/Gradle 插件集成

本文以 Maven 插件为例,Gradle 用户可参考官方文档。

2.3 Maven 插件配置

pom.xml 中添加插件:

<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>5.8.1</version>
    <configuration>
        <activeRecipes>
            <!-- 定义激活的 Recipe -->
        </activeRecipes>
    </configuration>
    <dependencies>
        <!-- Recipe 依赖 -->
    </dependencies>
</plugin>

关键配置说明:

  • <activeRecipes>:指定要执行的 Recipe
  • <dependencies>:Recipe 所需的额外依赖(如特定库版本)

2.4 准备演示项目

克隆 Spring PetClinic 并切换到旧版本分支:

git clone https://github.com/spring-projects/spring-petclinic.git
cd spring-petclinic
git switch -c 2.5.x 1.5.x

当前项目状态:

  • Java 8
  • Spring Boot 1.5

3. Spring Boot 升级实战

3.1 升级到 Spring Boot 2.7

配置 Recipe

<activeRecipes>
    <recipe>org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7</recipe>
</activeRecipes>

<dependencies>
    <dependency>
        <groupId>org.openrewrite.recipe</groupId>
        <artifactId>rewrite-spring</artifactId>
        <version>5.0.11</version>
    </dependency>
</dependencies>

⚠️ 升级到 Spring Boot 3 需使用 UpgradeSpringBoot_3_0 Recipe

执行升级

mvn rewrite:run

典型变更

  1. 父 POM 版本更新

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
    -    <version>1.5.4.RELEASE</version>
    +    <version>2.7.14</version>
    </parent>
    
  2. 构造器注入优化

    -import org.springframework.beans.factory.annotation.Autowired;
    
    class VetController {
        private final VetRepository vets;
    
    -    @Autowired
        public VetController(VetRepository clinicService) {
            this.vets = clinicService;
        }
    }
    

    Spring Boot 2.x+ 单构造器可省略 @Autowired

  3. 连带依赖升级

    • JUnit 4 → JUnit 5
    • 其他托管依赖自动更新

4. JUnit 5 迁移指南

4.1 配置 Recipe

<activeRecipes>
    <recipe>org.openrewrite.java.testing.junit5.JUnit5BestPractices</recipe>
</activeRecipes>

<dependencies>
    <dependency>
        <groupId>org.openrewrite.recipe</groupId>
        <artifactId>rewrite-testing-frameworks</artifactId>
        <version>2.0.12</version>
    </dependency>
</dependencies>

4.2 执行迁移

mvn rewrite:run

4.3 典型变更

- import org.junit.Before;
- import org.junit.Test;
- import org.junit.runner.RunWith;
+ import static org.junit.jupiter.api.Assertions.assertEquals;
+ import static org.junit.jupiter.api.Assertions.assertThrows;
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.extension.ExtendWith;
- import org.mockito.runners.MockitoJUnitRunner;
+ import org.mockito.junit.jupiter.MockitoExtension;

- @Before
- public void setup() {
+ @BeforeEach
+ void setup() {
      this.petTypeFormatter = new PetTypeFormatter(pets);
  }

- @Test(expected = ParseException.class)
- public void shouldThrowParseException() throws ParseException {
-     Mockito.when(this.pets.findPetTypes()).thenReturn(makePetTypes());
-     petTypeFormatter.parse("Fish", Locale.ENGLISH);
+ @Test
+ void shouldThrowParseException() throws ParseException {
+     assertThrows(ParseException.class, () -> {
+         Mockito.when(this.pets.findPetTypes()).thenReturn(makePetTypes());
+         petTypeFormatter.parse("Fish", Locale.ENGLISH);
+     });
  }

自动处理范围

  • ✅ 注解迁移(@Before@BeforeEach
  • ✅ 断言方法更新
  • ✅ JUnit 4 特性替换
  • ✅ 包名调整(junitjunit-jupiter

5. Java 8 → 11 升级

5.1 配置 Recipe

<activeRecipes>
    <recipe>org.openrewrite.java.migrate.Java8toJava11</recipe>
</activeRecipes>

<dependencies>
    <dependency>
        <groupId>org.openrewrite.recipe</groupId>
        <artifactId>rewrite-migrate-java</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>

⚠️ 升级到 Java 17 需使用 UpgradeToJava17 Recipe

5.2 执行升级

mvn rewrite:run

5.3 关键变更

  1. Java 版本设置

    - <java.version>1.8</java.version>
    + <java.version>11</java.version>
    
  2. 依赖调整

    + <dependency>
    +   <groupId>jakarta.xml.bind</groupId>
    +   <artifactId>jakarta.xml.bind-api</artifactId>
    +   <version>2.3.3</version>
    + </dependency>
    + 
    + <dependency>
    +   <groupId>org.glassfish.jaxb</groupId>
    +   <artifactId>jaxb-runtime</artifactId>
    +   <version>2.3.8</version>
    +   <scope>provided</scope>
    + </dependency>
    
  3. 插件更新

    - <wro4j.version>1.8.0</wro4j.version>
    + <wro4j.version>1.10.1</wro4j.version>
    

注意事项

  • Java 11 移除了 javax.xml.bind 模块
  • 需手动添加 JAXB 实现(如 Glassfish)
  • 自动启用 var 等新语法特性

6. 总结

通过 OpenRewrite,我们实现了:

  1. Spring Boot 1.5 → 2.7 的平滑升级
  2. JUnit 4 → 5 的自动化迁移
  3. Java 8 → 11 的版本跃迁

核心优势:

  • ✅ 减少手动重构工作量
  • ✅ 避免升级过程中的常见坑
  • ✅ 保证代码兼容性
  • ✅ 支持自定义 Recipe 扩展

建议在执行前使用 git commit 保存代码,方便通过 git diff 检查变更。对于复杂项目,建议先在测试分支验证。


原始标题:A Guide to OpenRewrite | Baeldung