在本文中,我们将介绍Spring组件扫描以及如何使用它。本文的所有示例使用Spring Boot来实现
为了实现依赖项注入,Spring创建了一个应用程序上下文
在启动期间,Spring实例化对象并将其添加到应用程序上下文中。应用程序上下文中的对象称为Spring bean或组件。
Spring解析Spring bean之间的依赖关系,并将Spring bean注入到其他Spring bean的字段或构造函数中。
在类路径中搜索应该构成应用程序上下文的类的过程称为组件扫描
如果Spring发现一个用几个注释中的一个注释注释的类,它将把这个类看作是在组件扫描期间添加到应用程序上下文的Spring bean的候选类。
Spring组件主要由四种类型组成
这是一个通用的原型注释,用来指示类是spring管理的组件;其他原型是@Component的特例化
这表明带注释的类是一个spring管理的控制器,它提供带有@RequestMapping注释的方法来响应web请求。
Spring 4.0引入了@RestController注释,它结合了@Controller和@ResponseBody,使创建返回JSON对象的RESTful服务变得容易
我们可以对包含业务逻辑的类或进入服务层的类使用@Service构造型
我们可以为负责提供对数据库实体的访问的DAO类使用@Repository构造型。
如果我们使用Spring Data管理数据库操作,那么我们应该使用Spring Data Repository接口,而不是构建自己的带有@Repository注释的类
Spring提供了一种通过@ComponentScan注释显式识别Spring bean候选对象的机制。
如果应用程序是一个Spring引导应用程序,那么包含Spring引导应用程序类的包下的所有包都将被隐式组件扫描覆盖。
SpringBoot的@SpringBootApplication注释暗示了@Configuration、@ComponentScan和@EnableAutoConfiguration注释。
默认情况下,@ComponentScan注释将扫描当前包及其所有子包中的组件。因此,如果您的应用程序没有变化的包结构,那么就不需要显式组件扫描。
在默认包中指定一个@Configuration注释类将告诉Spring扫描类路径中所有jar中的所有类。
我们使用@ComponentScan注释和@Configuration注释来告诉Spring扫描用任何原型注释注释的类。@ComponentScan注释提供了不同的属性,我们可以通过修改这些属性来获得所需的扫描行为。
在本文中,我们将使用ApplicationContext的getBeanDefinitionNames()方法来检查已成功扫描并添加到应用程序上下文的bean列表:
@Component
class BeanViewer {
private final Logger LOG = LoggerFactory.getLogger(BeanViewer.class);
@EventListener
public void showBeansRegistered(ApplicationReadyEvent event) {
String[] beanNames = event.getApplicationContext()
.getBeanDefinitionNames();
for(String beanName: beanNames) {
LOG.info("{}", beanName);
}
}
}
上面的BeanViewer将打印在应用程序上下文中注册的所有bean。这将帮助我们检查组件是否被正确加载。
如前所述,Spring Boot会自动扫描属于父包的所有包。让我们看看文件夹结构:
|- com.example.componentscan (main package)
|- SpringComponentScanningApplication.java
|- UserService.java (@Service stereotype)
|- BeanViewer.java
我们已经在父包com.example.componentscan中使用@Service构造型创建了一个UserService类。如前所述,由于这些类在父包下,其中有我们的@ SpringBootApplication注释的应用程序类,当我们启动SpringBoot应用程序时,默认情况下组件将被扫描:
...
INFO 95832 --- [main] com.example.componentscan.BeanViewer : beanViewer
INFO 95832 --- [main] com.example.componentscan.BeanViewer : users
...
上面的输出显示了为BeanViewer、ExplicitScan创建的bean
如果我们有一个不在父包之下的包,或者我们根本没有使用Spring Boot,我们可以使用@ComponentScan和@Configuration bean。
这将告诉Spring扫描这个@Configuration类的包及其子包中的组件:
package com.example.birds;
@Configuration
@ComponentScan
public class BirdsExplicitScan {
}
birds包紧挨着应用程序的主包,所以它不会被Spring Boot的默认扫描捕获:
|- com.example.componentscan
|- SpringComponentScanningApplication.java
|- com.example.birds
|- BirdsExplicitScan.java (@Configuration)
|- Eagle.java (@Component stereotype)
|- Sparrow.java (@Component stereotype)
如果我们想在Spring Boot应用程序中包含BirdsExplicitScan,我们必须导入它:
@SpringBootApplication
@Import(value= {BirdsExplicitScan.class})
public class SpringComponentScanningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringComponentScanningApplication.class, args);
}
}
当我们启动应用程序时,我们得到如下输出:
...
INFO 95832 --- [main] com.example.componentscan.BeanViewer : beanViewer
INFO 95832 --- [main] com.example.componentscan.BeanViewer : users
INFO 84644 --- [main] com.example.componentscan.BeanViewer : eagle
INFO 84644 --- [main] com.example.componentscan.BeanViewer : sparrow
...
正如我们在上面的输出中看到的,已经为Eagle和Sparrow类创建了bean。
让我们来看看@ComponentScan注释的属性,我们可以用来修改它的行为:
为了演示不同的属性,让我们向包com.example.vehicles添加一些类(它不是我们的应用程序主包com.example.componentscan的子包):
|- com.example.componentscan (Main Package)
|- ExplicitScan.java (@Configuration)
|- com.example.birds
|- com.example.vehicles
|- Car.java
|- Hyundai.java (@Component stereotype and extends Car)
|- Tesla.java (@Component stereotype and extends Car)
|- SpaceX.java (@Service stereotype)
|- Train.java (@Service stereotype)
让我们看看如何在组件扫描期间控制加载哪些类
我们将在应用程序的主包中创建类ExplicitScan类,这样它就会被默认的组件扫描选中。然后,我们添加包com.example.vehicles通过@ComponenScan注释的basePackages属性打包:
package com.example.componentscan;
@Configuration
@ComponentScan(basePackages= "com.example.vehicles")
public class ExplicitScan {
}
如果我们运行应用程序,我们会看到所有com.example.vehicles包中的组件都包含在应用程序上下文中:
...
INFO 65476 --- [main] com.example.componentscan.BeanViewer : hyundai
INFO 65476 --- [main] com.example.componentscan.BeanViewer : spaceX
INFO 65476 --- [main] com.example.componentscan.BeanViewer : tesla
INFO 65476 --- [main] com.example.componentscan.BeanViewer : train
...
让我们看看如何只包含扩展Car类型的类来进行组件扫描:
@Configuration
@ComponentScan(basePackages= "com.example.vehicles",
includeFilters=
@ComponentScan.Filter(
type=FilterType.ASSIGNABLE_TYPE,
classes=Car.class),
useDefaultFilters=false)
public class ExplicitScan {
}
通过includeFilters和FilterType的组合,我们可以告诉Spring包含遵循指定筛选条件的类。
我们使用过滤器类型ASSIGNABLE_TYPE来捕获所有可分配给/扩展Car类的类。
其他可用的过滤器类型有:
在上面的例子中,我们已经用includeFilters修改了我们的ExplicitScan类,以包含扩展Car.class的组件,并且我们正在修改useDefaultFilters = false,以便只应用我们特定的过滤器。
现在,只有现代和特斯拉bean被包括在组件扫描,因为他们扩展了汽车类:
INFO 68628 --- [main] com.example.componentscan.BeanViewer : hyundai
INFO 68628 --- [main] com.example.componentscan.BeanViewer : tesla
与includeFilters类似,我们可以使用FilterType和excludeFilters来根据匹配条件排除被扫描的类
让我们用excludeFilters修改我们的ExplicitScan,并告诉Spring从组件扫描中排除扩展Car的类
@Configuration
@ComponentScan(basePackages= "com.example.vehicles",
excludeFilters=
@ComponentScan.Filter(
type=FilterType.ASSIGNABLE_TYPE,
classes=Car.class))
public class ExplicitScan {
}
注意,我们没有将useDefaultFilters设置为false,因此在默认情况下,Spring将包括包中的所有类。
输出显示,我们排除了现代和特斯拉bean,只有包中的其他两个类包含在扫描中:
...
INFO 97832 --- [main] com.example.componentscan.BeanViewer : spaceX
INFO 97832 --- [main] com.example.componentscan.BeanViewer : train
...
大量的使用@ComponentScan注释会很快导致对如何创建应用程序的规则感到困惑!尽量少使用它,应该尽量使用约定的方式组织包和类
另外,一种好的做法是显式地导入带有@Import注释的@Configuration类,并将@ComponentScan注释添加到该配置类中,以便仅自动扫描该类的包。这样,我们在应用程序的包之间就有了清晰的边界
在本文中,我们了解了Spring组件原型、什么是组件扫描以及如何使用组件扫描,以及我们可以修改其各种属性以获得所需的扫描行为