Spring-快速参考指南-三-

176 阅读27分钟

Spring 快速参考指南(三)

原文:Spring Quick Reference Guide

协议:CC BY-NC-SA 4.0

十五、Spring Boot

Spring Boot 消除了与构建 Spring 应用相关的大量样板文件,从而提高了开发人员的工作效率。除了注释扫描等普通的 Spring 特性之外,它还使用了约定和“starter”依赖,极大地简化了应用的开发。

同时,它非常灵活,给了开发人员很大的权限来决定包含什么,并且随着时间的推移,您可以根据需要修改配置。

Spring Boot 简介

Spring Boot 极大地简化了基于 Spring 的应用或微服务的创建。它简化了软件开发人员的生活,通过自动配置一些东西,消除了为每个依赖项指定版本的需要。

使用 Spring Boot,开发人员可以包含许多“起始依赖项”,每个“起始依赖项”本身都包含许多库和项目的自动配置。这极大地改善了启动和添加项目的体验。在许多情况下,Spring Boot 还包括合理的默认配置。随着项目的增长,开发人员可以覆盖默认设置,并以许多不同的方式定制项目。

您可以将 web 项目打包成 WAR 或单个 JAR 文件。如果使用 jar 文件方法(这是推荐的方法),Spring Boot 将使用一个嵌入式 web 容器,如 Tomcat 或 Jetty,并在 JAR 文件中包含所有的依赖项(称为“胖 JAR”)。

创建新项目

启动 Spring Boot 项目有几种方式:

  1. 转到 Spring Initializr1网站,在那里创建一个项目模板。还有像 Spring Tool Suite 这样的工具,它们利用了 IDE 中的 Spring Initializr。

  2. 创建自己的基于 Maven 的项目。

  3. 创建自己的基于 Gradle 的项目。

  4. 使用 Spring Boot 命令行界面。

您可以通过在浏览器中进入 https://start.spring.io 或者在命令行中使用 Spring Boot CLI,轻松创建一个包含任意数量 Spring Boot 启动器的新项目。它提供了一些选项,比如是使用 Maven 还是 Gradle 构建,使用什么测试框架,使用哪种语言(Java、Groovy 或 Kotlin)等等。然后创建构建文件,主应用类,一个基本 Spring Boot 应用的测试。

Spring Boot 命令行界面

您可以使用 SDKMAN: 2 轻松安装 Spring Boot CLI

$ sdk install springboot
$ spring --version
Spring Boot v2.3.0.RELEASE

首先,使用spring init --list查看所有可用的依赖项:

img/498572_1_En_15_Fig1_HTML.jpg

图 15-1

Spring 初始化列表:输出

$ spring init –list

然后,一旦您理解了您需要哪些依赖项,您就可以使用spring init命令来创建您的项目;例如:

img/498572_1_En_15_Fig2_HTML.jpg

图 15-2

新 Spring Boot 项目文件结构

$ spring init –d actuator,web,data-jpa,security my-project
Using service at https://start.spring.io
Project extracted to '/Users/developer/example/my-project'

回弹应用

@SpringBootApplication注释告诉 Spring 许多事情:

  1. 使用自动配置。

  2. 使用组件扫描。扫描所有的包(从类的包和所有的子包开始),寻找用 Spring 注释标注的类,比如@Component

  3. 这个类是一个基于 Java 的配置类(与@Configuration相同),所以您可以在这里使用返回 bean 的方法上的@Bean注释来定义 bean。

  4. 它将该类标记为应用的主配置类(与@SpringBootConfiguration相同),并允许 Spring Boot 测试找到它(标记为@ SpringBootTest,这将在后面介绍)。

每个应用只能使用一个@SpringBootApplication

| `@Configuration` | 将该类标记为可以定义 beans 的 Java 配置类。 | 可以有任何数字。 | | `@SpringBootConfiguration` | 与@Configuration 相同,并且可以通过 Spring Boot 测试发现。 | 每个应用只能有一个。 | | `@SpringBootApplication` | 与`@SpringBootConfiguration`相同,此外它还支持自动配置和组件扫描。 | 每个应用只能有一个。 |

自动配置

Spring Boot 自动配置考虑应用的运行时,并根据许多因素(如类路径上的库)自动配置应用。

它遵循的格言是:“如果每个人都必须做,那么为什么每个人都必须做?”

例如,要创建一个典型的 MVC web 应用,您过去需要添加一个配置类和多个依赖项,并配置一个 Tomcat 容器。使用 Spring Boot,您需要添加的只是一个依赖项和一个控制器类,它会自动添加一个嵌入式 Tomcat 实例。

配置文件可以定义为属性文件、yaml 和其他方式。首先,在“src/main/resources”下创建一个名为“application.properties”的文件,并添加以下内容:

server.port=8003
app.name=Humble Code

这会将服务器设置为在端口 8003 上运行,并设置一个用户定义的属性 app.name,该属性可以是任何值。

启用自动配置

Spring Boot 背后的大部分“魔力”是自动配置——然而,一旦你知道自动配置是如何工作的,你就可以揭开它的神秘面纱。

通过在你的一个@Configuration类中包含@EnableAutoConfiguration@SpringBootApplication注释,你启动了 Spring 的自动配置。这使得 Spring 能够为您的整个项目自动配置。它可以做从创建嵌入式数据库到启动 Tomcat 实例的事情。

为了定位自动配置类(和其他东西),Spring Boot 检查您发布的 jar 中是否存在一个META-INF/spring.factories文件。该文件应该在EnableAutoConfiguration键下列出您的配置类,如下例所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.libx.autoconfigure.LibXAutoConfiguration,\
com.example.libx.autoconfigure.LibXWebAutoConfiguration

然后,这些自动配置类使用注释来描述它们应用的条件和规则。例如,看看下面名为MyReactiveRepositoriesAutoConfiguration的示例类声明:

