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显式声明的;cat和dog位于指定的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 默认扫描哪些包,以及如何自定义这些扫描路径。