Java编译时实现方法参数动态化:自定义Lombok式注解需求
实现支持Supplier参数的动态方法重载(类似Lombok注解)
针对你的大型Builder类需求,推荐两种可行方案:基于JDK注解处理器(APT)的编译时生成,或者自定义Lombok插件直接修改原类字节码,下面是具体实现步骤:
方案一:自定义注解+APT编译时生成重载方法
这种方案基于标准JDK特性,不依赖Lombok内部API,兼容性更强。
1. 定义@DynamicArguments注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) // 仅在源码阶段生效 public @interface DynamicArguments { }
2. 实现注解处理器(用JavaPoet简化代码生成)
使用JavaPoet库生成包含重载方法的Builder子类,自动将非函数式接口参数替换为Supplier<T>:
import com.squareup.javapoet.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import java.util.*; @SupportedAnnotationTypes("com.yourpackage.DynamicArguments") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DynamicArgumentsProcessor extends AbstractProcessor { private Elements elementUtils; private Types typeUtils; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils(); typeUtils = processingEnv.getTypeUtils(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(DynamicArguments.class)) { if (!(element instanceof ExecutableElement method)) continue; TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); List<ParameterSpec> overloadParams = new ArrayList<>(); List<CodeBlock> argCalls = new ArrayList<>(); // 遍历原方法参数,生成重载方法的参数和调用逻辑 for (VariableElement param : method.getParameters()) { TypeMirror paramType = param.asType(); if (isFunctionalInterface(paramType)) { // 保留原函数式接口参数(比如lambda类型) overloadParams.add(ParameterSpec.builder(paramType, param.getSimpleName().toString()).build()); argCalls.add(CodeBlock.of("$N", param.getSimpleName())); } else { // 替换为Supplier<T>参数 TypeName supplierType = ParameterizedTypeName.get(ClassName.get(Supplier.class), TypeName.get(paramType)); String supplierParamName = param.getSimpleName() + "Supplier"; overloadParams.add(ParameterSpec.builder(supplierType, supplierParamName).build()); argCalls.add(CodeBlock.of("$N.get()", supplierParamName)); } } // 构建重载方法体 MethodSpec.Builder overloadMethodBuilder = MethodSpec.methodBuilder(method.getSimpleName().toString()) .addModifiers(method.getModifiers()) .returns(TypeName.get(method.getReturnType())) .addParameters(overloadParams); if (method.getReturnType().getKind() == TypeKind.VOID) { overloadMethodBuilder.addStatement("this.$N($L)", method.getSimpleName(), CodeBlock.join(argCalls, ", ")); } else { overloadMethodBuilder.addStatement("return this.$N($L)", method.getSimpleName(), CodeBlock.join(argCalls, ", ")); } // 生成原Builder的子类,包含重载方法 TypeSpec dynamicBuilder = TypeSpec.classBuilder(enclosingClass.getSimpleName() + "Dynamic") .superclass(ClassName.get(enclosingClass)) .addMethod(overloadMethodBuilder.build()) .build(); try { JavaFile.builder(elementUtils.getPackageOf(enclosingClass).getQualifiedName().toString(), dynamicBuilder) .writeTo(processingEnv.getFiler()); } catch (Exception e) { processingEnv.getMessager().printMessage(javax.tools.Diagnostic.Kind.ERROR, "生成动态Builder失败: " + e.getMessage()); } } return true; } // 判断是否为函数式接口(排除lambda类型参数) private boolean isFunctionalInterface(TypeMirror type) { if (type.getKind() != TypeKind.DECLARED) return false; DeclaredType declaredType = (DeclaredType) type; TypeElement typeElement = (TypeElement) declaredType.asElement(); return typeElement.getAnnotation(FunctionalInterface.class) != null; } }
3. 依赖配置(Maven)
添加JavaPoet依赖:
<dependency> <groupId>com.squareup</groupId> <artifactId>javapoet</artifactId> <version>1.13.0</version> <scope>provided</scope> </dependency>
使用方式
编译后会生成原Builder类名Dynamic的子类,你可以用这个子类调用重载方法:
new MyBuilderDynamic().randFunc(() -> "123456", () -> 8);
方案二:自定义Lombok插件(直接修改原类)
如果希望直接在原Builder类中生成重载方法,和Lombok的@Builder等注解体验一致,可以自定义Lombok插件:
1. 实现Lombok注解处理器
import lombok.core.AnnotationHandler; import lombok.core.HandlerPriority; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; @ProviderFor(AnnotationHandler.class) @HandlerPriority(HandlerPriority.DEFAULT) public class DynamicArgumentsHandler extends JavacAnnotationHandler<DynamicArguments> { @Override public void handle(DynamicArguments annotation, JavacNode annotationNode) { JavacNode methodNode = annotationNode.up(); if (!(methodNode.get() instanceof JCTree.JCMethodDecl method)) return; TreeMaker treeMaker = annotationNode.getTreeMaker(); List<JCTree.JCVariableDecl> overloadParams = List.nil(); List<JCTree.JCExpression> argCalls = List.nil(); for (JCTree.JCVariableDecl param : method.params) { Type paramType = param.type.type; if (isFunctionalInterface(paramType, annotationNode)) { overloadParams = overloadParams.append(param); argCalls = argCalls.append(treeMaker.Ident(param.name)); } else { // 构建Supplier<T>类型 Type supplierType = annotationNode.getTypeUtils().getDeclaredType( annotationNode.getTypeUtils().getTypeElement("java.util.function.Supplier"), paramType ); Name supplierParamName = annotationNode.getNames().fromString(param.name + "Supplier"); JCTree.JCVariableDecl supplierParam = treeMaker.VarDef( treeMaker.Modifiers(List.nil()), supplierParamName, treeMaker.Type(supplierType), null ); overloadParams = overloadParams.append(supplierParam); // 生成 supplier.get() 调用 argCalls = argCalls.append(treeMaker.App( treeMaker.Ident(annotationNode.getNames().fromString("get")), treeMaker.Ident(supplierParamName), List.nil() )); } } // 构建方法体 JCTree.JCStatement body; if (method.restype.type.getKind() == Type.Kind.VOID) { body = treeMaker.Exec(treeMaker.App( treeMaker.Ident(method.name), treeMaker.Ident(annotationNode.getNames().fromString("this")), argCalls )); } else { body = treeMaker.Return(treeMaker.App( treeMaker.Ident(method.name), treeMaker.Ident(annotationNode.getNames().fromString("this")), argCalls )); } // 创建重载方法并添加到原类 JCTree.JCMethodDecl overloadMethod = treeMaker.MethodDef( method.mods, method.name, method.restype, method.typeparams, overloadParams, method.thrown, treeMaker.Block(0, List.of(body)), method.defaultValue ); JavacNode classNode = methodNode.up(); ((JCTree.JCClassDecl) classNode.get()).defs = ((JCTree.JCClassDecl) classNode.get()).defs.append(overloadMethod); classNode.rebuild(); } private boolean isFunctionalInterface(Type type, JavacNode node) { if (type.getKind() != Type.Kind.INTERFACE) return false; TypeElement typeElement = (TypeElement) node.getTypeUtils().asElement(type); return typeElement.getAnnotation(FunctionalInterface.class) != null; } }
2. 打包Lombok插件
- 将上述类打包成JAR
- 在JAR的
META-INF/services/lombok.core.AnnotationHandler文件中添加类的全限定名(比如com.yourpackage.DynamicArgumentsHandler)
3. 项目中使用
- 引入该插件JAR
- 在项目根目录添加
lombok.config文件:lombok.config.stopBubbling = true lombok.extensionMethod = com.yourpackage.DynamicArgumentsHandler - 给Builder方法添加
@DynamicArguments注解后,编译时会自动在原类中生成重载方法,直接调用即可:new MyBuilder().randFunc(() -> "123456", () -> 8);
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| APT生成子类 | 基于标准JDK,兼容性好,无Lombok依赖 | 需要使用生成的子类,不能直接修改原类 |
| Lombok插件 | 直接修改原类,使用体验和原生Lombok一致 | 依赖Lombok内部API,版本兼容性可能受影响 |
内容的提问来源于stack exchange,提问作者Laipel
相关产品推荐
相关产品推荐