@Configuration
@ConditionalOnClass({ MyClient.class, ReactiveMyRepository.class })
@ConditionalOnMissingBean({
ReactiveMyRepositoryFactoryBean.class,
ReactiveMyRepositoryConfigurationExtension.class })
@ConditionalOnProperty(
prefix = "spring.data.mydb.reactive-repositories",
name = "enabled", havingValue = "true", matchIfMissing = true)
@Import(MyReactiveRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter(MyReactiveDataAutoConfiguration.class)
public class

MyReactiveRepositoriesAutoConfiguration {

我们将一次检查一个注释。

  1. @Configuration–声明这个类是一个 Spring 配置类。

  2. @ConditionalOnClass–告诉 Spring 仅当这些类在运行时类路径中时才使用这个配置。

  3. @ConditionalOnMissingBean–仅当 ApplicationContext 中不存在这些类型的 Beans 时,此配置才有效。

  4. @ConditionalOnProperty–属性spring.data.mydb.reactive-repositories.enabled必须设置为 true 或 missing ( matchIfMissing)才能调用此配置。

  5. @Import–导入将由 Spring 处理的另一个配置类。

  6. @AutoConfigureAfter–告诉 Spring 只在MyReactiveDataAutoConfiguration已经被处理之后才处理这个配置。

不包括自动配置

您可以通过使用EnableAutoConfiguration注释的 exclude 属性来排除某些自动配置类,例如:

@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Application {
/* main method */
}

这将停止对DataSourceAutoConfiguration类的评估,而不管它上面的其他条件。

您还可以通过属性排除自动配置类,例如,在 yaml:

spring:
  autoconfigure.exclude:
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

或者在您的 application.properties 文件中:

spring.autoconfigure.exclude=\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

应用属性

默认情况下,Spring Boot 将从名为application.properties(对于标准属性)或application.yml(对于 YAML 格式的属性)的文件,以及那些为每个活动概要文件附加了 -PROFILE_NAME 的文件中加载属性。

例如,如果您激活了测试概要文件,Spring 将按照这个顺序加载以下内容(后面的属性可以覆盖前面的属性):

  1. 应用.属性

  2. 应用程式. yml

  3. 应用测试属性

  4. 应用测试. yml

覆盖属性

环境变量也可以覆盖任何属性。Spring 自动将大写下划线语法转换为属性语法。例如,名为SPRING_PROFILES_ACTIVE的环境变量将覆盖属性文件中的spring.profiles.active

优先级顺序是(列表中较早的条目优先于较晚的条目)

  1. 命令行参数

  2. 来自SPRING_APPLICATION_JSON的属性(嵌入在环境变量或系统属性中的内联 JSON)

  3. web.xml中的 Servlet 上下文参数

  4. JNDI 属性来自java:comp/env

  5. Java 系统属性(来自 System.getProperties())

  6. 环境变量

  7. application-{profile}.properties文件或类似的 YAML 文件加载的特定于配置文件的属性

  8. application.properties文件或application.yml加载的属性

自动 Spring AOP

如果包含在类路径中,Spring AOP 将被自动配置。如果您设置了spring.aop.auto=false,配置将不会被激活。

Spring Boot 执行器

Spring Boot 执行器是 Spring Boot 的一个子项目,它为应用增加了几个生产级服务,将在下一章介绍。

Spring Boot 测试

Spring Boot 为测试提供了全面的内置支持。例如,用@RunWith(SpringRunner.class)@SpringBootTest注释一个 JUnit 4 测试类,我们可以在整个应用如下运行的情况下运行集成测试:

表 15-1

web 环境枚举

| `DEFINED_PORT` | 使用配置的任何端口创建 web 应用上下文。 | | `MOCK (the default)` | 如果 servlet APIs 位于类路径上,则使用模拟 servlet 环境创建 WebApplicationContext,如果 Spring WebFlux 位于类路径上,则创建 ReactiveWebApplicationContext,否则创建常规 ApplicationContext。 | | `RANDOM_PORT` | 通常与测试中的`@LocalServerPort`注入字段(设置为 Spring 分配的 HTTP 端口)一起使用。 | | `NONE` | 创建一个 ApplicationContext 并将 spring application . setwebapplicationtype(web applicationtype)设置为`WebApplicationType.NONE`,这对于在没有 web 逻辑的情况下测试服务器端逻辑非常有用。 |
  1. 在 JUnit 4 测试中,使用@RunWith( SpringRunner.class )来启用 Spring 测试对测试中自动连接字段的支持(对于 JUnit 5 测试,使用@ExtendWith(SpringExtension.class))。

  2. 使用@ SpringBootTest告诉 Spring 在当前包或更高的包中查找用@ SpringBootConfiguration或@ SpringBootApplication注释的类,并从该配置开始启动 ApplicationContext。使用"webEnvironment = WebEnvironment.RANDOM_PORT"指定每次运行测试时,Spring Boot 应用应该随机选择一个端口在本地运行。这有助于避免与任何其他正在运行的应用发生冲突。这些是可能的值:

@RunWith(SpringRunner.class)                                \\1
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)\\2
public class BootApplicationTests {
  @Autowired
  private TestRestTemplate testRestTemplate;

  @Test
  public void testFreeMarkerTemplate() {
    ResponseEntity<String> entity = testRestTemplate
          .getForEntity("/", String.class);
    assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(entity.getBody()).contains("Welcome to");
  }
}

Listing 15-1BootApplicationTests.java

这个简单的测试启动了我们的 Spring Boot 应用,并验证了根页面返回 HTTP OK (200)状态代码,并且正文包含文本“Welcome to”。

TestRestTemplate 是自动创建的,并被设置为向正在运行的 Spring Boot 应用发出 HTTP 请求。

Spring 还提供了@ WebMvcTest注释,用于创建一个只实例化和测试 MVC 控制器的测试。所需的任何其他 beans 都需要通过您提供的配置或者作为使用@ MockBean注释的模拟来提供,例如:

  1. 为了只测试一个控制器(CourseController)),我们可以使用@WebMvcTest并指定一个类(您可以指定多个控制器)。Spring 将自动配置 Spring MVC 基础设施,刚好足以测试这个控制器。实际上没有网络 I/O 发生,所以测试可以非常快速地运行。

  2. 自动创建一个 MockMvc 的实例,该实例可用于直接测试任何使用 Spring MVC 的控制器(如第七章所述)。

  3. @MockBean标记一个字段告诉 spring-test 基础设施创建该接口的一个模拟实例(使用 mockito)(在本例中为CourseService)。

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(CourseController.class) //1
public class WebMockTest {

        @Autowired
        private MockMvc mockMvc;       //2

        @MockBean
        private CourseService service; //3

        // tests...
}

Footnotes 1

https://start.spring.io/

  2

https://sdkman.io/

 

十六、Spring Boot 执行器

Spring Boot 执行器是 Spring Boot 的子项目,它为应用添加了几个生产级服务,如健康端点和指标。

默认端点

Spring Boot 执行器有助于实现 web 服务的常见非功能需求,如健康检查、配置、审计、调试和度量。

