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

Lombok无需用户导出内部API的实现方式及同类注解处理器开发问题

如何让自定义注解处理器无需引用方添加JVM导出参数即可使用Javac内部API?

我正在开发一款类似Lombok的注解处理器,但遇到了一个棘手的问题——所有引用该处理器的项目都必须添加JVM参数来导出/开放JVM内部库。我试过用tools.jar替代添加JVM导出参数、尝试开放库而非导出库等多种方法,都没能解决问题。目前使用我的库的项目必须在build.gradle中添加如下配置:

compileJava {
    options.fork = true
    options.forkOptions.jvmArgs += [
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
            '--add-exports', 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
    ]
}

请问如何像Lombok一样使用内部API,无需其他项目添加相关参数?


解决方案

Lombok之所以能让引用方免配置JVM参数,核心是通过Jar元数据传递参数+反射兼容的组合方案,具体实现步骤如下:

1. 通过Jar的MANIFEST.MF自动传递导出参数

在你的注解处理器Jar的MANIFEST.MF中添加Add-Exports属性,构建工具(Gradle/Maven)在加载该Jar时会自动应用这些导出规则,无需引用方手动配置。

示例MANIFEST.MF内容:

Multi-Release: true
Add-Exports: jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
Add-Exports: jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

Gradle配置生成该MANIFEST.MF

在你的处理器项目的build.gradle中配置jar任务:

jar {
    manifest {
        attributes(
            'Multi-Release': 'true',
            'Add-Exports': [
                'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
                'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
            ].join(' ')
        )
    }
}

Maven配置生成该MANIFEST.MF

在pom.xml中配置maven-jar-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Multi-Release>true</Multi-Release>
                <Add-Exports>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</Add-Exports>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

2. 用反射+Lookup API兼容高版本JDK访问限制

对于JDK 9+的模块系统,直接编译依赖内部API会触发非法访问错误,Lombok会通过java.lang.invoke.MethodHandles.Lookup反射获取内部类实例,避免编译期依赖。

示例代码(获取JavacTreeMaker):

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;

// 假设你已经获取到了Javac的Context实例
Context context = ...;

try {
    Class<?> treeMakerClass = Class.forName("com.sun.tools.javac.tree.TreeMaker");
    Constructor<?> constructor = treeMakerClass.getDeclaredConstructor(Context.class);
    constructor.setAccessible(true);
    Object treeMaker = MethodHandles.lookup().unreflectConstructor(constructor).invoke(context);
    // 使用treeMaker进行后续操作
} catch (Exception e) {
    // 处理反射异常
}

3. 兼容旧版构建工具(可选)

如果部分旧版本Gradle/Maven插件不支持自动读取Add-Exports属性,可以开发一个配套的Gradle/Maven插件,在插件中自动为compileJava任务注入所需的JVM参数。比如Gradle插件示例:

class MyProcessorPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.tasks.withType(JavaCompile).each { task ->
            task.options.fork = true
            task.options.forkOptions.jvmArgs.addAll([
                '--add-exports', 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
                // 其他导出参数
            ])
        }
    }
}

引用方只需要应用你的插件,无需手动配置参数。


内容的提问来源于stack exchange,提问作者phoenix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:43:13