Spring 快速参考指南(三)
十五、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 项目有几种方式:
-
转到 Spring Initializr1网站,在那里创建一个项目模板。还有像 Spring Tool Suite 这样的工具,它们利用了 IDE 中的 Spring Initializr。
-
创建自己的基于 Maven 的项目。
-
创建自己的基于 Gradle 的项目。
-
使用 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查看所有可用的依赖项:
图 15-1
Spring 初始化列表:输出
$ spring init –list
然后,一旦您理解了您需要哪些依赖项,您就可以使用spring init命令来创建您的项目;例如:
图 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 许多事情:
-
使用自动配置。
-
使用组件扫描。扫描所有的包(从类的包和所有的子包开始),寻找用 Spring 注释标注的类,比如
@Component。 -
这个类是一个基于 Java 的配置类(与
@Configuration相同),所以您可以在这里使用返回 bean 的方法上的@Bean注释来定义 bean。 -
它将该类标记为应用的主配置类(与
@SpringBootConfiguration相同),并允许 Spring Boot 测试找到它(标记为@SpringBootTest,这将在后面介绍)。
每个应用只能使用一个@SpringBootApplication。
自动配置
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 {
我们将一次检查一个注释。
-
@
Configuration–声明这个类是一个 Spring 配置类。 -
@
ConditionalOnClass–告诉 Spring 仅当这些类在运行时类路径中时才使用这个配置。 -
@
ConditionalOnMissingBean–仅当 ApplicationContext 中不存在这些类型的 Beans 时,此配置才有效。 -
@
ConditionalOnProperty–属性spring.data.mydb.reactive-repositories.enabled必须设置为 true 或 missing (matchIfMissing)才能调用此配置。 -
@
Import–导入将由 Spring 处理的另一个配置类。 -
@
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 将按照这个顺序加载以下内容(后面的属性可以覆盖前面的属性):
-
应用.属性
-
应用程式. yml
-
应用测试属性
-
应用测试. yml
覆盖属性
环境变量也可以覆盖任何属性。Spring 自动将大写下划线语法转换为属性语法。例如,名为SPRING_PROFILES_ACTIVE的环境变量将覆盖属性文件中的spring.profiles.active。
优先级顺序是(列表中较早的条目优先于较晚的条目)
-
命令行参数
-
来自
SPRING_APPLICATION_JSON的属性(嵌入在环境变量或系统属性中的内联 JSON) -
web.xml中的 Servlet 上下文参数 -
JNDI 属性来自
java:comp/env -
Java 系统属性(来自 System.getProperties())
-
环境变量
-
从
application-{profile}.properties文件或类似的 YAML 文件加载的特定于配置文件的属性 -
从
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 逻辑的情况下测试服务器端逻辑非常有用。 |-
在 JUnit 4 测试中,使用
@RunWith(SpringRunner.class)来启用 Spring 测试对测试中自动连接字段的支持(对于 JUnit 5 测试,使用@ExtendWith(SpringExtension.class))。 -
使用@
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注释的模拟来提供,例如:
-
为了只测试一个控制器(
CourseController)),我们可以使用@WebMvcTest并指定一个类(您可以指定多个控制器)。Spring 将自动配置 Spring MVC 基础设施,刚好足以测试这个控制器。实际上没有网络 I/O 发生,所以测试可以非常快速地运行。 -
自动创建一个 MockMvc 的实例,该实例可用于直接测试任何使用 Spring MVC 的控制器(如第七章所述)。
-
用
@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
2
十六、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 中),并提供在运行时修改配置的方法。对于每个记录器,它提供了configuredLevel和effectiveLevel。您可以修改configuredLevel(这通常也会改变effectiveLevel)。
您可以通过在/ loggers/{name}端点使用 HTTP GET 来请求信息或特定的记录器,例如:
$ curl http://localhost:8081/actuator/loggers/com.apress
{"configuredLevel":null,"effectiveLevel":"INFO"}
确保你已经将致动器配置为暴露(如本章前面所解释的)并且将 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
十七、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 如下所示:
-
您可能注意到的第一件事是缺少指定的版本;Spring Boot 为你提供这些,并确保一切都是兼容的。
-
我们为构建工件指定 groupId 和版本。我们还将 Java 源代码版本指定为 11。您不需要指定主类。这是由 Spring Boot 通过注释确定的。
-
我们包含了“webflux”启动器来启用 Spring 的 WebFlux。
-
我们在这里包含 lombok 项目只是为了简化模型类。Lombok 提供注释,并根据每个类使用的注释自动生成样板代码,如 getters 和 setters。
-
这里我们包括了 spring-data starter,用于将反应式 MongoDB 与反应器集成在一起。
-
最后,我们包括“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
}
-
前两个注释是 Lombok 注释。告诉 Lombok 为每个字段添加 getters 和 setters、equals 和 hashCode 方法、一个构造函数和一个 toString 方法。 5
-
@Document注释是 spring-data-mongodb 注释,声明这个类代表一个 mongodb 文档。 -
@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>,这些方法将自动反应。
例如,我们可以创建一个课程存储库:
-
第一个泛型类型是这个存储库存储的类型(课程),第二个是课程 ID 的类型。
-
该方法查找名称与给定搜索字符串匹配的所有球场,并返回一个 Flux 。
-
该方法查找具有给定名称的所有课程。如果我们确信名字是唯一的,我们可以使用
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 函数的定义如下:
-
首先,我们调用我们的 RESTful API,我们将在后面定义它。
-
由于我们使用的是 jQuery,它会自动确定响应是 JSON 并解析返回的数据。
-
使用 forEach,我们构建一个 HTML 列表来显示每门课程,并提供一个链接来加载每门课程。
-
我们更新 DOM 以包含我们构建的列表。
-
这里我们指定了错误处理函数,以防 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:
-
这个注释告诉 Spring Security 保护您的 WebFlux 应用。
-
我们使用 ant 模式定义了允许所有用户使用的路径,其中“**”表示任何一个或多个目录。这使得每个人都可以访问主页和静态文件。
-
在这里,我们确保用户必须登录才能访问“/user/”路径下的任何路径。
-
这一行将 UserRepository 中的所有用户转换成一个列表。然后将它传递给 MapReactiveUserDetailsService,该服务为用户提供 Spring Security 性。
-
您必须定义一个密码编码。这里我们定义一个明文编码只是为了演示的目的。在实际系统中,您应该使用 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
2
3
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
8
十八、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 Environment和PropertySource抽象,因此它们非常适合 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();
}
}
-
使用
@EnableFeignClients来启用 feign 客户端的自动配置,这是我们在这个类中定义的一个客户端。 -
在这里,我们用 FeignClient 对接口进行了注释,并将其命名为 service1。您还可以添加自定义配置,如下所示:
@FeignClient(name = "service1", configuration = FooConfiguration.class)。 -
对于 HTTP,我们使用 Spring MVC 的
@GetMapping注释来定义映射到这个方法的路径。您可以使用任何类似的注释,如@RequestMapping或@PostMapping或JAX-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...
}
有关更多信息,请参见用于 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 1https://spring.io/projects/spring-cloud
2
https://github.com/OpenFeign/feign
3
4
5
https://aws.amazon.com/elasticache/
6
7
https://spring.io/projects/spring-cloud-aws
8
www.rabbitmq.com/download.html