Lombok无需用户导出内部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

