ClassGraph 扫描慢的主因是默认全路径扫描,应通过 acceptPaths() 限定路径、禁用无关扫描、复用 ScanResult 等优化;自定义注解需确保保留策略为 CLASS 或 RUNTIME 且在扫描路径内;Spring Boot fat jar 需手动解析 BOOT-INF 路径。

ClassGraph 扫描慢?先确认是不是用了默认配置
ClassGraph 默认会扫描整个类路径(包括所有 jar、classes 目录),在上百个 Jar 包的环境下,不做任何限制时,扫描耗时很容易突破 10 秒——根本不是“秒级”。关键不是换工具,而是关掉它不必要干的事。
实操建议:
- 用
acceptPaths()显式限定只扫你关心的包路径,比如acceptPaths("com.example.api", "com.example.core"),避免遍历javax.*、org.slf4j等无关模块 - 禁用资源扫描:调用
disableRuntimeJarScanning()和disableNestedJarScanning(),除非你真需要解析嵌套 Jar(如 Spring Boot fat jar 内部) - 跳过字节码解析:如果只查注解存在性,不用读取方法体或字段详情,加上
enableAnnotationInfo()就够了,别用enableMethodInfo()或enableFieldInfo() - 避免重复初始化:把
ClassGraph实例构建和scan()拆开,扫描逻辑复用同一个ScanResult,不要每次请求都 new 一个新扫描器
自定义注解没被扫到?检查 ClassGraph 的注解保留策略支持
ClassGraph 只能发现 @Retention(RetentionPolicy.CLASS) 或 @Retention(RetentionPolicy.RUNTIME) 的注解。如果注解是 RetentionPolicy.SOURCE(比如 Lombok 的 @Data),它压根不会出现在字节码里,ClassGraph 必然找不到。
实操建议:
- 确认注解类本身在扫描路径内:如果注解定义在另一个未被
acceptPaths()覆盖的 Jar 中,ClassGraph 无法解析其元信息,会导致getAnnotations()返回空 - 显式注册注解类:对跨模块注解,用
enableAnnotationInfo().acceptAnnotations("com.example.MyCustomAnnotation") - 避免反射加载注解类:不要在扫描前用
Class.forName()触发类加载,这可能干扰 ClassGraph 的字节码读取逻辑;让它纯靠字节码分析
并发扫描多个 Jar 目录?别直接 new 多个 ClassGraph 实例
ClassGraph 内部已做线程安全设计,但每个实例都会独立打开 ZIP 流、解析 MANIFEST、构建索引。并发起 10 个 scan(),实际是 10 套重复 IO + 解析流程,反而更慢。
实操建议:
- 用单个
ClassGraph实例配置多个路径:acceptPaths("lib/a.jar", "lib/b.jar", "classes/"),它内部会合并处理 - 若必须分片(如按业务域隔离),用
ClassGraph().overrideClasspath(...)构造不同扫描上下文,但注意overrideClasspath()接收的是List<URL>,需手动 resolve 到真实 Jar URL,别传字符串路径 - 扫描结果可复用:
scanResult.getSubclasses("java.lang.Exception")这类查询不触发重扫描,放心多次调用
Spring Boot fat jar 下扫描失败?得绕过 BOOT-INF 类加载隔离
Spring Boot 打包后,真实类都在 BOOT-INF/classes/ 和 BOOT-INF/lib/*.jar 下,而 ClassGraph 默认只看顶层 ClassLoader 的 urls,这些路径不会自动包含进去,导致“扫描了却什么也没找到”。
实操建议:
- 手动展开 fat jar:启动时用
new ClassGraph().overrideClasspath(extractBootInfUrls()),其中extractBootInfUrls()需解析META-INF/MANIFEST.MF的Start-Class,再定位BOOT-INF/classes和遍历BOOT-INF/lib/下所有 Jar - 更稳的方式:在非 fat jar 环境下做编译期扫描(如 Maven 插件阶段),或改用
spring-context-indexer配合spring.components文件做静态索引 - 别依赖
Thread.currentThread().getContextClassLoader():fat jar 场景下它返回的是LaunchedURLClassLoader,但它的getURLs()不包含 BOOT-INF 内部路径
真正卡住性能的,往往不是 ClassGraph 本身,而是没意识到它默认在替你“全盘托出”。删掉不需要的扫描项,比升级版本或加机器更管用。另外,注解是否生效、fat jar 路径是否可达,这两点在上线前最容易漏测。

















