bigywater.tech
Spring · 9

组件扫描:@ComponentScan 的代价与元数据读取

组件扫描到底在做什么、代价在哪?它会加载类吗?过滤器怎么用?为什么主类不能放在默认包?两个包里同名的类会怎样?这篇在 Spring Framework 7.0.9 上用脚本生成的类逐项量出来。

205 个
扫描 100、1000、3000 个 @Component 时,扫描期间新加载的类数(不随组件数增长)
44 → 401 ms
同一个包里的普通类从 0 个增加到 8000 个时 scan() 的耗时
默认包
@SpringBootApplication 的主类放在默认包时启动失败(扫描到 classpath 里的 AspectJ 相关配置)

扫描不加载类

实测 · 本机执行输出
  @Component 数  |  scan() 耗时  |  扫描时新加载的类  |  BeanDefinition 数  |  refresh() 耗时(实例化)  |  refresh 时新加载的类
  100  |  41 ms  |  205  |  105  |  97 ms  |  471
  1000  |  144 ms  |  205  |  1005  |  274 ms  |  1371
  3000  |  220 ms  |  205  |  3005  |  551 ms  |  3371

scan() 读 .class 的元数据而不加载类,扫描期间新加载的类数不随组件数增长;类在实例化时才加载。

范围决定耗时

实测 · 本机执行输出
  包里的普通类数  |  scan() 耗时  |  扫描时新加载的类  |  BeanDefinition 数
  0  |  44 ms  |  205  |  105
  2000  |  145 ms  |  206  |  105
  8000  |  401 ms  |  206  |  105
  扫描用元数据读取(读 .class 文件,不加载类),所以扫描期间新加载的类很少;但每个 .class 文件都要被打开读一遍,不管有没有注解,所以扫描的范围越大越慢;类是在后面实例化的时候才被加载

耗时随扫描范围里的类文件数增长,与是否有注解无关。

过滤器

实测 · 本机执行输出
  [svc0, svc1, svc10, svc11, svc12, svc13, svc14, svc15, svc16, svc17, svc18, svc19, svc2, svc3, svc4, svc5, svc6, svc7, svc8]

includeFilters / excludeFilters 控制留下哪些类。

默认包

实测 · 本机执行输出
  主类在默认包:启动失败 BeanCreationException ← ClassNotFoundException: org.aspectj.lang.annotation.Pointcut
  主类在 demo 包:启动成功

主类放在默认包会让扫描范围变成整个 classpath,启动失败。

同名冲突

实测 · 本机执行输出
  ConflictingBeanDefinitionException: Annotation-specified bean name 'svc' for bean class [demo.p2.Svc] conflicts with existing, non-compatible bean definition of same name and class [demo.p1.Svc]

不同包里同名的 @Component 默认 Bean 名一样,会抛 ConflictingBeanDefinitionException。

分步演示

组件扫描
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。类是脚本生成的;第一、二节每个规模各启动 5 个 JVM 取中位数,第一节第 3000 个那行的 scan 耗时每次运行在 200 到 230 ms 之间;第四节用 SpringApplication(WebApplicationType.NONE),类路径里有 spring-boot-starter-webmvc
    • 没有验证:@ComponentScan 的元数据读取是不是用 ASM(只观察到了“扫描时不加载类”这个现象);spring-context-indexer(已经被移除,没有测);多模块、多 jar 同包名时的扫描;默认包失败的完整原因链(只记录了根异常);AOT 模式下组件扫描在构建期完成的情形
    京ICP备2026067502号-1京公网安备11010502063487号