OpenRewrite 学习笔记(一):基本知识与原理解析
最近的工作多与系统改造相关,涉及框架与平台的迁移。偶然接触到 OpenRewrite 这款有趣的工具,于是打算整理一些学习笔记,也是对学习过程的记录。后续还会持续探索并更新文章,只是目前尚不确定会写多少篇。
随着软件项目日趋复杂、版本迭代和新技术引进的加快,工程师往往需要对成千上万行的代码进行统一风格化处理、批量重构或版本迁移。然而手动完成这些工作不仅费时费力,而且容易遗漏。大规模场景下,整个过程像是一个大的工程实施,因此就有了 Migration Engineering。
Migration Engineering 迁移工程是一种系统化的工程实践,广泛应用于技术栈更新、代码现代化、架构优化和大规模变更管理。通过自动化工具和流程的结合,它能够在确保质量的同时减少人工介入,从而提高效率,降低风险,并确保代码库在技术演进中保持一致性。
- From ChatGPT
为了实现精确可控且可重复的代码自动化重构(Automated Refactoring),出现了多种工具和框架。OpenRewrite 正是其中备受关注的一个,它能帮助开发者在大规模代码重构中以一种结构化、可持续和可扩展的方式对代码进行批量改写和重构,从而提升开发团队的生产力和代码质量。
接下来,和我一起从从基础认识入手,了解什么是 OpenRewrite 以及它所依据的基本原理。
什么是 OpenRewrite
OpenRewrite 是由 Moderne 公司开源的自动化重构框架。它的目标是通过一系列可组合的“配方”(官方术语 Recipes,可以理解为重构规则,为了更贴近官方文档的术语,将其翻译为配方。)在无需手动干预的情况下对代码进行有条理的结构重写。简言之,它不是简单的全局替换字符串工具,而是能基于 无损语义树(LST) 进行语义级别的代码修改。
应用场景
在深入原理之前,我们先来看几个常见的场景,这能帮助我们理解为什么要使用 OpenRewrite:
- 统一代码风格和格式化规则: 当团队决定采用统一的代码风格(如使用特定的命名规范、去除不必要的注释、多余的空格或换行),可以通过 OpenRewrite 的规则对全仓库进行自动化的清理。
- 批量升级依赖版本和框架迁移: 想象你维护着一个庞大的微服务体系结构,需要从 Spring Boot 2.x 升级到 3.x,这往往意味着在几十个甚至上百个仓库中同步修改数百个依赖项。OpenRewrite 可以通过一条 Recipe 来自动化完成这些变更。
- 统一日志框架或安全库: 如果团队决定弃用某个日志库或安全框架,需要全仓库替换相关调用。手动修改非常繁琐,而 OpenRewrite 能确保精确定位和替换代码。
- 遗留代码现代化: 对老旧项目的遗留代码进行现代化改造,例如将早期的 Java 语言特性(如匿名内部类)改为更现代的 Lambda 表达式,将旧的集合框架替换为流式处理 API,快速为老系统注入新特性和代码规范。
关键特性
- 语法级与语义级分析:基于 LST 进行代码解析,确保代码修改的精准性。
- 可扩展的配方(Recipes):提供大量内置和可扩展的规则集,用于自动化处理常见的代码问题。配方可与其他配方组合成更复杂的配方。
- 语言与框架的广泛支持:编程语言如 Java、Kotlin、Groovy;数据格式:XML、YAML、Properties、JSON、ProtoBuf;构建工具 Maven、Gradle;框架:Quarkus、Spring、Micronaut、Jakarta。
如果说 Recipe 是 OpenRewrite 进行自动化重构的核心机制,那么 LST 就是是确保代码在重构时具备精确语义结构的基石。
基本原理概述
OpenRewrite 的核心思想是:通过对源代码进行解析和抽象(LST 建模),从而在结构化的层面上对代码进行精准、可控的修改。
无损语义树 LST
与传统的 AST(Abstract Syntax Tree)相比,OpenRewrite 使用的是一种被称为 Lossless Semantic Tree (LST) 的数据结构。LST 的特点是无损地保留了原始源代码的所有细节信息,这不仅包括代码的语法和语义,还包含了空格、换行、注释、格式化风格等通常会在简单 AST 转换中丢失的内容。
配方 Recipe 与访问者 Visitors
OpenRewrite 的核心概念之一是 Recipe(配方)。Recipe 是一个或者一组行为逻辑的集合,它定义了如何对代码进行分析或修改。配方可以是非常简单的(如在类名中增加一个后缀)或非常复杂的(如将一套老旧框架的使用逻辑迁移到新框架中)。
在更底层的实现中,Recipe 中经常会用到 Visitor 模式(访问者模式)。Visitor 会遍历 LST 中的所有节点,对符合条件的节点进行相应改写。这个过程类似于为代码中的各个部分定制“检阅官”:当检阅官走到满足条件的代码元素时,就应用对应的规则进行修改。
LST 的生命周期
OpenRewrite 针对不同的编程语言和数据格式,提供了对应的解析器。解析器会将源文件作为静态文本进行解析,解析成 LST 数据结构。
当在本地运行 OpenRewrite 的配方时:
- OpenRewrite 会创建一个 LST 并将其存储在内存中。这个 LST 代表了磁盘上当前代码仓库的状态。
- 接下来,执行指定的配方对 LST 进行各种转换操作。
- 当配方执行完成后,LST 会被转换回纯文本形式,然后使用这些文本覆盖本地那些已被修改的文件。
- 在所有文件成功被覆写持久化后,整个过程结束。在不同的配方运行之间不会存留任何信息。
- 如果你在第一次配方运行结束后继续运行另外一个配方,那么在那个时候会重新生成一个新的 LST。
- 如果上一个配方对仓库作出了修改,这些修改已经以文本形式存在于本地文件中,那么新生成的 LST 将包含所有这些变更。如果上一个配方没有作出任何修改,那么新生成的 LST 将与之前的 LST 在内容上基本保持一致(不过是重新生成的,因为之前的 LST 已不复存在)。
快速上手
OpenRewrite 官方提供了大量拆箱即用的配方,也有 社区提供的大量配方,通过 Recipe Catalog 可以查看配方的使用说明。同时,这些配方也为我们编写自己的配方提供了参考。
OpenRewrite 提供了多种方式让我们不写任何代码就可以运行已有的配方。OpenRewrite 提供了 Maven 和 Gradle 的插件,同时在 IDEA 2024.1 的 Ultimate 版本中加入了对 OpenRewrite 的支持。
- 在命令行中使用 Maven 或 Gradle 插件,比如
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=org.openrewrite.java.RemoveUnusedImports
。不过,命令行的方式无法为配方配置参数。 - 在
pom.xml
或者build.gradle
中使用插件,这种方式我们可以对插件和配方进行灵活的配置。 - 通过在项目中创建 YAML 文件,通过 IDEA 来执行。
当然还可以自己编写代码来执行配置。这里我们选择最后一种,使用已有的配方来演示如何修改。Java 项目用以前演示 Tekton 时用的 tekton-demo。
在代码中有两个 endpoint,我想讲其中一个的方法名修改为 greeting
。
package com.example.tekton.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class TektonTestApplication {
public static void main(String[] args) {
SpringApplication.run(TektonTestApplication.class, args);
}
@GetMapping("/hi")
public String hello() {
return "hello, devops";
}
@GetMapping("/hi/{name}")
public String hello(@PathVariable String name) {
return "hello, " + name + "!";
}
}
在项目的根目录中创建一个文件 rewrite.yml
,使用配置 ChangeMethodName
,指定要替换的方法签名,以及新的方法名。
---
type: specs.openrewrite.org/v1beta/recipe
name: com.atbug.demo.ChangeMethodNameExample
displayName: Change method name example
recipeList:
- org.openrewrite.java.ChangeMethodName:
methodPattern: com.example.tekton.test.TektonTestApplication hello(String)
newMethodName: greeting
在 IDEA 中可以识别到该配置文件,点击按钮就可以运行。在 IDEA 中执行,实际也是运行 Maven Rewrite 插件,只是无需修改 pom 文件。
查看结果可以看到方法名已经被修改了。
[INFO] Using active recipe(s) [com.atbug.demo.ChangeMethodNameExample]
[INFO] Using active styles(s) []
[INFO] Validating active recipes...
[INFO] Project [tekton-test] Resolving Poms...
[INFO] Project [tekton-test] Parsing source files
[INFO] Running recipe(s)...
[WARNING] Changes have been made to src/main/java/com/example/tekton/test/TektonTestApplication.java by:
[WARNING] org.openrewrite.java.ChangeMethodName: {methodPattern=com.example.tekton.test.TektonTestApplication hello(String), newMethodName=greeting}
[WARNING] Please review and commit the results.
[WARNING] Estimate time saved: 5m
总结
本篇文章介绍了 OpenRewrite 的基本概念与原理,并以较为简单的示例进行演示。实际应用中,可以灵活组合多种配方,或自行编写更复杂的规则,以实现大规模且可控的代码迁移。此外,还可将 OpenRewrite 集成至 CI/CD 流程,实现自动化的迁移与持续测试。
尽管官方和社区已提供大量现成的配方,但在企业中往往会遇到许多特定的业务场景,因此自行编写更为复杂的配方在所难免。要实现这一点,我们需要更深入地理解 LST、配方与访问者等核心概念,以确保能够定制出满足实际需求的解决方案。后续我将继续整理更多相关笔记并分享给大家。