创建自己的定制的Spring Boot Starter快速指南


通过一个例子来了解如何实现一个Spring Boot Starter。对于我们正在构建的每个Spring Boot应用程序,我们不希望从头开始实现某些跨领域的问题。相反,我们希望一次性实现这些功能,并根据需要将它们作为组件包含在任何应用程序中。
在Spring Boot中,用于提供此类交叉问题的模块的术语是“starter”。Spring Boot starter的一些示例用例是:

  • 提供可配置和/或默认的日志记录配置,或使其易于登录到中央日志服务器
  • 提供可配置和/或默认的安全配置
  • 提供可配置和/或默认的错误处理策略
  • 为中央消息传递基础设施提供适配器
  • 集成第三方库并使其可配置为与Spring Boot一起使用
  • ...

在本文中,我们将构建一个Spring Boot启动器,它允许Spring Boot应用程序通过虚构的中央消息传递基础架构轻松发送和接收事件。

在我们深入了解创建Spring Boot Starter的细节之前,让我们讨论一些有助于理解Starter工作的关键字。
什么是应用程序上下文?
在Spring应用程序中,应用程序上下文是组成应用程序的对象(或“bean”)的网络。它包含我们的Web控制器,服务,存储库以及我们的应用程序可能需要的任何(通常是无状态的)对象。

什么是配置?
使用注释@Configuration标注的类,扮演添加到应用程序上下文的bean工厂。它可能包含带注释的工厂方法,@Bean其返回值由Spring自动添加到应用程序上下文中。
简而言之,Spring配置为应用程序上下文提供bean。

什么是自动配置?
自动配置是Spring自动发现的@Configuration类。只要该类位于在类路径classpath上,即可自动配置,并将配置的结果添加到应用程序上下文中。自动配置可以是有条件的,使得其激活取决于外部因素,例如具有特定值的特定配置参数。

什么是自动配置模块?
自动配置模块是包含自动配置类的Maven或Gradle模块。这样,我们就可以构建自动为应用程序上下文做贡献的模块,添加某个功能或提供对某个外部库的访问。我们在Spring Boot应用程序中使用它所要做的就是在我们的pom.xml或者包含它的依赖项build.gradle。
Spring Boot团队大量使用此方法将Spring Boot与外部库集成。

什么是Spring Boot Starter?
最后,Spring Boot Starter是一个Maven或Gradle模块,其唯一目的是提供“使用某个功能”“开始”所需的所有依赖项。这通常意味着它是一个单独的pom.xml或build.gradle文件,包含一个或多个自动配置模块的依赖项以及可能需要的任何其他依赖项。
在Spring Boot应用程序中,我们只需要包含此启动器Starter即可使用该功能。

案例
通过一个例子来了解如何实现一个Spring Boot Starter,想象一下,我们正在微服务环境中工作,并希望实现一个允许服务异步通信的启动器。我们正在建造的Starter将提供以下功能:

  • EventPublisher事件发布:允许我们将事件发送到中央消息传递基础结构的bean
  • 一个抽象EventListener事件监听类,可以实现从中央消息传递基础结构订阅某些事件。

请注意,本文中的实现实际上不会连接到中央消息传递基础结构,而是提供虚拟实现。本文的目标是展示如何构建Spring Boot Starter,而不是如何进行消息传递。

设置Gradle构建
由于启动器Starter是跨多个Spring Boot应用程序的交叉问题,它应该存在于自己的代码库中并拥有自己的Maven或Gradle模块。我们将使用Gradle作为选择的构建工具,但它与Maven非常相似。
我们需要声明的依赖基本Spring Boot启动我们的build.gradle文件:

plugins {
  id 'io.spring.dependency-management' version '1.0.8.RELEASE'
  id 'java'
}

