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

如何让EclEmma报告包含自定义ClassLoader加载类的覆盖率?

让EclEmma捕获自定义ClassLoader加载类的覆盖率

要解决自定义ClassLoader加载类的覆盖率统计问题,核心是让Jacoco Agent(EclEmma基于它)能拦截到类的加载过程,并复用Agent的运行时环境收集执行数据,具体可按以下步骤操作:

1. 确保Jacoco Agent能拦截自定义ClassLoader的类加载

Jacoco Agent通过java.lang.instrument.Instrumentation的类转换器插装类,默认会处理所有类加载器加载的类,但如果自定义ClassLoader未正确触发转换器,需调整Agent参数并验证:

  • 调整VM启动参数,明确指定要覆盖的类,并开启类转储验证插装情况:
    -javaagent:/path/to/jacocoagent.jar=includes=your.package.**.,output=tcpclient,port=NNNNN,classdumpdir=jacoco_dump
    
    • includes:指定要统计覆盖率的包或类,例如com.yourcompany.**
    • classdumpdir:将插装后的类文件输出到指定目录,可检查自定义ClassLoader加载的类是否被正确插装

2. 复用Jacoco Agent的Runtime环境,避免创建独立Runtime

你当前代码手动创建了LoggerRuntime和RuntimeData,这与EclEmma Agent的运行时完全独立,导致收集的覆盖率数据无法被Agent捕获。正确做法是获取Agent已初始化的Runtime:

修改Coverage类,替换手动创建的Runtime和Instrumenter:

import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.RuntimeData;
import org.jacoco.core.instr.Instrumenter;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public static class Coverage implements AutoCloseable {
    private final IRuntime runtime;
    private final Instrumenter instrumenter;
    private final RuntimeData data;
    private final Set<String> coverageClassNames;

    public Coverage(Class<?>... coverageClasses) {
        try {
            coverageClassNames = Stream.of(coverageClasses)
                .map(Class::getName)
                .collect(Collectors.toUnmodifiableSet());
            
            // 获取Jacoco Agent已初始化的Runtime(替换<version>为实际Jacoco版本,如0.8.10对应internal_0_8_10)
            Field runtimeField = Class.forName("org.jacoco.agent.rt.internal_<version>.Agent")
                .getDeclaredField("runtime");
            runtimeField.setAccessible(true);
            this.runtime = (IRuntime) runtimeField.get(null);
            
            // 获取Agent的RuntimeData
            Field dataField = runtime.getClass().getDeclaredField("data");
            dataField.setAccessible(true);
            this.data = (RuntimeData) dataField.get(runtime);
            
            this.instrumenter = new Instrumenter(runtime);
        } catch (Exception e) {
            throw new RuntimeException("Failed to access Jacoco Agent runtime", e);
        }
    }

    // 其余方法保持不变...
}

注意:需将org.jacoco.agent.rt.internal_<version>.Agent中的<version>替换为你使用的Jacoco版本号,可通过查看Jacoco agent jar包内的类结构确认。

3. 调整自定义ClassLoader的类加载逻辑

确保自定义ClassLoader加载类时,让Jacoco Agent的转换器处理字节码。复用Agent的Runtime后,可修改InstrumentingClassLoader的findClass方法:

private class InstrumentingClassLoader extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] originalBytes = loadClassFile(name);
            // 用Agent的Instrumenter插装类字节码,数据会自动进入Agent的RuntimeData
            byte[] instrumentedBytes = instrumenter.instrument(originalBytes, name);
            return defineClass(name, instrumentedBytes, 0, instrumentedBytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }

    // 其余方法保持不变...
}

4. 确保Session ID一致(手动发送数据时)

如果需要手动发送执行数据到Agent,必须保证Session ID与Agent一致,可通过反射获取:

// 替换<version>为实际Jacoco版本
Field sessionIdField = Class.forName("org.jacoco.agent.rt.internal_<version>.Agent")
    .getDeclaredField("sessionId");
sessionIdField.setAccessible(true);
String sessionId = (String) sessionIdField.get(null);

使用RemoteControlWriter发送数据时指定该Session ID,确保数据被Agent正确接收合并。

原方案不生效的原因

  • 手动创建的LoggerRuntime是独立运行时,与EclEmma Agent的Runtime无关联,收集的覆盖率数据无法被Agent捕获。
  • 自定义ClassLoader加载类时,未让Jacoco Agent的类转换器处理字节码,导致Agent无法跟踪类的执行情况。
  • Session ID不匹配会导致Agent拒绝接收手动发送的执行数据。

内容的提问来源于stack exchange,提问作者boot-and-bonnet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:29:57