默认情况下,Actuator 提供许多端点(默认情况下映射到/actuator/*)。可以通过 HTTP 或 JMX 协议访问它们。例如,您可以检查默认运行状况检查端点,如下所示:

$ curl localhost:8080/actuator/health
{"status":"UP"}

每个端点可以单独启用或禁用,并且通常以 JSON 响应。以下是所有默认端点:

  • auditevents–显示当前应用的审计事件信息。需要 AuditEventRepository bean

  • beans–显示应用中所有 Spring beans 的完整列表

  • caches–显示可用的缓存

  • conditions–显示在配置和自动配置类上评估的条件,以及它们匹配或不匹配的原因

  • configprops–显示所有@ConfigurationProperties的列表

  • env–显示 Spring 环境的属性

  • flyway–显示已经应用的任何 Flyway 数据库迁移。需要一个或多个 Flyway beans

  • health –显示应用健康信息

  • httptrace–显示 HTTP 跟踪信息(默认情况下,最后 100 次 HTTP 请求-响应交换)。需要一个 HttpTraceRepository bean

  • info–显示任意应用信息

  • integrationgraph–显示 Spring 积分图。需要依赖 spring-integration-core

  • loggers–显示和修改应用中记录器的配置

  • liquibase–显示已经应用的任何 Liquibase 数据库迁移。需要一个或多个 Liquibase beans

  • metrics–显示当前应用的指标信息

  • mappings–显示所有@ RequestMapping路径的整理列表

  • scheduledtasks–显示应用中的计划任务

  • sessions–允许从 Spring 会话支持的会话存储中检索和删除用户会话。需要使用 Spring 会话的基于 Servlet 的 web 应用

  • shutdown–让应用正常关闭(默认禁用;有关如何启用它,请参见下一节)

  • threaddump–执行线程转储

配置执行器

要启用(或禁用)端点,请使用management.endpoint.[name].enabled属性,例如:

management.endpoint.shutdown.enabled=true

如果希望在不同的端口或路径上为管理端点提供服务,可以使用management.server.port and management.endpoints.web.base-path进行设置,例如:

management.endpoints.web.base-path=/manage
management.server.port=8070

请记住,这将启动一个新的嵌入式容器(如 Tomcat)来支持它。

公开端点

默认情况下,大多数端点都不公开。您可以指定哪些端点应该通过 HTTP (web)公开,哪些不应该使用management.endpoints.web.exposure.include,例如(application.properties 中的*):*

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans

您还可以确定通过 JMX 公开哪些端点,例如,公开除了bean之外的所有端点:

management.endpoints.jmx.exposure.include=*
management.endpoints.jmx.exposure.exclude=beans
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=beans

信息

您可以通过创建一个实现了InfoContributor接口的 Spring bean 来添加这个端点公开的定制信息。例如,您可以使用下面的InfoContributor,它公开了来自SimpleCourseRepository的课程数:

package com.apress.springquick.springbootmvc;

import com.apress.spring_quick.jpa.simple.SimpleCourseRepository;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
/**
* Contributes to Actuator's /info end-point with additional info
* (count of courses in this case).
 */
@Component
public class CustomInfoContributor implements InfoContributor {

    final SimpleCourseRepository courseRepository;

    public CustomInfoContributor(SimpleCourseRepository courseRepository) {
        this.courseRepository = courseRepository;
    }

    @Override
    public void contribute(final Info.Builder builder) {
        builder.withDetail("count_courses", courseRepository.count());
    }
}

Listing 16-1CustomInfoContributor.java

默认情况下,Info 作为 JSON 提供,所以这个例子将从位于/actuator/info端点的 HTTP get 产生类似于{"count_courses": 0}的内容。

健康

自动配置的健康指示器

以下健康指示器由 Spring Boot 在适当的时候自动配置(例如,CassandraHealthIndicator 仅在配置了 Cassandra 数据库的情况下创建):

|

名称

|

描述

| | --- | --- | | CassandraHealthIndicator | 检查 Cassandra 数据库是否启动。 | | CouchbaseHealthIndicator | 检查 Couchbase 集群是否启动。 | | DataSourceHealthIndicator | 检查是否可以获得到数据源的连接。 | | DiskSpaceHealthIndicator | 检查磁盘空间是否不足。 | | ElasticSearchRestHealthIndicator | 检查 Elasticsearch 集群是否启动。 | | HazelcastHealthIndicator | 检查 Hazelcast 服务器是否启动。 | | InfluxDbHealthIndicator | 检查 InfluxDB 服务器是否已启动。 | | JmsHealthIndicator | 检查 JMS 代理是否已启动。 | | LdapHealthIndicator | 检查 LDAP 服务器是否启动。 | | MailHealthIndicator | 检查邮件服务器是否启动。 | | MongoHealthIndicator | 检查 Mongo 数据库是否启动。 | | Neo4jHealthIndicator | 检查 Neo4j 数据库是否启动。 | | PingHealthIndicator | 总是用 UP 来回应。 | | RabbitHealthIndicator | 检查 Rabbit 服务器是否启动。 | | RedisHealthIndicator | 检查 Redis 服务器是否启动。 | | SolrHealthIndicator | 检查 Solr 服务器是否启动。 |

定制健康

包含的健康指标很多,可以自己写。通过实现HealthIndicator接口创建自己的接口,例如:

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
  @Override
  public Health health() {
   int errorCode = check(); // perform some specific health check
   if (errorCode != 0) {
     return Health.down().withDetail("Error Code", errorCode).build();
   }
   return Health.up().build();
  }
}

Listing 16-2MyHealthIndicator.class

可能的健康状态如下:

|

状态

|

养生法

|

意为

| | --- | --- | --- | | 起来 | 向上( ) | 一切正常。 | | 停止服务 | 服务中断( ) | 不可用,也许是故意的。 | | 未知的 | 未知( ) | 状态未知。 | | 向下 | 向下( ) | 有东西坏了。 |

即使有一个健康指标不正常,其状态也会传播到最高级别。换句话说,只有当一切正常时,健康端点才会返回{"status":"UP"}

韵律学

Spring Boot 执行器为千分尺、 1 提供依赖管理和自动配置,是支持众多监控系统的应用度量门面。这些系统对于深入了解您的应用在运行时的行为非常有用,无论是在登台环境中还是在生产环境中,并使您能够尽早发现性能问题。

千分尺支持许多外部系统,包括

  • 应用

  • 阿特拉斯

  • Datadog

  • 动态跟踪

  • 弹性的

  • 神经中枢

  • 石墨

  • 潮湿的

  • 流入

  • 管理扩展

  • KairosDB

  • 新遗迹

  • 普罗米修斯

  • 信号 Fx

  • 简单(内存中)

  • 堆栈驱动程序

  • 状态:状态

  • 波阵面

哪些指标可用取决于应用的类型。

导航至/actuator/metrics显示可用血糖仪名称列表。通过以选择器的形式提供特定血糖仪的名称,例如/actuator/metrics/jvm.memory.max,您可以深入查看关于该血糖仪的信息。

您还可以添加自定义指标。将MeterRegistry注入到您的 bean 中,然后使用它来添加您的度量,例如:

import io.micrometer.core.instrument.MeterRegistry;

class MyBean {
  private final List<String> nameList = new ArrayList<>();
  MyBean(final MeterRegistry registry) {
  registry.gaugeCollectionSize("nameList.size",
       Tags.empty(), nameList);
  }
  //...code
}

Listing 16-3MyBean.java

这个例子创建了一个标尺(一个可以上升或下降的值),用来跟踪nameList集合的大小。标签可用于标记您的过滤指标。

审计

Spring Boot 执行器有一个灵活的审计框架,将事件发布到一个AuditEventRepository。默认情况下,Spring Security 自动发布身份验证事件。例如,这对于报告和实现基于身份验证失败的锁定策略非常有用。

记录

/actuator/loggers端点提供关于系统日志配置的信息(在 JSON 中),并提供在运行时修改配置的方法。对于每个记录器,它提供了configuredLeveleffectiveLevel。您可以修改configuredLevel(这通常也会改变effectiveLevel)。

您可以通过在/ loggers/{name}端点使用 HTTP GET 来请求信息或特定的记录器,例如:

$ curl http://localhost:8081/actuator/loggers/com.apress
{"configuredLevel":null,"effectiveLevel":"INFO"}

