如何将Maven中ECJ插件的输出传递给自定义插件?
如何在自定义Maven插件中获取ECJ的错误与警告(无需文件输出)
与其绕弯子去读取文件输出,其实有几种更直接、更优雅的方式来获取ECJ的诊断信息,下面是我实践过的几个靠谱方案:
1. 直接集成ECJ编译API,主动捕获诊断信息
这是最灵活可控的方式——不用依赖现成的Maven ECJ插件,直接在你的自定义插件里调用ECJ核心API编译代码,同时收集所有错误和警告细节。
步骤示例:
首先在插件的pom.xml里引入ECJ依赖:
<dependency> <groupId>org.eclipse.jdt</groupId> <artifactId>org.eclipse.jdt.core</artifactId> <version>3.36.0</version> <!-- 替换为最新稳定版 --> </dependency>
然后编写代码调用ECJ,自定义监听器收集诊断:
import org.eclipse.jdt.core.compiler.Problem; import org.eclipse.jdt.core.compiler.IDiagnosticListener; import org.eclipse.jdt.internal.compiler.*; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.Mojo; import java.io.File; import java.util.ArrayList; import java.util.List; // 自定义诊断监听器,直接捕获ECJ的Problem对象 class EcjDiagnosticCollector implements IDiagnosticListener { private final List<Problem> diagnostics = new ArrayList<>(); @Override public void reportProblem(Problem problem) { diagnostics.add(problem); // 可直接区分错误/警告:problem.isError() / problem.isWarning() } public List<Problem> getDiagnostics() { return diagnostics; } } @Mojo(name = "ecj-analyze") public class EcjAnalyzerMojo extends AbstractMojo { @Parameter(defaultValue = "${project.basedir}/src/main/java", required = true) private File sourceDir; @Override public void execute() { EcjDiagnosticCollector collector = new EcjDiagnosticCollector(); // 配置ECJ编译参数(比如指定Java版本) CompilerOptions options = new CompilerOptions(); options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_17); options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_17); options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_17); // 初始化编译器(这里需要实现名称环境和请求器,可参考ECJ官方示例或现有插件代码) INameEnvironment nameEnv = createNameEnvironment(); ICompilerRequestor requestor = (unit, classFiles) -> {}; // 忽略编译结果,只关心诊断 Compiler compiler = new Compiler( nameEnv, DefaultErrorHandlingPolicies.exitOnFirstError(), options, requestor, collector, new DefaultProblemFactory() ); // 扫描源码文件并触发编译 List<ICompilationUnit> units = scanSourceFiles(sourceDir); compiler.compile(units.toArray(new ICompilationUnit[0])); // 处理收集到的诊断信息 for (Problem problem : collector.getDiagnostics()) { getLog().info(String.format("[%s] %s - %s 第%d行", problem.isError() ? "ERROR" : "WARNING", problem.getMessage(), problem.getOriginatingFileName(), problem.getSourceLineNumber() )); // 这里可以加入你的自定义解析逻辑 } } // 辅助方法:扫描源码目录生成编译单元列表(需自行实现) private List<ICompilationUnit> scanSourceFiles(File sourceDir) { // 遍历目录,把.java文件转为ICompilationUnit实例 return new ArrayList<>(); } // 辅助方法:创建名称环境(处理类路径、依赖等,需自行实现) private INameEnvironment createNameEnvironment() { // 可参考org.eclipse.jdt.internal.compiler.env.NameEnvironment的实现 return null; } }
这种方式的核心优势是直接拿到ECJ的Problem对象,包含错误类型、位置、详细消息等结构化数据,完全不需要解析字符串,可靠性拉满。
2. 通过Maven BuildListener监听ECJ插件日志
如果一定要复用现有的Maven ECJ插件,你可以注册一个BuildListener,实时捕获ECJ输出的日志信息,再做解析。
步骤示例:
先实现一个BuildListener:
import org.apache.maven.execution.AbstractExecutionListener; import org.apache.maven.execution.ExecutionEvent; import org.codehaus.plexus.component.annotations.Component; import org.apache.maven.BuildListener; import java.util.ArrayList; import java.util.List; @Component(role = BuildListener.class) public class EcjLogCollector extends AbstractExecutionListener { private final List<String> ecjMessages = new ArrayList<>(); @Override public void logEvent(ExecutionEvent event) { String msg = event.getMessage(); // 过滤ECJ输出的错误/警告日志(通常带[ERROR]/[WARNING]前缀) if (msg != null && (msg.startsWith("[ERROR]") || msg.startsWith("[WARNING]"))) { ecjMessages.add(msg); } } public List<String> getEcjMessages() { return ecjMessages; } }
然后在你的Mojo里注入这个监听器,获取收集到的日志:
import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.LifecyclePhase; @Mojo(name = "ecj-log-parse", defaultPhase = LifecyclePhase.COMPILE) public class EcjLogParserMojo extends AbstractMojo { @Component private EcjLogCollector logCollector; @Override public void execute() { for (String msg : logCollector.getEcjMessages()) { // 解析日志字符串,提取错误/警告信息 getLog().info("解析到ECJ消息:" + msg); } } }
这种方式的缺点是需要解析日志字符串,容易受日志格式变更影响,但好处是可以直接复用现有ECJ插件的配置。
3. 扩展Maven ECJ插件,自定义问题报告器
如果用的是Tycho的ECJ插件或者Apache的maven-compiler-plugin(指定ECJ为编译器),可以实现ECJ的IProblemReporter,让插件直接把诊断信息传给你。
核心思路:
- 实现
IProblemReporter接口,在reportProblem方法中收集问题; - 通过Maven插件配置,让ECJ插件使用你的自定义报告器;
- 在你的插件中获取报告器实例,拿到收集到的问题列表。
这种方式需要修改ECJ插件的配置,相对复杂,但能深度集成现有插件流程。
总的来说,直接集成ECJ API的方式是最优解,它不依赖外部插件的输出,直接获取结构化的诊断信息,解析成本最低也最可靠。
内容的提问来源于stack exchange,提问作者Jobinary




