透彻解析!Spring Boot Starter 功能与应用场景全面指南
当开发人员设置和集成服务时,常常面临耗时的挑战。Starters 通过组织代码简化了这一过程,使其更易于管理。
让我们看看如何创建两个 starters,自动配置其设置并在服务中使用它们。
那么,Spring Boot Starters 到底是什么,它们有什么优势?
Spring Boot Starters 是模块,旨在简化在使用 Spring 的项目中设置和使用库及组件的过程。它们带有配置好的设置和依赖项,显著减少开发时间,并简化依赖管理。
使用 Spring Boot Starter 的好处:
库的集成
Starters 包含所需技术的所有依赖项。例如,
**spring-boot-starter-web**
提供构建 Web 应用程序所需的一切,而**spring-boot-starter-data-jpa**
则帮助进行 JPA 数据库工作。通过将这些 starters 添加到项目中,开发人员可以开始使用所需的技术,而无需担心兼容性问题或版本差异。
专注于业务逻辑
开发人员可以专注于创建业务逻辑,而不是处理基础设施代码。
这种方法加快了开发和功能部署,最终提升了团队的生产力。
使用配置
使用预定义的设置有助于确保项目设置和组织的一致性,使代码更易于维护和推进。此外,它还有助于新团队成员的上手,提供了代码结构和设置。
项目增强
此外,使用包含已知库的 starters 简化了更新依赖项和集成 Spring Boot 版本的过程。
来自 Spring 团队社区的支持也确保了在开发过程中解决任何问题或障碍的可能性。
任务描述
在本文中,我们将解决从 REST 服务和 GraphQL 服务整合数据的问题。这种问题常常出现在微服务架构的项目中,需要将来自不同服务的数据组合在一起。
在微服务环境中,可以为每个集成建立微服务。当集成广泛且有资源进行维护时,这种方法是合理的。然而,在处理单体应用或缺乏多微服务支持资源的情况下,选择 starters 可能更为实用。
选择库 starter 的理由包括:
业务逻辑分离。Starters 促进业务逻辑和集成配置的分离。
遵循 SOLID 原则。将功能拆分为模块符合原则,提高代码的可维护性和可扩展性。
简化设置。Starters 简化了服务配置的过程,减少了所需的配置代码量。
易于使用。通过添加依赖项和配置必要参数,集成服务变得更简单。
我们的场景
让我们通过一个示例来说明解决方案,涉及一个旅游聚合器,收集来自旅游运营商的数据并进行合并。首先,我们将开发两个 starters(tour-operator-one-starter和
tour-operator-two-starter),它们将使用一个共享模块(
common-model),该模块包含基本模型和接口。这些 starter 库将连接到聚合服务(
tour-aggregator`)。
创建 tour-operator-one-starter
Starter 旨在与旅游运营商集成,通过 REST API 获取数据。
所有官方 starters 都使用命名方案
spring-boot-starter-*
,其中*
表示特定类型的应用程序。第三方 starters 不应以spring-boot
开头,因为该前缀保留给 Spring 团队的官方 starters。通常,第三方 starters 以项目名称开头。例如,我的 starter 将命名为
tour-operator-one-spring-boot-starter
。
创建 pom.xml
添加依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.common.model</groupId>
<artifactId>common-model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建 TourOperatorOneProperties
这些是我们将在聚合器中设置的属性,以配置我们的 starter。
@ConfigurationProperties(prefix = "tour-operator.one.service")
public class TourOperatorOneProperties {
private final Boolean enabled;
private final String url;
private final Credentials credentials;
public TourOperatorOneProperties(
Boolean enabled,
String url,
Credentials credentials) {
this.enabled = enabled;
this.url = url;
this.credentials = credentials;
}
// getters
public static class Credentials {
private final String username;
private final String password;
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
// getters
}
}
创建 TourOperatorOneAutoConfiguration
@AutoConfiguration
@ConditionalOnProperty(prefix = "tour-operator.one.service", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(TourOperatorOneProperties.class)
public class TourOperatorOneAutoconfiguration {
private static final Logger log = LoggerFactory.getLogger(TourOperatorOneAutoconfiguration.class);
private final TourOperatorOneProperties properties;
public TourOperatorOneAutoconfiguration(TourOperatorOneProperties properties) {
this.properties = properties;
}
@Bean("operatorOneRestClient")
public RestClient restClient(RestClient.Builder builder) {
log.info("Configuration operatorRestClient: {}", properties);
return builder
.baseUrl(properties.getUrl())
.defaultHeaders(httpHeaders -> {
if (null != properties.getCredentials()) {
httpHeaders.setBasicAuth(
properties.getCredentials().getUsername(),
properties.getCredentials().getPassword());
}
})
.build();
}
@Bean("tourOperatorOneService")
public TourOperatorOneServiceImpl tourOperatorService(TourOperatorOneProperties properties,
@Qualifier("operatorOneRestClient") RestClient restClient) {
log.info("Configuration tourOperatorService: {} and restClient: {}", properties, restClient);
return new TourOperatorOneServiceImpl(restClient);
}
}
@AutoConfiguration
:表示此类是 Spring Boot 自动配置的配置类。@ConditionalOnProperty
:如果属性tour-operator.one.service.enabled
设置为true
,则激活配置。如果属性缺失,配置由于matchIfMissing = true
也会激活。@EnableConfigurationProperties(TourOperatorOneProperties.class)
:为TourOperatorOneProperties
类启用@ConfigurationProperties
注解的支持。
在此示例中,我使用 @ConditionalOnProperty
,但还有许多其他条件注解:
@ConditionalOnBean
— 当指定的 bean 存在于BeanFactory
时生成一个 bean。@ConditionalOnMissingBean
— 如果BeanFactory
中未找到特定 bean,则便于创建一个 bean。@ConditionalOnClass
— 当特定类在类路径中存在时生成一个 bean。@ConditionalOnMissingClass
— 与@ConditionalOnClass
相对。
你应该选择最适合你需求的方式。你可以在 这里 了解更多关于条件注解的信息。
创建 TourOperatorOneServiceImpl
在此类中,我们实现基本接口,并建立从第一个旅游运营商检索数据的主要业务逻辑,并根据公共接口进行标准化。
public class TourOperatorOneServiceImpl implements TourOperatorService {
private final RestClient restClient;
public TourOperatorOneServiceImpl(@Qualifier("operatorOneRestClient") RestClient restClient) {
this.restClient = restClient;
}
@Override
public TourOperatorResponse makeRequest(TourOperatorRequest request) {
var tourRequest = mapToOperatorRequest(request); // 将我们的请求转化为旅游运营商能够理解的请求
var responseList = restClient
.post()
.body(tourRequest)
.retrieve()
.toEntity(new ParameterizedTypeReference<List<TourProposition>>() {
});
return TourOperatorResponse.builder()
.deals(responseList
.getBody()
.stream()
.map(ModelUtils::mapToCommonModel)
.toList())
.build();
}
}
创建自动配置文件
为了注册自动配置,我们创建文件 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
。
com.tour.operator.one.autoconfiguration.TourOperatorOneAutoConfiguration
该文件包含一组配置。在我的代码中,添加 TourOperatorOneAutoConfiguration
。
创建 tour-operator-two-starter
接下来,我们将创建 **tour-operator-two-starter**
,这是一个用于与第二个旅游运营商集成并通过简单的 HTTP 请求从 GraphQL 服务器检索数据的工具包。
让我们继续使用 tour-operator-one-starter
的流程。
创建 pom.xml
添加依赖项。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.common.model</groupId>
<artifactId>common-model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建 TourOperatorTwoProperties
这些是我们将在旅游聚合器中设置的属性,以配置我们的启动器。
@ConfigurationProperties(prefix = "tour-operator.two.service")
public class TourOperatorTwoProperties {
private final Boolean enabled;
private final String url;
private final String apiKey;
public TourOperatorTwoProperties(Boolean enabled, String url, String apiKey) {
this.enabled = enabled;
this.url = url;
this.apiKey = apiKey;
}
// getters
}
创建 TourOperatorTwoAutoConfiguration
@AutoConfiguration
@ConditionalOnProperty(prefix = "tour-operator.two.service", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(TourOperatorTwoProperties.class)
public class TourOperatorTwoAutoconfiguration {
private static final Logger log = LoggerFactory.getLogger(TourOperatorTwoAutoconfiguration.class);
private final TourOperatorTwoProperties properties;
public TourOperatorTwoAutoconfiguration(TourOperatorTwoProperties properties) {
log.info("Configuration with: {}", properties);
this.properties = properties;
}
@Bean("operatorTwoRestClient")
public RestClient restClient(RestClient.Builder builder) {
log.info("Configuration operatorRestClient: {}", properties);
return builder
.baseUrl(properties.getUrl())
.defaultHeaders(httpHeaders -> {
httpHeaders.set("X-Api-Key", properties.getApiKey());
})
.build();
}
@Bean("tourOperatorTwoService")
public TourOperatorTwoServiceImpl tourOperatorService(TourOperatorTwoProperties properties,
@Qualifier("operatorTwoRestClient") RestClient restClient) {
log.info("Configuration tourOperatorService: {} and restClient: {}", properties, restClient);
return new TourOperatorTwoServiceImpl(restClient);
}
}
创建 TourOperatorTwoServiceImpl
从第二个旅游运营商接收数据。
public class TourOperatorTwoServiceImpl implements TourOperatorService {
private static final String QUERY =
"""
query makeTourRequest($request: TourOperatorRequest) {
makeTourRequest(request: $request) {
id
startDate
endDate
price
currency
days
hotel {
hotelName
hotelRating
countryCode
}
}
}
""";
private final RestClient restClient;
public TourOperatorTwoServiceImpl(@Qualifier("operatorTwoRestClient") RestClient restClient) {
this.restClient = restClient;
}
@Override
public TourOperatorResponse makeRequest(TourOperatorRequest request) {
var tourRequest = mapToOperatorRequest(request);
var variables = Map.ofEntries(Map.entry("request", tourRequest));
var requestBody = Map.ofEntries(
Map.entry("query", QUERY),
Map.entry("variables", variables));
var response = restClient
.post()
.body(requestBody)
.retrieve()
.toEntity(QueryResponse.class);
return TourOperatorResponse.builder()
.deals(response.getBody()
.data()
.makeTourRequest()
.stream()
.map(ModelUtils::mapToCommonModel).toList())
.build();
}
}
创建自动配置文件
创建文件 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.tour.operator.two.autoconfiguration.TourOperatorTwoAutoconfiguration
创建和使用聚合服务
聚合服务旨在从旅游运营商收集数据。这涉及到连接启动器、配置参数以及使用共享接口的 Bean。
连接启动器库
在 pom.xml
中包含两个库的依赖项。
<dependencies>
...
<dependency>
<groupId>com.tour.operator</groupId>
<artifactId>tour-operator-one-spring-boot-starter</artifactId>
<version>0.0.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.tour.operator</groupId>
<artifactId>tour-operator-two-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
...
</dependencies>
在 application.yaml
中配置参数
在 application.yaml
中指定必要的数据,如 URL 和连接参数。
spring:
application:
name: tour-aggregator
tour-operator:
one:
service:
enabled: true
url: http://localhost:8090/api/tours
credentials:
username: user123
password: pass123
two:
service:
enabled: true
url: http://localhost:8091/graphql
api-key: 11d1de45-5743-4b58-9e08-f6038fe05c8f
使用服务
我们在 TourServiceImpl
类中使用实现 TourOperatorService
接口的已建立的 Bean。该类概述了从各种旅游运营商检索和聚合数据的过程。
@Service
public class TourServiceImpl implements TourService {
private static final Logger log = LoggerFactory.getLogger(TourServiceImpl.class);
private final List<TourOperatorService> tourOperatorServices;
private final Executor tourOperatorExecutor;
private final Integer responseTimeout;
public TourServiceImpl(List<TourOperatorService> tourOperatorServices,
@Qualifier("tourOperatorTaskExecutor") Executor tourOperatorExecutor,
@Value("${app.response-timeout:5}") Integer responseTimeout) {
this.tourOperatorServices = tourOperatorServices;
this.tourOperatorExecutor = tourOperatorExecutor;
this.responseTimeout = responseTimeout;
}
public List<TourOffer> getTourOffers(@RequestBody TourOperatorRequest request) {
log.info("Send request: {}", request);
var futures = tourOperatorServices.stream()
.map(tourOperator -> CompletableFuture.supplyAsync(() -> tourOperator.makeRequest(request), tourOperatorExecutor)
.orTimeout(responseTimeout, TimeUnit.SECONDS)
.exceptionally(ex -> TourOperatorResponse.builder().deals(List.of()).build())
)
.toList();
var response = futures.stream()
.map(CompletableFuture::join)
.map(TourOperatorResponse::getDeals)
.filter(Objects::nonNull)
.flatMap(List::stream)
.toList();
return response;
}
}
为调用分配资源
为调用分配单独的资源是良好的实践,有助于更好的线程管理和性能优化。
@Configuration
public class ThreadPoolConfig {
private final Integer threadCount;
public ThreadPoolConfig(@Value("${app.thread-count:5}") Integer threadCount) {
this.threadCount = threadCount;
}
@Bean(name = "tourOperatorTaskExecutor")
public Executor tourOperatorTaskExecutor() {
return Executors.newFixedThreadPool(threadCount);
}
}
这段代码确保有效管理异步任务,帮助避免阻塞主线程,从而提高整体系统性能。
结论
通过使用 Spring Boot Starters,大家能够轻松整合多个服务,简化依赖管理和配置。这样的设计让开发者能够专注于业务逻辑,从而加快开发速度。
希望这个示例有助于大家理解 Spring Boot Starters 的构建和应用场景。接下来,大家可以根据自己的需要深入探索这些功能。