img/498572_1_En_16_Figa_HTML.jpg确保你已经将致动器配置为暴露(如本章前面所解释的)并且将 Spring Security 配置为允许你访问该端点(参见第九章)。

然后,您可以使用带有 JSON 的 HTTP POST 在运行时修改记录器的级别,确保正确设置内容类型头。

$ curl -H "Content-Type: application/json" -d "{\"configuredLevel\":\"DEBUG\"}" http://localhost:8081/actuator/loggers/com.apress

然后,您可以再次请求该特定记录器的配置,以确保您的更改得到反映,例如:

$ curl http://localhost:8081/actuator/loggers/com.apress
{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

Footnotes 1

https://micrometer.io/

 

十七、Spring Webflux

Spring WebFlux 类似于 Spring MVC,但允许您使用反应式流,并且是异步和非阻塞的,如果使用正确,可以让您的应用具有更好的性能。

通过 WebFlux,我们可以使用 HTTP 或 WebSocket 连接快速创建异步、非阻塞和事件驱动的应用。Spring 在它的许多 API 中使用自己的 Reactive Streams 实现,Reactor 1 (带有Flux<T>Mono<T>)。您可以在应用中使用另一种实现,如 RxJava,但 project Reactor 具有与 WebFlux 的最佳集成。

默认情况下,Spring WebFlux 应用使用 Netty, 2 的嵌入式实例,这是一个异步事件驱动的应用框架,尽管您可以将其配置为使用嵌入式 Tomcat、Jetty 或 Undertow。

在这一章中,我们将看看如何使用 Spring Boot、WebFlux 和 Reactor 以及 MongoDB 持久层来实现一个完整的项目。

使用 Spring WebFlux,我们可以非常容易地创建一个非阻塞的异步应用,它支持 MongoDB、Redis 或 Cassandra 数据库或任何实现了 R2DBC 驱动程序的关系数据库。

入门指南

为了本章的目的,我们将创建一个基于 Java 的梯度构建的 Spring Boot 项目。

Spring Boot 是高度可定制的,你可以为你的项目添加任何你想要的“启动器”(网络、邮件、freemarker、安全等)。).这使得它尽可能的轻便。

我们将创建一个基于 WebFlux 的项目,它使用 Spring 的 Reactor 项目以及 MongoDB 3 来创建一个完全反应式的 web 应用。

这个项目的代码可以在 adamldavis/humblecode 的 GitHub 上获得。 4

Gradle Plugin

Spring Boot 的基本 Gradle build 如下所示:

  1. 您可能注意到的第一件事是缺少指定的版本;Spring Boot 为你提供这些,并确保一切都是兼容的。

  2. 我们为构建工件指定 groupId 和版本。我们还将 Java 源代码版本指定为 11。您不需要指定主类。这是由 Spring Boot 通过注释确定的。

  3. 我们包含了“webflux”启动器来启用 Spring 的 WebFlux。

  4. 我们在这里包含 lombok 项目只是为了简化模型类。Lombok 提供注释,并根据每个类使用的注释自动生成样板代码,如 getters 和 setters。

  5. 这里我们包括了 spring-data starter,用于将反应式 MongoDB 与反应器集成在一起。

  6. 最后,我们包括“spring-boot-starter-test”和“reactor-test”来使用 spring 提供的测试支持。

plugins {                                                       //1
  id 'org.springframework.boot' version '2.3.0.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
}
group = 'com.humblecode'                                             //2
version = '0.0.2-SNAPSHOT'
sourceCompatibility = 11

repositories {
  mavenCentral()
}
dependencies {
  compile('org.springframework.boot:spring-boot-starter-webflux')     //3

  compileOnly('org.projectlombok:lombok')                   //4
  compile(
'org.springframework.boot:spring-boot-starter-data-mongodb-reactive') //5
  testCompile('org.springframework.boot:spring-boot-starter-test')
  testCompile('io.projectreactor:reactor-test')             //6
}

请记住,为了使后端完全反应性,我们与数据库的集成需要异步。这不是每种类型的数据库都能做到的。在这种情况下,我们使用支持异步操作的 MongoDB。

截至本文撰写之时,Spring Data 为 Redis、MongoDB 和 Cassandra 提供了直接的反应式集成。要做到这一点,只需在“starter”编译依赖项中为您想要的数据库切换“mongodb”即可。Spring Data 还为关系数据库提供了对 R2DBC 的支持(详见第六章)。

任务

Spring Boot 插件为构建添加了几个任务。

要运行该项目,请运行“gradle bootRun”(默认情况下在端口 8080 上运行)。查看命令行输出,了解有用的信息,比如应用运行在哪个端口上。

当您准备好部署时,运行"gradle bootRepackage”,这将构建一个胖 jar,在一个 jar 中包含运行整个应用所需的一切。

spring boot 应用

主类是通过用@SpringBootApplication标注来指定的。在com.example.demo包中创建一个名为DemoApplication.java的文件,并放入以下内容:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
  @Bean
  public Service sampleService() {
    return new SampleService(); } //2
}

稍后,我们可以添加自己的配置类来更好地配置应用中的安全性等内容。例如,下面是 SecurityConfig 类的开头,它将在我们的应用中启用 Spring Security 性:

@EnableWebFluxSecurity
public class SecurityConfig

稍后,我们将探索如何为 WebFlux 项目增加安全性。

我们的领域模型

在这一节中,我们将实现一个非常简单的网站,使用 RESTful API 进行在线学习。每门课程都有一个价格(以美分计)、一个名称和一个部分列表。

我们将使用以下领域模型课程类定义:

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.*;
@Data //1
@AllArgsConstructor
@Document //2
public class Course {
  @Id UUID id = UUID.randomUUID(); //3

  public String name;
  public long price = 2000; // $20.00 is default price

  public final List<Segment> segments = new ArrayList<>();

  public Course(String name) {this.name = name;}

  public void setSegments(List<Segment> segments) {
    this.segments.clear();
    this.segments.addAll(segments);
  }
  // custom toString method

}

  1. 前两个注释是 Lombok 注释。告诉 Lombok 为每个字段添加 getters 和 setters、equals 和 hashCode 方法、一个构造函数和一个 toString 方法。 5

  2. @Document注释是 spring-data-mongodb 注释,声明这个类代表一个 mongodb 文档。

  3. @Id 注释表示该文档的 Id 属性。

安装 mongodb 后,可以使用以下命令启动它(在基于 Unix 的系统上):

mongod –dbpath data/ --fork --logpath ∼/mongodb/logs/mongodb.log

反应免疫储存

首先,我们需要创建一个到后端数据库的接口,在本例中是 MongoDB。

使用我们包含的spring-boot-starter-data-mongodb-reactive依赖项,我们可以简单地创建一个扩展ReactiveMongoRepository<T,ID>的新接口,并且(除了这个接口上已经有的方法之外)Spring 将生成代码,支持我们使用标准命名方案定义的任何方法(正如我们在第六章中了解到的)。通过返回反应器类,像Flux<T>Mono<T>,这些方法将自动反应。

