You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编译时为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 18:32:07