如何在maven-compiler-plugin中让注解处理器加载传递依赖的Plugin?
核心原因
当你通过maven-compiler-plugin的annotationProcessorPaths配置注解处理器时,注解处理器的运行类路径是完全独立的——它只包含annotationProcessorPaths里声明的依赖,项目C的普通<dependencies>(包括项目B的插件)不会被加入到这个类路径中。这就是为什么ServiceLoader找不到插件的META-INF/services配置和实现类。
而把注解处理器设为provided依赖时,处理器会和项目的编译类路径合并,插件自然会出现在类路径里,所以能被正确加载。
可行解决方案
1. 将插件添加到annotationProcessorPaths
既然注解处理器的类路径是独立的,直接把项目B的插件也加入到annotationProcessorPaths即可,这是最符合Maven设计的方案。
- 全局统一配置(父POM):如果所有子项目都需要这个插件,在父POM的
pluginManagement里追加插件路径:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <parameters>true</parameters> <annotationProcessorPaths> <annotationProcessorPath> <groupId>my.group-id</groupId> <artifactId>processor</artifactId> <version>${processor.version}</version> </annotationProcessorPath> <!-- 把插件加入注解处理器类路径 --> <annotationProcessorPath> <groupId>my.group-id</groupId> <artifactId>plugin</artifactId> <version>${plugin.version}</version> </annotationProcessorPath> </annotationProcessorPaths> </configuration> </plugin>
- 子项目按需配置:如果插件是可选的,子项目可以在自己的
maven-compiler-plugin中追加路径(父POM的配置会被继承):
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <!-- 仅追加当前项目需要的插件 --> <annotationProcessorPath> <groupId>my.group-id</groupId> <artifactId>plugin</artifactId> <version>${plugin.version}</version> </annotationProcessorPath> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
2. 让注解处理器依赖插件(强制依赖场景)
如果项目A的注解处理器必须依赖项目B的插件才能运行,直接在项目A的POM中添加对项目B的依赖。这样当你配置processor的annotationProcessorPath时,Maven会自动引入其传递依赖(项目B),无需额外配置。但这种方式会让所有使用该处理器的项目都自动引入插件,只适合插件是处理器核心组件的场景。
3. 修改类加载器逻辑(灵活但需注意隔离)
修改项目A中ServiceLoader的加载逻辑,让它使用项目编译类路径的类加载器(而非处理器自身的类加载器):
// 使用上下文类加载器加载插件,该类加载器包含项目C的所有依赖 ServiceLoader.load(type, Thread.currentThread().getContextClassLoader()) .stream() .map(provider -> injector.get(provider.type())) .peek(it -> { it.initialize(); logger.debug(() -> "Initialized ProcessorPlugin: " + it.getClass().getSimpleName()); }) .toList();
这种方式下,项目C的普通依赖会被包含在上下文类加载器的类路径中,ServiceLoader就能找到插件。但要注意类加载隔离问题,避免出现类冲突。
总结
优先选择方案1,它既保持了注解处理器类路径的独立性,又能通过父POM统一配置,满足你不想每个项目都单独声明处理器的需求。
内容的提问来源于stack exchange,提问作者Thorben Kuck