例如,我们可以创建一个课程存储库:

  1. 第一个泛型类型是这个存储库存储的类型(课程),第二个是课程 ID 的类型。

  2. 该方法查找名称与给定搜索字符串匹配的所有球场,并返回一个 Flux 。

  3. 该方法查找具有给定名称的所有课程。如果我们确信名字是唯一的,我们可以使用Mono<Course> findByName(String name)

public interface CourseRepository extends
    ReactiveMongoRepository<Course, UUID> {            //1
  Flux<Course> findAllByNameLike(String searchString); //2
  Flux<Course> findAllByName(String name);             //3
}

简单地通过扩展ReactiveMongoRepository<T,ID>接口,我们的库将拥有大量有用的方法,如 findById、insert 和保存所有返回的反应器类型(Mono <T>或 Flux <T>)。

控制器

接下来,我们需要制作一个基本的控制器来呈现我们的视图模板。

用@ Controller注释一个类以创建一个 web 控制器,例如:

@Controller
public class WebController {
  @GetMapping("/hello")
  public String hello() { return "home"; }
}

由于前面的方法返回字符串“home ”,它将呈现相应的视图模板。

GetMapping 注释等同于使用@RequestMapping(path="/hello", method = RequestMethod.GET)。它还没有反应,我们将在本章的后面补充。

默认情况下,Spring WebFlux 使用嵌入式 Netty 实例。使用嵌入式容器意味着容器只是另一个“bean ”,这使得配置更加容易。可以使用application.properties和其他应用配置文件进行配置。

接下来,我们想向我们的存储库添加一些初始数据,这样就有东西可看了。我们可以通过添加一个用@PostConstruct注释的方法来实现这一点,该方法只在计数为零时向 courseRepository 添加数据:

@PostConstruct
public void setup() {
  courseRepository.count().blockOptional().filter(count -> count == 0)
  .ifPresent(it ->
    Flux.just(
      new Course("Beginning Java"),
      new Course("Advanced Java"),
      new Course("Reactive Streams in Java"))
    .doOnNext(c -> System.out.println(c.toString()))
    .flatMap(courseRepository::save)
    .subscribeOn(Schedulers.single())
    .subscribe() // need to do this to actually execute save
  );
}

这里的代码混合使用了 Java 8 的Optional<T>和 Reactor。请注意,我们必须调用 Flux 上的 subscribe,否则它永远不会执行。我们在这里通过调用不带参数的 subscribe()来实现这一点。由于 count()返回一个Mono<Long>,我们调用blockOptional(),它将阻塞(等待单声道完成)然后使用给定值;如果是零,那么我们将三个课程对象保存到courseRepository。关于使用 Flux 和 Mono 的复习,请参见第十二章。

查看模板

在任何 Spring Boot 项目中,我们可以使用许多视图模板渲染器中的一个。在这种情况下,我们将 freemarker spring starter 包含到依赖项下的构建文件中:

compile('org.springframework.boot:spring-boot-starter-freemarker')

我们将模板放在src/main/resources/templates下。这里是文件的重要部分,home.ftl:

<div class="page-header">
    <h1>Welcome to ${applicationName}!</h1>
</div>
<article id="content" class="jumbotron center"></article>
<script type="application/javascript">
jQuery(document).ready(HC.loadCourses);
</script>

这将调用相应的 JavaScript 从我们的 RestController 获取课程列表。loadCourses 函数的定义如下:

  1. 首先,我们调用我们的 RESTful API,我们将在后面定义它。

  2. 由于我们使用的是 jQuery,它会自动确定响应是 JSON 并解析返回的数据。

  3. 使用 forEach,我们构建一个 HTML 列表来显示每门课程,并提供一个链接来加载每门课程。

  4. 我们更新 DOM 以包含我们构建的列表。

  5. 这里我们指定了错误处理函数,以防 HTTP 请求出错。

jQuery.ajax({method: 'get', url: '/api/courses'}).done( //1
function(data) {
  var list = data;                                      //2
  var ul = jQuery('<ul class="courses btn-group"></ul>');
  list.forEach((crs) => {                               //3
    ul.append('<li class="btn-link" onclick="HC.loadCourse(\''+
    crs.id+'\'); return false">'
    + crs.name + ': <i>' + crs.price + '</i></li>')
  });
  jQuery('#content').html(ul);                          //4
}
).fail( errorHandler );                                 //5

尽管我们在这里使用 jQuery,但是我们也可以选择任何 JavaScript 库/框架。对于 Spring Boot,JavaScript 文件应该存储在 src/main/resources/static/js。

约定接口规范

默认情况下,Spring 将来自@ RestController的数据编码到 JSON 中,因此相应的CourseController是这样定义的:

@RestController
public class CourseController {
      final CourseRepository courseRepository;

      public CourseControl(CourseRepository courseRepository) {
            this.courseRepository = courseRepository;
      }

      @GetMapping("/api/courses")
      public Flux<Course> getCourses() {
            return courseRepository.findAll();
      }

      @GetMapping("/api/courses/{id}")
      public Mono<Course> getCourse(@PathVariable("id") String id) {
            return courseRepository.findById(UUID.fromString(id));
      }
}

Listing 17-1CourseController.java

注意我们如何直接从一个RestController返回像通量这样的反应器数据类型,因为我们使用的是 WebFlux。这意味着每个 HTTP 请求都是非阻塞的,并使用 Reactor 来确定在哪些线程上运行操作。

注意,在这个例子中,我们直接从控制器调用存储库。在生产系统中,最佳实践是在控制器和存储库之间添加一个“服务”层来保存业务逻辑。

现在我们有了阅读课程的能力,但我们还需要保存和更新课程的能力。

因为我们正在制作一个 RESTful API,所以我们使用@ PostMapping来处理保存新实体的 HTTP POST,使用@ PutMapping来处理更新的 PUT。

下面是保存方法:

