Spring Boot中 的Component组件扫描原理和运用

Spring Boot中 的Component组件扫描原理和运用

1. 概述

在本文中,我们将介绍Spring组件扫描以及如何使用它。本文的所有示例使用Spring Boot来实现

2. 什么是组件扫描

为了实现依赖项注入,Spring创建了一个应用程序上下文

在启动期间,Spring实例化对象并将其添加到应用程序上下文中。应用程序上下文中的对象称为Spring bean或组件。

Spring解析Spring bean之间的依赖关系,并将Spring bean注入到其他Spring bean的字段或构造函数中。

在类路径中搜索应该构成应用程序上下文的类的过程称为组件扫描

3. 构造型注解

如果Spring发现一个用几个注释中的一个注释注释的类,它将把这个类看作是在组件扫描期间添加到应用程序上下文的Spring bean的候选类。

Spring组件主要由四种类型组成

3.1 @Component

这是一个通用的原型注释,用来指示类是spring管理的组件;其他原型是@Component的特例化

3.2 @Controller

这表明带注释的类是一个spring管理的控制器,它提供带有@RequestMapping注释的方法来响应web请求。

Spring 4.0引入了@RestController注释,它结合了@Controller和@ResponseBody,使创建返回JSON对象的RESTful服务变得容易

3.3 @Service

我们可以对包含业务逻辑的类或进入服务层的类使用@Service构造型

3.4 @Repository

我们可以为负责提供对数据库实体的访问的DAO类使用@Repository构造型。

如果我们使用Spring Data管理数据库操作,那么我们应该使用Spring Data Repository接口,而不是构建自己的带有@Repository注释的类

4. 什么时候使用组件扫描

Spring提供了一种通过@ComponentScan注释显式识别Spring bean候选对象的机制。

如果应用程序是一个Spring引导应用程序,那么包含Spring引导应用程序类的包下的所有包都将被隐式组件扫描覆盖。

SpringBoot的@SpringBootApplication注释暗示了@Configuration、@ComponentScan和@EnableAutoConfiguration注释。

默认情况下,@ComponentScan注释将扫描当前包及其所有子包中的组件。因此,如果您的应用程序没有变化的包结构,那么就不需要显式组件扫描。

在默认包中指定一个@Configuration注释类将告诉Spring扫描类路径中所有jar中的所有类。

5. 如何使用

我们使用@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。这将帮助我们检查组件是否被正确加载。

6. Spring Boot的隐式自动扫描

如前所述,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

7. 使用不带任何属性的@ComponentScan

如果我们有一个不在父包之下的包,或者我们根本没有使用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。

8. 使用带有属性的@ComponentScan

让我们来看看@ComponentScan注释的属性,我们可以用来修改它的行为:

  • basePackages: 获取一个应该为组件扫描的包名称列表
  • basePackageClasses: 获取一个类列表,这些类的包应该被扫描
  • includeFilters: 允许我们指定应该扫描什么类型的组件
  • excludeFilters: 与includeFilters相反。我们可以指定条件,在扫描时根据标准忽略某些组件
  • useDefaultFilters: 如果为真,它可以自动检测用任何原型注释的类。如果为false,属于includeFilters和excludeFilters定义的筛选条件下的组件将被包括

为了演示不同的属性,让我们向包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)

让我们看看如何在组件扫描期间控制加载哪些类

8.1 使用basePackages扫描整个包

我们将在应用程序的主包中创建类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
...

8.2 使用includeFilters扫描组件

让我们看看如何只包含扩展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类的类。

其他可用的过滤器类型有:

  • ANNOTATION: 只将类与特定的原型注释匹配
  • ASPECTJ: 使用ASPECTJ类型模式表达式匹配类
  • ASSIGNABLE_TYPE: 匹配扩展或实现此类或接口的类
  • REGEX: 使用正则表达式匹配包名

在上面的例子中,我们已经用includeFilters修改了我们的ExplicitScan类,以包含扩展Car.class的组件,并且我们正在修改useDefaultFilters = false,以便只应用我们特定的过滤器。

现在,只有现代和特斯拉bean被包括在组件扫描,因为他们扩展了汽车类:

INFO 68628 --- [main] com.example.componentscan.BeanViewer  : hyundai
INFO 68628 --- [main] com.example.componentscan.BeanViewer  : tesla

8.3 使用excludeFilters排除组件

与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
...

9. 让组件扫描尽可能显式

大量的使用@ComponentScan注释会很快导致对如何创建应用程序的规则感到困惑!尽量少使用它,应该尽量使用约定的方式组织包和类

另外,一种好的做法是显式地导入带有@Import注释的@Configuration类,并将@ComponentScan注释添加到该配置类中,以便仅自动扫描该类的包。这样,我们在应用程序的包之间就有了清晰的边界

10. 结论

在本文中,我们了解了Spring组件原型、什么是组件扫描以及如何使用组件扫描,以及我们可以修改其各种属性以获得所需的扫描行为

你可能感兴趣的:(spring,boot,教程,spring,spring,boot)