编译时为Java接口添加常量字段(不修改源码)
Java 11编译阶段为接口自动生成方法名常量字段
方案一:标准注解处理器生成辅助类(无内部API依赖)
这种方案完全基于Java标准的JSR 269注解处理API,兼容Java 8到最新版本,不需要依赖编译器内部类。
1. 定义注解@MyAnnotation
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { }
2. 实现注解处理器
处理器会扫描带@MyAnnotation的接口,生成一个对应常量类,把接口的方法名转为大写常量:
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; import java.util.Set; @SupportedAnnotationTypes("com.yourpackage.MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_11) public class MyAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) { if (element.getKind() != ElementKind.INTERFACE) continue; TypeElement interfaceElement = (TypeElement) element; String interfaceName = interfaceElement.getSimpleName().toString(); String packageName = processingEnv.getElementUtils().getPackageOf(interfaceElement).getQualifiedName().toString(); try { JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + interfaceName + "Constants"); try (PrintWriter writer = new PrintWriter(file.openWriter())) { writer.println("package " + packageName + ";"); writer.println("public final class " + interfaceName + "Constants {"); writer.println(" private " + interfaceName + "Constants() {}"); for (Element enclosed : interfaceElement.getEnclosedElements()) { if (enclosed.getKind() == ElementKind.METHOD) { ExecutableElement method = (ExecutableElement) enclosed; String methodName = method.getSimpleName().toString(); writer.println(" public static final String " + methodName.toUpperCase() + " = \"" + methodName + "\";"); } } writer.println("}"); } } catch (IOException e) { processingEnv.getMessager().printError("生成常量文件失败: " + e.getMessage()); } } return true; } }
使用方式
编译完成后,直接通过FooConstants.METHOD_A访问常量,全程无需修改原接口源码。
方案二:字节码修改实现接口内嵌常量(类似Lombok效果)
如果必须要直接通过Foo.METHOD_A访问常量,需要结合注解处理器和ASM字节码工具,同时处理Java11的模块访问限制。
1. 添加依赖(Maven)
<dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>9.5</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> <annotationProcessors> <annotationProcessor>com.yourpackage.MyBytecodeProcessor</annotationProcessor> </annotationProcessors> <!-- Java11+必须添加的模块导出参数,允许访问编译器内部API --> <compilerArgs> <arg>--add-exports</arg> <arg>jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> <arg>--add-exports</arg> <arg>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> <arg>--add-exports</arg> <arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>--add-exports</arg> <arg>jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> </compilerArgs> </configuration> </plugin> </plugins> </build>
2. 实现字节码修改处理器
这个处理器会在编译阶段读取接口的class文件,插入静态常量字段后重新写入:
import com.sun.tools.javac.processing.JavacProcessingEnvironment; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Set; @SupportedAnnotationTypes("com.yourpackage.MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_11) public class MyBytecodeProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) { if (element.getKind() != ElementKind.INTERFACE) continue; TypeElement interfaceElement = (TypeElement) element; String className = interfaceElement.getQualifiedName().toString().replace('.', '/'); try (InputStream is = processingEnv.getClassLoader().getResourceAsStream(className + ".class")) { if (is == null) { processingEnv.getMessager().printError("找不到类文件: " + interfaceElement.getQualifiedName()); continue; } // 解析原字节码 ClassReader cr = new ClassReader(is); ClassNode cn = new ClassNode(); cr.accept(cn, ClassReader.EXPAND_FRAMES); // 为每个方法添加静态常量 for (Element enclosed : interfaceElement.getEnclosedElements()) { if (enclosed.getKind() == ElementKind.METHOD) { String methodName = enclosed.getSimpleName().toString(); String constantName = methodName.toUpperCase(); // 避免重复添加 boolean exists = cn.fields.stream().anyMatch(f -> f.name.equals(constantName)); if (!exists) { FieldNode field = new FieldNode( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, constantName, "Ljava/lang/String;", null, methodName ); cn.fields.add(0, field); // 插入到字段列表开头 } } } // 生成修改后的字节码并写入 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cn.accept(cw); byte[] modifiedBytes = cw.toByteArray(); try (OutputStream os = javacEnv.getFiler().createClassFile(interfaceElement).openOutputStream()) { os.write(modifiedBytes); } } catch (IOException e) { processingEnv.getMessager().printError("修改类文件失败: " + e.getMessage()); } } return true; } }
效果验证
编译后,生成的Foo.class会包含METHOD_A和METHOD_B静态常量,直接通过Foo.METHOD_A即可访问,和你预期的效果完全一致。
内容的提问来源于stack exchange,提问作者lelmarir
相关产品推荐
相关产品推荐