dependencyManagement {
  imports {
    mavenBom("org.springframework.boot:spring-boot-dependencies:2.1.7.RELEASE")
  }
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

完整的文件可以在github上找到
要获得与某个Spring Boot版本兼容的基本启动程序版本,我们使用Spring Dependency Management插件来包含该特定版本的BOM(物料清单)。
这样,Gradle在此BOM中查找启动程序的兼容版本(以及Spring Boot需要的任何其他依赖项的版本),我们不必手动声明它。

提供自动配置
提供了一个@Configuration类实现事件发布者:

@Configuration
class EventAutoConfiguration {

  @Bean
  EventPublisher eventPublisher(List<EventListener> listeners){
    return new EventPublisher(listeners);
  }

}

此配置包括的@Bean是我们提供给Starter所需的所有定义。这里是需将EventPublisherbean 添加到应用程序上下文中。
EventPublisher需要知道所有的虚拟实现,EventListeners因此它可以将事件传递给它们,因此我们让Spring注入EventListeners应用程序上下文中所有可用的列表。

要使我们的配置成为自动配置,我们将其列在文件中META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  io.reflectoring.starter.EventAutoConfiguration


Spring Boot会搜索spring.factories它在类路径中找到的所有文件,并加载在其中声明的配置。
有了这个EventAutoConfiguration类,我们现在可以为Spring Boot starter自动激活单点入口。

使其成为可选
允许是否可以禁用Spring Boot starter总是一个好主意。在提供对诸如消息传递服务之类的外部系统的访问时,这尤其重要。例如,该服务在测试环境中不可用,因此我们希望在测试期间关闭该功能。
我们可以使用Spring Boot的条件注释使我们的入口点配置成为可选:

@Configuration
@ConditionalOnProperty(value = "eventstarter.enabled", havingValue = "true")
@ConditionalOnClass(name =
"io.reflectoring.KafkaConnector")
class EventAutoConfiguration {
  ...
}

通过使用ConditionalOnProperty,我们告诉Spring只在属性eventstarter.enabled设置为true的情况下将(以及它声明的所有bean)包含到应用程序上下文中。
@ConditionalOnClass注解告诉Spring:只有在io.reflectoring.KafkaConnector出现类路径classpath时,才能激活自动配置(这仅仅是一个虚拟类向人们展示了使用条件注释)。

使其可配置
对于在多个应用程序中使用的库,例如我们的启动程序,将行为设置为尽可能可行是一个好主意。
想象一下,应用程序只对某些事件感兴趣。为了使每个应用程序可配置,我们可以在application.yml(或application.properties)文件中提供已启用事件的列表:

eventstarter:
  listener:
    enabled-events:
      - foo
      - bar

为了在我们的入门代码中轻松访问这些属性,我们可以提供一个@ConfigurationProperties类

@ConfigurationProperties(prefix = "eventstarter.listener")
@Data
class EventListenerProperties {

 
/**
   * List of event types that will be passed to {@link EventListener}
   * implementations. All other events will be ignored.
   */

  private List<String> enabledEvents = Collections.emptyList();

}

通过使用@EnableConfigurationProperties注释我们的入口点配置来启用EventListenerProperties类:

@Configuration
@EnableConfigurationProperties(EventListenerProperties.class)
class EventAutoConfiguration {
  ...
}

最后,我们可以让Spring将EventListenerPropertiesbean 注入我们需要的任何地方,例如在我们的抽象EventListener类中过滤掉我们不感兴趣的事件:

@RequiredArgsConstructor
public abstract class EventListener {

  private final EventListenerProperties properties;

  public void receive(Event event) {
    if(isEnabled(event) && isSubscribed(event)){
      onEvent(event);
    }
  }

  private boolean isSubscribed(Event event) {
    return event.getType().equals(getSubscribedEventType());
  }

  private boolean isEnabled(Event event) {
    return properties.getEnabledEvents().contains(event.getType());
  }
}

创建IDE友好的配置元数据
使用eventstarter.enabled和eventstarter.listener.enabled-events我们为启动器指定了两个配置参数。如果这些参数让开发人员在配置文件中输入时自动完成,那将是很好的。
Spring Boot提供了一个注释处理器,可以从找到的所有@ConfigurationProperties类中收集有关配置参数的元数据。我们只是将它包含在我们的build.gradle文件中:

dependencies {
  ...
  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

此注解处理器将生成META-INF/spring-configuration-metadata.json包含有关我们EventListenerProperties类中的配置参数的元数据的文件。此元数据包含字段上的Javadoc,因此请务必使Javadoc尽可能清晰。
在IntelliJ中,Spring Assistant插件将读取此元数据并为这些属性提供自动完成功能。

现在还留下eventstarter.enabled问题,它没有在@ConfigurationProperties的列表中。

我们可以通过创建文件META-INF/additional-spring-configuration-metadata.json,手动添加此属性:

{
  "properties": [
    {
     
"name": "eventstarter.enabled",
     
"type": "java.lang.Boolean",
     
"description": "Enables or disables the EventStarter completely."
    }
  ]
}

然后,注释处理器将自动将此文件的内容与自动生成的文件合并,以供IDE工具选取。该文件的格式记录在参考手册中

改善启动时间
对于类路径上的每个自动配置类,Spring Boot必须评估@Conditional...注释中编码的条件,以决定是否加载自动配置及其所需的所有类。根据Spring Boot应用程序中启动器的大小和数量,这可能是非常昂贵的操作并影响启动时间。
还有另一个注释处理器可生成有关所有自动配置条件的元数据。Spring Boot在启动期间读取此元数据,并且可以过滤掉不满足条件的配置,而无需实际检查这些类。
要生成此元数据,我们只需将注释处理器添加到启动器模块:

dependencies {
    ...
    annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor'
}

在构建期间,元数据将生成到META-INF/spring-autoconfigure-metadata.properties文件中,如下所示:

io.reflectoring.starter.EventAutoConfiguration=
io.reflectoring.starter.EventAutoConfiguration.ConditionalOnClass=io.reflectoring.KafkaConnector
io.reflectoring.starter.EventAutoConfiguration.Configuration=

我不确定为什么元数据包含@ConditionalOnClass条件但不包含@ConditionalOnProperty条件。如果您知道原因,请在评论中告诉我。

使用入门
现在启动器已经完成,它已准备好,可以包含在Spring Boot应用程序中了。
在build.gradle文件中添加单个依赖项即可实现包含它:

dependencies {
  ...
  implementation project(':event-starter')
}

在上面的示例中,starter是同一Gradle构建中的模块,因此我们不必像在完全限定的Maven中需要使用来定位标识starter。

我们现在可以使用上面介绍的配置参数配置启动器。希望我们的IDE能够评估我们创建的配置元数据,并为我们自动完成参数名称。

要使用我们的事件启动器,我们现在可以EventPublisher在bean中注入并使用它来发布事件。此外,我们可以创建扩展EventListener类的bean 来接收和处理事件。
GitHub上提供一个工作示例应用程序。