
本教程详细介绍了如何利用openrewrite框架有条件地修改java方法参数上的注解属性,特别针对spring的`@requestparam`注解。文章将探讨声明式和命令式两种配方(recipe)的实现方式,并重点演示如何通过命令式java配方结合openrewrite的`cursor`机制,根据参数的特定条件(如是否存在其他注解、参数类型或名称)精准地添加或更新注解属性,从而解决在特定代码片段上应用配方时遇到的常见问题,实现更精细化的代码重构。
OpenRewrite是一个强大的代码重构工具,它允许开发者通过编写配方(Recipe)来自动化修改代码库。在许多场景下,我们需要对代码进行有条件的修改,例如只修改满足特定条件的方法参数上的注解。本文将深入探讨如何实现这种精准的代码重构。
OpenRewrite配方可以分为两种主要类型:声明式(Declarative)和命令式(Imperative)。
声明式配方通常以YAML格式定义,适用于表达相对简单的代码修改逻辑。例如,要为所有@RequestParam注解添加或更新required属性并设置为true,可以使用如下声明式配方:
type: specs.openrewrite.org/v1beta/recipe
name: org.example.MandatoryRequestParameter
displayName: Make Spring `RequestParam` mandatory
description: Add `required` attribute to `RequestParam` and set the value to `true`.
recipeList:
- org.openrewrite.java.AddOrUpdateAnnotationAttribute:
annotationType: org.springframework.web.bind.annotation.RequestParam
attributeName: required
attributeValue: "true"应用方式: 将上述YAML文件保存为rewrite.yml在项目根目录,并通过Maven或Gradle插件激活。
Maven配置示例: 在pom.xml中添加OpenRewrite Maven插件:
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>4.38.0</version> <!-- 请使用最新版本 -->
<configuration>
<activeRecipes>
<recipe>org.example.MandatoryRequestParameter</recipe>
</activeRecipes>
</configuration>
</plugin>局限性: 声明式配方虽然简洁,但难以表达复杂的条件逻辑,例如“只修改同时带有@NotNull和@RequestParam注解的参数”。对于这类需求,我们需要借助命令式配方。
命令式配方使用Java编写,提供了更强大的灵活性和控制力,允许开发者通过遍历AST(抽象语法树)并结合Cursor机制来定位和修改代码。
在OpenRewrite中,对代码进行修改通常涉及TreeVisitor。当我们需要在特定上下文(例如,一个注解的父节点是一个参数声明)中应用另一个子配方时,必须确保子配方在正确的AST节点和Cursor上下文中执行。直接在非注解节点上调用AddOrUpdateAnnotationAttribute的Visitor可能导致UncaughtVisitorException,因为它期望在其Cursor的父级找到一个匹配的注解。
以下是一个实现特定条件修改的命令式配方示例。此配方旨在查找同时满足以下条件的@RequestParam注解:
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.AddOrUpdateAnnotationAttribute;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.TypeUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class MandatoryRequestParameter extends Recipe {
private static final String REQUEST_PARAM_FQ_NAME = "org.springframework.web.bind.annotation.RequestParam";
private static final String NOT_NULL_FQ_NAME = "javax.validation.constraints.NotNull"; // 引入NotNull注解的完全限定名
@Override
public @NotNull String getDisplayName() {
return "使Spring `RequestParam`注解强制必填";
}
@Override
public String getDescription() {
return "为满足特定条件的 `RequestParam` 注解添加 `required=true` 属性。";
}
@Override
protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
// 优化:只有当源文件包含 RequestParam 注解时,才运行此Visitor。
return new UsesType<>(REQUEST_PARAM_FQ_NAME);
}
@Override
protected @NotNull JavaVisitor<ExecutionContext> getVisitor() {
// 创建一个用于添加或更新注解属性的内部Visitor实例
JavaIsoVisitor<ExecutionContext> addAttributeVisitor = new AddOrUpdateAnnotationAttribute(
REQUEST_PARAM_FQ_NAME, "required", "true", false
).getVisitor();
return new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
J.Annotation a = super.visitAnnotation(annotation, ctx);
// 1. 检查当前访问的注解是否为 @RequestParam
if (!TypeUtils.isOfClassType(a.getType(), REQUEST_PARAM_FQ_NAME)) {
return a;
}
// 2. 使用 Cursor 向上导航到父节点,获取参数声明
// 当我们访问一个注解时,它的父节点通常是 J.VariableDeclarations (参数声明)
J.VariableDeclarations variableDeclaration = getCursor().getParent().getValue();
// 3. 定义条件:
// a. 检查参数是否带有 @NotNull 注解
boolean hasNotNull = variableDeclaration.getLeadingAnnotations().stream()
.anyMatch(ann -> TypeUtils.isOfClassType(ann.getType(), NOT_NULL_FQ_NAME));
// b. 检查参数类型是否为 java.lang.Number 的子类型
JavaType paramType = variableDeclaration.getType();
boolean isNumberType = TypeUtils.isAssignableTo("java.lang.Number", paramType);
// c. 检查参数名称是否为 "fred"
String paramName = variableDeclaration.getVariables().get(0).getSimpleName();
boolean isFredParam = paramName.equals("fred");
// 4. 应用条件逻辑:如果满足任何一个条件,则委托给 addAttributeVisitor 进行修改
if (hasNotNull || isNumberType || isFredParam) {
// 将当前的注解 'a' 及其 Cursor 传递给 addAttributeVisitor 进行处理
// 这是确保子Visitor在正确上下文执行的关键
return (J.Annotation) addAttributeVisitor.visit(a, ctx, getCursor());
}
return a;
}
};
}
}代码解析:
原始代码:
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotNull;
class ControllerClass {
public String sayHello (
@NotNull @RequestParam(value = "name") String name, // 满足 @NotNull 条件
@RequestParam(value = "lang") String lang, // 不满足条件
@RequestParam(value = "aNumber") Long aNumber, // 满足 Number 类型条件
@RequestParam(value = "fred") String fred // 满足名称为 "fred" 条件
) {
return "Hello";
}
}应用配方后的预期代码:
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotNull;
class ControllerClass {
public String sayHello (
@NotNull @RequestParam(required = true, value = "name") String name,
@RequestParam(value = "lang") String lang,
@RequestParam(required = true, value = "aNumber") Long aNumber,
@RequestParam(required = true, value = "fred") String fred
) {
return "Hello";
}
}OpenRewrite提供了一个方便的测试框架,用于验证配方的行为。
import org.junit.jupiter.api.Test;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
import static org.openrewrite.java.Assertions.java;
class MandatoryRequestParameterTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new MandatoryRequestParameter())
.parser(JavaParser.fromJavaVersion().classpath("spring-web", "validation-api")); // 确保classpath包含必要的依赖
}
@Test
void requiredRequestParam() {
rewriteRun(
java(
"""
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotNull;
class ControllerClass {
public String sayHello (
@NotNull @RequestParam(value = "name") String name,
@RequestParam(value = "lang") String lang,
@RequestParam(value = "aNumber") Long aNumber,
@RequestParam(value = "fred") String fred
) {
return "Hello";
}
}
""",
"""
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotNull;
class ControllerClass {
public String sayHello (
@NotNull @RequestParam(required = true, value = "name") String name,
@RequestParam(value = "lang") String lang,
@RequestParam(required = true, value = "aNumber") Long aNumber,
@RequestParam(required = true, value = "fred") String fred
) {
return "Hello";
}
}
"""
)
);
}
}在测试中,defaults方法用于配置测试环境,包括要运行的配方和Java解析器。rewriteRun方法接收一对字符串,分别代表原始代码和期望修改后的代码,OpenRewrite会执行配方并比较结果。
通过掌握OpenRewrite的命令式配方和Cursor机制,开发者可以实现高度定制化和精准的代码重构任务,极大地提高代码维护和升级的效率。
以上就是OpenRewrite教程:精准修改特定方法参数上的注解属性的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号