@PostMapping(value = "/api/courses",
      consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<Course> saveCourse(@RequestBody Map<String,Object> body) {
      Course

course = new Course((String) body.get("name"));
      course.price = Long.parseLong(body.get("price").toString());

      return courseRepository.insert(course);
}

注意,insert 方法返回一个反应器 Mono;您可能还记得,Mono 只能返回零个或一个实例,或者失败并出现错误。

下面是更新方法:

@PutMapping(value = "/api/courses/{id}",
      consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<Course> updateCourse(@PathVariable("id") String id,
                                 @RequestBody Map<String,Object> body) {

  Mono<Course> courseMono = courseRepository.findById(UUID.fromString(id));

  return courseMono.flatMap(course -> {
      if (body.containsKey("price")) course.price =
      Long.parseLong(body.get("price").toString());
      if (body.containsKey("name")) course.name =
            (String) body.get("name");
      return courseRepository.save(course);
    });
}

注意我们在这里如何使用flatMap来更新课程并返回 save 方法的结果,该方法也返回单声道。如果我们使用 map,返回类型将是 Mono<Mono<Course>>。通过使用flatMap,我们将它“展平”为我们想要的返回类型Mono<Course>

关于反应器的更多信息,参见第十二章。

进一步配置

在实际的应用中,我们很可能想要覆盖应用的许多默认配置。例如,我们希望实现自定义的错误处理和安全性。

首先,为了定制 WebFlux,我们添加了一个扩展WebFluxConfigurationSupport的类(这里它被命名为 WebFluxConfig,但是它可以被命名为任何东西):

@EnableWebFlux
public class WebFluxConfig extends WebFluxConfigurationSupport {

  @Override
  public WebExceptionHandler responseStatusExceptionHandler() {
    return (exchange, ex) -> Mono.create(callback -> {
            exchange.getResponse().setStatusCode(HttpStatus.I_AM_A_TEAPOT);
      System.err.println(ex.getMessage());
      callback.success(null);
    });
  }
}

这里我们覆盖了responseStatusExceptionHandler来设置状态代码为418(我是茶壶 6 ),这是一个实际存在的 HTTP 状态代码(只是为了演示)。您可以重写许多方法来提供自己的自定义逻辑。

最后,没有某种形式的安全性,任何应用都是不完整的。首先,确保将 spring-security 依赖项添加到您的构建文件中:

compile('org.springframework.boot:spring-boot-starter-security')

接下来,添加一个类并用@EnableWebFluxSecurity对其进行注释,并如下定义 beans:

  1. 这个注释告诉 Spring Security 保护您的 WebFlux 应用。

  2. 我们使用 ant 模式定义了允许所有用户使用的路径,其中“**”表示任何一个或多个目录。这使得每个人都可以访问主页和静态文件。

  3. 在这里,我们确保用户必须登录才能访问“/user/”路径下的任何路径。

  4. 这一行将 UserRepository 中的所有用户转换成一个列表。然后将它传递给 MapReactiveUserDetailsService,该服务为用户提供 Spring Security 性。

  5. 您必须定义一个密码编码。这里我们定义一个明文编码只是为了演示的目的。在实际系统中,您应该使用 StandardPasswordEncoder 或 BCryptPasswordEncoder。

@EnableWebFluxSecurity //1
public class SecurityConfig {

  @Bean
  public SecurityWebFilterChain
         springSecurityFilterChain(ServerHttpSecurity http) {
    http
      .authorizeExchange()
      .pathMatchers("/api/**", "/css/**", "/js/**",img/**", "/")
      .permitAll() //2
      .pathMatchers("/user/**").hasAuthority("user") //3
      .and()
      .formLogin();
      return http.build();
    }

    @Bean
    public MapReactiveUserDetailsService
        userDetailsService(@Autowired UserRepository userRepository) {
      List<UserDetails> userDetails = new ArrayList<>();
      userDetails.addAll(
            userRepository.findAll().collectList().block());//4
      return new MapReactiveUserDetailsService(userDetails);
  }

  @Bean
  public PasswordEncoder myPasswordEncoder() { //5
      // never do this in production of course
      return new PasswordEncoder() {/*plaintext encoder*/};
  }
}

测试

Spring Boot 为测试提供了全面的内置支持。例如,用@RunWith(SpringRunner.class)@SpringBootTest注释一个 JUnit (4)测试类,我们可以如下运行整个应用来运行集成测试:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HumblecodeApplicationTests {
  @Autowired
  private TestRestTemplate testRestTemplate;

  @Test
  public void testFreeMarkerTemplate() {
    ResponseEntity<String> entity = testRestTemplate
          .getForEntity("/", String.class);
    assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(entity.getBody()).contains("Welcome to");
  }
}

这个简单的测试启动了我们的 Spring Boot 应用,并验证了根页面返回 HTTP OK (200)状态代码,并且正文包含文本“Welcome to”。使用“webEnvironment = WebEnvironment.RANDOM_PORT”指定每次运行测试时,Spring Boot 应用应该随机选择一个端口在本地运行。

Spring Data R2DBC

R2DBC 7 (反应式关系数据库连接)是一个标准的编程接口,用于以反应式、非阻塞的方式与关系数据库(如 MySQL)集成。

尽管仍处于早期阶段,但已经有一些驱动程序的实现,包括 MySQL、H2、微软 SQL Server 和 PostgreSQL。

R2DBC 在第六章中有更全面的介绍。

Netty 还是 Tomcat

默认情况下,Spring WebFlux 将使用嵌入式 Netty web 容器。但是,如果 spring-web 包含在您的类路径中,您的应用将使用 Tomcat 运行。Spring Web 引入 Tomcat 作为依赖项,这是自动配置的默认设置。

WebFlux 支持使用 Netty(一种异步、非阻塞、事件驱动的网络应用框架)或 Servlet 3.1 非阻塞标准(使用 Tomcat 或 Jetty)运行。

为了确保在 Netty 上运行,应该从依赖项中排除 Tomcat。

web 客户端

如果您的类路径中有 Spring WebFlux,您也可以使用WebClient来调用远程 web 服务。与RestTemplate相比,这个客户端有更多的功能感,并且是完全反应式的,使用 Netty 作为并发引擎。

您可以从静态的WebClient.builder()方法开始使用构建器模式创建 WebClient 的实例,例如:

import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
// later on...
WebClient myWebClient = WebClient.builder()
    .baseUrl("http://localhost:8080")
    .defaultCookie("cookieKey", "cookieValue")
    .defaultHeader(HttpHeaders.CONTENT_TYPE,
               MediaType.APPLICATION_JSON_VALUE)
  .build();

这将使用给定的 baseUrl 构建一个 WebClient,这样所有的请求都将从这个 Url 开始。它还为每个请求提供了一个 cookie 和头部。配置 WebClient 的方法还有很多。 8

每个请求从定义 HTTP 方法开始,然后您可以指定一个额外的 URL 路径(有或没有路径变量)并调用返回一个Mono<ClientResponse>的 exchange,例如:

// get the Course with ID=1 and print it out:
myWebClient.get()
          .uri("/api/courses/{id}", 1L)
          .exchange()
          .flatMap((ClientResponse response) ->
                       response.bodyToMono(Course.class))
          .subscribe(course -> System.out.println("course = " + course));

Footnotes 1

https://projectreactor.io/

  2

https://netty.io/

  3

www.mongodb.com/

  4

https://github.com/adamldavis/humblecode

  5

https://projectlombok.org/features/Data

  6

https://en.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol

  7

https://r2dbc.io/

  8

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.Builder.html

 

十八、SpringCloud

SpringCloud 1 是一个伞形项目,由几个与构建基于云的应用和微服务相关的项目组成。这些项目包括 Spring Cloud 网飞、Spring Cloud Config、Spring Cloud Vault、Spring Cloud OpenFeign、Spring Cloud for Amazon Web Services、Spring Cloud Stream 和 Spring Cloud Bus。有太多的内容要在一章中涵盖,所以我们将涵盖一些重点。

特征

Spring Cloud 专注于为基于云的微服务的典型用例提供良好的开箱即用体验,以及覆盖其他的可扩展性机制。

  • 分布式/版本化配置

  • 服务注册和发现

  • 选择途径

  • 服务对服务呼叫

  • 负载平衡

  • 断路器

  • 全局锁

  • 领导选举与集群国家

  • 分布式消息传递

Spring Cloud 采用了一种非常声明性的方法,通常只需更改一个类路径和/或一个注释就可以获得很多特性。例如,这个示例应用启用了 Eureka discovery 客户端(一个与 Eureka 服务器通信以定位服务的客户端):

@SpringBootApplication
@EnableDiscoveryClient
public class DiscoveryApplication

{
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Listing 18-1DiscoveryApplication.java

入门指南

首先,您需要将 spring-cloud-dependencies 构件添加到您的构建中。对于 Gradle 来说,这看起来像下面这样:

dependencyManagement {
 imports {
  mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR6"
 }
}

对于 Maven,它看起来是这样的:

<dependencyManagement>
 <dependencies>
  <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-dependencies</artifactId>
   <version>Hoxton.SR6</version>
   <type>pom</type>
   <scope>import</scope>
  </dependency>
 </dependencies>
</dependencyManagement>

接下来,您需要包含 spring-cloud-starter 以及您想要使用的任何其他特定启动器,这取决于您的需求。作为参考,下面是 Spring Cloud 的依赖工件及其描述(groupId 是org.springframework.cloud):

|

ArtifactId

|

描述

| | --- | --- | | spring-cloud-starter | SpringCloud 基地首发。 | | spring-cloud-starter-eureka-server | 尤里卡发现服务器,它是网飞 Spring 云的一部分。 | | spring-cloud-starter-aws | Spring Cloud AWS 支持的启动程序。 | | spring-cloud-starter-aws-jdbc | 允许您轻松连接到 AWS RDS 数据库。 | | spring-cloud-starter-aws-messaging | 支持消息传递的 AWS SQS。 | | spring-cloud-starter-config | 支持云配置解决方案。 | | spring-cloud-starter-consul-config | 使用 Consul 支持云配置。 | | spring-cloud-starter-gateway | 提供了一种简单而有效的方法来路由到 API,并提供跨领域的关注点,如安全性、指标和弹性。 | | spring-cloud-gcp-starter | 支持谷歌云平台(GCP)。 | | spring-cloud-gcp-starter-pubsub | 支持 Google Cloud PubSub。 | | spring-cloud-gcp-starter-storage | 支持谷歌云存储。 | | spring-cloud-starter-netflix-eureka-client | 用于 Eureka 的基于 REST 的 API,用于负载平衡和故障转移的发现客户机。 | | spring-cloud-starter-netflix-hystrix | 带有网飞 Hystrix 的断路器。 | | spring-cloud-starter-netflix-ribbon | 用网飞的丝带实现客户端负载平衡。 | | spring-cloud-starter-netflix-zuul | 使用 Zuul 支持智能和可编程路由。 | | spring-cloud-starter-openfeign | Feign 是一个 REST 客户端,允许您在接口上使用 JAX RS 或 Spring MVC 注释来定义客户端的代理。 | | spring-cloud-starter-vault-config | 为使用 Vault 提供客户端支持,Vault 是 HashiCorp 的外部机密配置存储。 | | spring-cloud-starter-zipkin | 支持使用 Zipkin 的分布式跟踪。 | | spring-cloud-starter-zookeeper-config | 使用 ZooKeeper 支持分布式配置。 | | spring-cloud-stream | 用于构建高度可伸缩的事件驱动微服务的框架(需要 RabbitMQ 或 Kafka 这样的消息系统)。 |

例如,对于 spring-cloud-stream,在 Maven pom 中添加以下内容:

<dependency>

  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream</artifactId>
</dependency>

为了更容易地开始,您可以使用第十五章中描述的 Spring Boot CLI。

SpringCloud 网飞

Spring Cloud 网飞包括对来自网飞的 OSS(开源软件)的支持,包括 Eureka、Hystrix、ZooKeeper 等。这些项目中的每一个都有不同的用途,它们可以单独使用,也可以一起使用。

换句话说,Eureka 用于云发现,以便服务可以在云环境中轻松找到彼此,而不需要知道彼此的确切 IP 地址。Spring 使得启动 Eureka 服务器变得非常容易,例如:

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServer {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Listing 18-2DiscoveryServer.java

然后,您可以像使用任何 Spring 配置一样配置 Eureka 服务器,例如:

# Configure this Discovery Server
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false # do not auto-register as client
    fetchRegistry: false
server:
  port: 3000   # where this discovery server is accessible

Listing 18-3application.yml

然后,在使用@ EnableEurekaClient的其他 Spring Boot 应用中,您可以将它们配置为向 Eureka 服务器注册:

# Application name
spring:
  application:
    name: SERVICE1
# Discovery Server Access
eureka:
  client:
    registerWithEureka: true
    fetchRegistry: false
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://localhost:3000/eureka/}

根据您的设置,您应该用尤里卡服务器的实际主机的位置替换 localhost ,或者在运行时设置尤里卡 _URI 环境变量。

Exercise: Implement a Eureka Server and Client

使用前面的说明,创建两个应用——一个是 Eureka 服务器,一个是客户端服务。也许,使用现有的 Spring Boot 应用作为客户端。

寻找服务

一旦有了 Eureka 服务器和配置了 Eureka 客户端的应用,有几种方法可以“发现”或定位注册到 Eureka 服务器的服务:

  • 使用尤里卡客户端

  • 使用发现客户端

  • 通过使用 Eureka 服务标识符代替实际的 URL 来使用 RestTemplate

  • 使用 Feign(这将在本章后面介绍)

您可以使用更通用的(不是特定于网飞的)选项org.springframework.cloud.client.discovery.DiscoveryClient,它为发现客户端提供了一个简单的 API,如下例所示:

@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
    List<ServiceInstance> list = discoveryClient.getInstances("SERVICE1");
    if (list != null && list.size() > 0 ) {
        return list.get(0).getUri();
    }
    return null;
}

要使用的标识符基于应用名称;在本例中,它是 SERVICE1。在这个简单的例子中,我们使用找到的第一个服务的 URI(通用资源标识符),如果有的话;否则,我们返回 null (URI 比 URL 更通用,但含义相似)。

Exercise: Use the Discovery Client

创建另一个 Spring Boot 应用,该应用使用 DiscoveryClient 来定位和调用您之前定义的服务。记得使用spring.application.name属性设置每个应用的名称。

Spring 云配置

Spring Cloud Config 包含了一个针对云原生应用的集中式配置的抽象。有了配置服务器,您就有了一个中央位置来管理所有环境中应用的外部属性。客户机和服务器上的概念完全对应于 Spring EnvironmentPropertySource抽象,因此它们非常适合 Spring 应用。服务器存储后端的默认实现使用 git 存储库来存储配置。

只要 Spring Boot 执行器和 Spring 配置客户端在类路径上,任何 Spring Boot 应用都会尝试联系一个在http://localhost:8888(默认值为spring.cloud.config.uri)上的配置服务器。如果你想改变这个默认设置,你可以在bootstrap.yml or bootstrap.properties中或者通过系统属性或环境变量设置spring.cloud.config.uri。然后你可以添加@EnableAutoConfiguration注释,例如:

@Configuration
@EnableAutoConfiguration
@RestController
public class Application {

  @Value("${config.name}") // can come from config server
  String name = "";

  //more code...
}

要运行自己的服务器,使用spring-cloud-config-server依赖项,并在 Java 配置类或主@SpringBootApplication注释类上添加@EnableConfigServer注释。如果您设置属性spring.config.name=configserver,,应用将在端口8888上运行,并提供来自样本存储库的数据。将spring.cloud.config.server.git.uri属性设置为 git 存储库的位置(它可以以file:开头,表示它是文件系统上的本地路径)。

SpringCloud 开佯

该项目支持 OpenFeign 2 (有时也称为 Feign),这是一种用于包装 REST 应用消息和构建声明性 REST 客户端的抽象。Feign 创建了一个用 JAX RS 或 Spring MVC 注释修饰的接口的动态实现,例如:

@SpringBootApplication
@EnableFeignClients                    //1
public class WebApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }

    @FeignClient("service1")        //2
    static interface NameService {
        @GetMapping("/")          //3
        public String getName();
    }
}

  1. 使用@EnableFeignClients来启用 feign 客户端的自动配置,这是我们在这个类中定义的一个客户端。

  2. 在这里,我们用 FeignClient 对接口进行了注释,并将其命名为 service1。您还可以添加自定义配置,如下所示:@FeignClient(name = "service1", configuration = FooConfiguration.class)

  3. 对于 HTTP,我们使用 Spring MVC 的@GetMapping注释来定义映射到这个方法的路径。您可以使用任何类似的注释,如@RequestMapping@PostMappingJAX-RS注释。

在@ FeignClient注释中,给定的字符串值(如前面示例中的“service1”所示)是一个客户端名称,用于创建带状负载平衡器或 Spring Cloud 负载平衡器。您可以使用 url 属性指定一个绝对 URL。负载平衡器将在运行时发现“service1”服务的物理地址。如果您的应用是一个 Eureka 客户机,那么它将在 Eureka 服务注册中心解析服务。

AWS 的 Spring 云

该项目支持 AWS(亚马逊 Web 服务 3 ),包括为 SQS 4 (简单队列服务)实现 Spring Messaging API,为 ElastiCache 实现 Spring Cache API, 5 ,以及基于 RDS 6 实例的逻辑名自动创建 JDBC 数据源。

例如,要在您的应用中开始监听 AWS SQS 消息队列,您需要在构建文件中包含spring-cloud-starter-aws-messaging依赖项,然后使用@ MessageMapping注释,如下所示:

@MessageMapping("logicalQueueName")
private void receiveMessage(Course course,
                            @Header("SenderId") String senderId) {
    // handle message...

}

img/498572_1_En_18_Figb_HTML.jpg有关更多信息,请参见用于 AWS 的 Spring Cloud 的 Spring 文档。 7

SpringCloudStream

Spring Cloud Stream 类似于 Spring Integration 的消息支持,但仅适用于发布和消费消息。Spring Cloud Stream 支持 RabbitMQ、Apache Kafka、Amazon Kinesis 等多种绑定器实现。

Spring Cloud Stream 的核心构件是

  • 目的地绑定器——负责提供与外部消息传递系统(如 RabbitMQ)集成的组件。

  • 输入和输出/生产者和消费者——从 Spring Cloud Stream 2.1 开始,内置了对使用 Java 8 函数接口来定义接收器(输入)、源(输出)和处理器(两者)的支持,分别使用java.util.function.Consumer<T>Supplier<T>Function<T,R>(请参见下面的 EnableBinding 以了解遗留替代方案)。

  • 消息——生产者和消费者用来与目的地绑定者通信的规范数据结构。

启用绑定

遗留的 Spring CloudStream 使用@ EnableBinding注释,该注释带有 Sink、Source、Processor 或您自己定制的带注释接口的值。接收器接口有一个 input()方法,源接口有一个 output()方法,处理器接口扩展接收器和源,例如:

@EnableBinding(Source.class)
public class TimerSource {
  @Bean
  @InboundChannelAdapter(value = Source.OUTPUT, poller =
     @Poller(fixedDelay = "10", maxMessagesPerPoll = "1"))
  public MessageSource<String> timerMessageSource() {
    return () -> new GenericMessage<>("Hello Spring Cloud Stream");
  }
}

在这种情况下,它支持将此方法作为输出/源进行轮询。

Install Rabbitmq

在 Ubuntu Linux 上,使用以下命令:

$ sudo apt install rabbitmq 伺服器

在 MacOS 上,使用“brew install rabbitmq”,其他系统请参见在线指南。 8

下面是使用Supplier<T>语义的例子:

@SpringBootApplication
public static class SourceFromSupplier {
        @Bean

public Supplier<String> source1() {
                return () -> "" + new Date();
        }
        // other beans...
}

默认情况下,类似前面的简单源每秒轮询一次。这可以通过设置spring.cloud.stream.poller.fixed-delay属性(毫秒)和spring.cloud.stream.poller.max-messages-per-poll属性(默认为 1)来改变。

下面是一个使用Consumer<T>接收器语义的例子(它只打印出传入的消息):

        @Bean

        public Consumer<String> sink() {
                return System.out::println;
        }

然后,配置 Spring Cloud Stream 以绑定到您的函数,如下所示(在 application.yml 中):

spring:
  cloud:
    stream:
      bindings:
        source1-out-0:
          destination: test1
        sink-in-0:
          destination: test1
      function:
        definition: source1;source2;sink

如果使用 RabbitMQ,这将在运行时创建一个名为“test1”的队列,并将源和接收器都链接到它。默认情况下,每秒钟轮询一次源。确保在您的构建依赖关系中包含一个消息绑定,比如与 RabbitMQ 通信的"spring-cloud-stream-binder-rabbit"

Spring Cloud 函数构建在 Project Reactor 之上,因此您可以在实现供应商、函数或消费者时轻松受益于反应式编程模型(例如,通过将您的源函数更改为返回类型为供应商 >)。在这种情况下,轮询是不必要的,您可以控制数据的供应,例如:

@SpringBootApplication
public static class FluxSupplierConfiguration {
  @Bean
  public Supplier<Flux<String>> stringSupplier() {
    return () -> Flux.from(emitter -> {
        while (true) {
          try {
            emitter.onNext("Hello from Supplier");
            Thread.sleep(2000); //sleep two seconds
          } catch (Exception ignore) {}
        }
    });
  }
}

Listing 18-4FluxSupplierConfiguration.java

Build a Pair of Spring Cloud Stream Apps

在最后的练习中,构建两个使用 RabbitMQ 的应用:一个提供消息,一个打印消息。对于消息,请使用“Hello from”+新日期()。对 Maven 构建使用以下依赖关系(或使用第十五章中的 Spring Initializr):

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>
 spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

有关提示,请参见前面的示例代码。尝试轮询方法,然后尝试之前显示的反应方式。

Footnotes 1

https://spring.io/projects/spring-cloud

  2

https://github.com/OpenFeign/feign

  3

https://aws.amazon.com/

  4

https://aws.amazon.com/sqs/

  5

https://aws.amazon.com/elasticache/

  6

https://aws.amazon.com/rds/

  7

https://spring.io/projects/spring-cloud-aws

  8

www.rabbitmq.com/download.html