Spring 组件扫描(Component Scanning)

更新于 2025-12-30

baeldung 2024-05-11

1. 概述

在本教程中,我们将介绍 Spring 中的组件扫描(Component Scanning)。使用 Spring 时,我们可以通过注解将类标记为 Spring Bean。此外,我们还可以告诉 Spring 在哪些包中查找这些带注解的类,因为并非所有类都需要在当前运行中成为 Bean。

当然,Spring 提供了组件扫描的默认行为,但我们也可以自定义要扫描的包。

首先,让我们看看默认设置。

2. 不带参数的 @ComponentScan

2.1 在 Spring 应用程序中使用 @ComponentScan

在 Spring 中,我们通常将 @ComponentScan 注解与 @Configuration 注解一起使用,以指定需要扫描的包。如果 @ComponentScan 不带任何参数,Spring 会自动扫描配置类所在包及其所有子包。

假设我们在 com.baeldung.componentscan.springapp 包中有如下 @Configuration 类:

@Configuration
@ComponentScan
public class SpringComponentScanApp {
    private static ApplicationContext applicationContext;

    @Bean
    public ExampleBean exampleBean() {
        return new ExampleBean();
    }

    public static void main(String[] args) {
        applicationContext = 
          new AnnotationConfigApplicationContext(SpringComponentScanApp.class);

        for (String beanName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
    }
}

此外,在 com.baeldung.componentscan.springapp.animals 包中,我们有两个组件:

package com.baeldung.componentscan.springapp.animals;
// ...
@Component
public class Cat {}
package com.baeldung.componentscan.springapp.animals;
// ...
@Component
public class Dog {}

最后,在 com.baeldung.componentscan.springapp.flowers 包中有一个组件:

package com.baeldung.componentscan.springapp.flowers;
// ...
@Component
public class Rose {}

运行 main() 方法后,输出将包含 com.baeldung.componentscan.springapp 包及其所有子包中的所有 Bean:

springComponentScanApp
cat
dog
rose
exampleBean

注意:主应用程序类本身也是一个 Bean,因为它被 @Configuration 注解标注,而 @Configuration 本质上是 @Component 的一种。

另外需要注意的是,主应用程序类和配置类不一定非得是同一个类。如果它们不同,主应用程序类的位置并不重要,真正起作用的是配置类的位置,因为组件扫描默认从配置类所在的包开始。

最后请注意,在我们的示例中,@ComponentScan 等价于:

@ComponentScan(basePackages = "com.baeldung.componentscan.springapp")

其中 basePackages 参数可以是一个包名或包名数组,用于指定扫描范围。

2.2 在 Spring Boot 应用程序中使用 @ComponentScan

Spring Boot 的巧妙之处在于很多操作都是隐式完成的。我们通常使用 @SpringBootApplication 注解,它实际上是以下三个注解的组合:

  • @Configuration
  • @EnableAutoConfiguration
  • @ComponentScan

现在,我们在 com.baeldung.componentscan.springbootapp 包中创建类似的结构。主应用程序类如下:

package com.baeldung.componentscan.springbootapp;
// ...
@SpringBootApplication
public class SpringBootComponentScanApp {
    private static ApplicationContext applicationContext;

    @Bean
    public ExampleBean exampleBean() {
        return new ExampleBean();
    }

    public static void main(String[] args) {
        applicationContext = SpringApplication.run(SpringBootComponentScanApp.class, args);
        checkBeansPresence(
          "cat", "dog", "rose", "exampleBean", "springBootComponentScanApp");
    }

    private static void checkBeansPresence(String... beans) {
        for (String beanName : beans) {
            System.out.println("Is " + beanName + " in ApplicationContext: " + 
              applicationContext.containsBean(beanName));
        }
    }
}

其他包和类保持不变,只需将它们复制到 com.baeldung.componentscan.springbootapp 包下即可。

Spring Boot 的包扫描方式与前面的例子类似。输出结果如下:

Is cat in ApplicationContext: true
Is dog in ApplicationContext: true
Is rose in ApplicationContext: true
Is exampleBean in ApplicationContext: true
Is springBootComponentScanApp in ApplicationContext: true

之所以在第二个例子中我们只检查 Bean 是否存在(而不是打印出所有 Bean),是因为输出内容会非常庞大。

这是由于隐式的 @EnableAutoConfiguration 注解导致 Spring Boot 根据 pom.xml 中的依赖自动创建了许多 Bean。

3. 带参数的 @ComponentScan

现在,让我们自定义扫描路径。例如,假设我们希望排除 Rose Bean。

3.1 扫描特定包

有几种方式可以实现。首先,我们可以更改基础包:

@ComponentScan(basePackages = "com.baeldung.componentscan.springapp.animals")
@Configuration
public class SpringComponentScanApp {
   // ...
}

此时输出结果为:

springComponentScanApp
cat
dog
exampleBean

原因如下:

  • springComponentScanApp 是作为 AnnotationConfigApplicationContext 的配置类传入的,因此会被注册为 Bean;
  • exampleBean 是在配置类中通过 @Bean 显式声明的;
  • catdog 位于指定的 com.baeldung.componentscan.springapp.animals 包中。

上述所有自定义方式同样适用于 Spring Boot。我们可以在 @SpringBootApplication 上同时使用 @ComponentScan,效果相同:

@SpringBootApplication
@ComponentScan(basePackages = "com.baeldung.componentscan.springbootapp.animals")

3.2 扫描多个包

Spring 提供了一种便捷的方式来指定多个包名。我们可以使用字符串数组:

@ComponentScan(basePackages = {
    "com.baeldung.componentscan.springapp.animals",
    "com.baeldung.componentscan.springapp.flowers"
})

此外,从 Spring 4.1.1 开始,我们还可以使用逗号、分号或空格分隔包名:

@ComponentScan(basePackages = "com.baeldung.componentscan.springapp.animals;com.baeldung.componentscan.springapp.flowers")
@ComponentScan(basePackages = "com.baeldung.componentscan.springapp.animals,com.baeldung.componentscan.springapp.flowers")
@ComponentScan(basePackages = "com.baeldung.componentscan.springapp.animals com.baeldung.componentscan.springapp.flowers")

3.3 使用排除过滤器(Exclusions)

另一种方法是使用过滤器,指定要排除的类的匹配模式:

@ComponentScan(excludeFilters = 
  @ComponentScan.Filter(type = FilterType.REGEX,
    pattern = "com\\.baeldung\\.componentscan\\.springapp\\.flowers\\..*"))

我们也可以选择不同的过滤器类型。该注解支持多种灵活的过滤选项,例如:

@ComponentScan(excludeFilters = 
  @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Rose.class))

4. 默认包(The Default Package)

应避免将 @Configuration 类放在默认包中(即不指定任何包名)。如果这样做,Spring 会尝试扫描 classpath 中所有 JAR 文件里的所有类,这通常会导致错误,并可能导致应用程序无法启动。

5. 结论

在本文中,我们学习了 Spring 默认扫描哪些包,以及如何自定义这些扫描路径。