OpenRewrite执行顺序问题:依赖未被添加至POM文件
先明确核心执行逻辑
OpenRewrite的Recipe执行顺序确实是先给单个文件应用所有Recipe,再处理下一个文件,而非先把某个Recipe跑完所有文件。这种设计是为了保证单个文件内的变更原子性,但会带来跨文件的依赖感知问题——比如Java文件刚生成了AssertJ的代码,但处理POM文件时,依赖检测逻辑还是基于初始代码快照,看不到刚新增的AssertJ引用,所以第一次运行不会自动添加依赖,第二次扫描到变更后的代码才会生效。
为什么你的Hamcrest转AssertJ场景会复现
你使用的org.openrewrite.java.testing.hamcrest.MigrateHamcrestToAssertJ,其依赖添加逻辑是靠扫描代码中是否存在AssertJ使用触发的。第一次执行时,Java文件的AssertJ代码是刚修改生成的,但POM文件的处理流程已经走完(每个文件按顺序处理,单个文件先跑完全部Recipe),依赖检测逻辑没捕捉到新的引用,所以不会添加依赖。第二次执行时,代码已是修改后的状态,扫描就能检测到引用,自然会添加依赖。
可行的解决方案
1. 拆分执行阶段,分两次运行
把OpenRewrite的执行拆成两步:
- 第一步:仅运行代码迁移类Recipe(如
MigrateHamcrestToAssertJ),先将所有Java文件的Hamcrest代码改为AssertJ - 第二步:运行依赖添加类Recipe(如
AddDependency),此时代码已是修改后的状态,依赖检测能准确触发
在Maven中可配置两个独立的执行块,分别指定不同的Recipe集合:
<build> <plugins> <plugin> <groupId>org.openrewrite.maven</groupId> <artifactId>rewrite-maven-plugin</artifactId> <version>5.40.0</version> <executions> <!-- 第一阶段:仅做代码迁移 --> <execution> <id>migrate-hamcrest-to-assertj</id> <goals> <goal>run</goal> </goals> <configuration> <recipes> <recipe>org.openrewrite.java.testing.hamcrest.MigrateHamcrestToAssertJ</recipe> </recipes> </configuration> </execution> <!-- 第二阶段:添加AssertJ依赖 --> <execution> <id>add-assertj-dependency</id> <goals> <goal>run</goal> </goals> <configuration> <recipes> <recipe>org.openrewrite.maven.AddDependency</recipe> </recipes> <data> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> </data> </configuration> </execution> </executions> </plugin> </plugins> </build>
2. 自定义Recipe,绑定代码迁移和依赖添加
自己编写一个组合Recipe,在代码迁移完成后主动扫描Java文件的变更,再触发依赖添加。这样能保证在同一个执行周期内完成两个动作,无需重复运行。
核心逻辑示例(Java自定义Recipe):
public class MigrateHamcrestToAssertJWithDep extends Recipe { @Override public String getDisplayName() { return "迁移Hamcrest到AssertJ并自动添加依赖"; } @Override public TreeVisitor<?, ExecutionContext> getVisitor() { // 先执行官方迁移Recipe TreeVisitor<?, ExecutionContext> migrateVisitor = new MigrateHamcrestToAssertJ().getVisitor(); // 迁移完成后检查Java文件的AssertJ引用,再给POM添加依赖 return TreeVisitor.postVisit(migrateVisitor, (tree, ctx) -> { if (tree instanceof MavenPom) { // 遍历所有Java文件,检查是否存在AssertJ导入 boolean hasAssertJ = ctx.getFindSourceFiles().findByType(JavaSourceFile.class) .stream() .map(JavaSourceFile.class::cast) .anyMatch(javaFile -> javaFile.getImports().stream() .anyMatch(imp -> imp.getQualifiedName().startsWith("org.assertj"))); if (hasAssertJ) { // 调用AddDependency逻辑添加依赖 return new AddDependency( "org.assertj", "assertj-core", "3.24.2", null, null, null ).getVisitor().visit(tree, ctx); } } return tree; }); } }
3. 参考JUnit5迁移Recipe的实现思路
org.openrewrite.java.testing.junit5.JUnit4to5Migration之所以能自动添加依赖,是因为它在迁移Java代码的同时,主动给POM文件添加依赖,而非等待后续的依赖扫描。你可以参考它的源码,把这种“主动触发”的逻辑用到你的场景中——比如在修改Java文件的Recipe里,直接触发POM的依赖添加动作,跳过依赖扫描环节。
多模块项目的额外提示
如果是多模块Maven项目,要确保Recipe能遍历所有模块的文件,或者使用org.openrewrite.maven.MavenRecipes统一处理父POM和子模块,避免子模块的代码变更未被父POM的依赖逻辑感知到。
内容的提问来源于stack exchange,提问作者Philippe

