读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》使用Spring Boot将您的应用程序投入生产
这是我的源代码
帮我在云端运行它
我不在乎
– Cloud Foundry 俳句 (Onsi Fakhouri @onsijoe)
在上一章中,我们学习了如何保护基于微服务的社交媒体平台。
在本章中,我们将介绍以下主题:
- Configuring profile-specific beans
- Creating configuration property beans
- Overriding property settings in production
- Deploying our social media platform to the cloud
所以,今天就是这一天。我们工作了数周来构建这个系统。现在我们想把它投入生产。会发生什么?会出什么问题?
答:很多。而且……很多。
Spring Boot 具有强大的功能,可以轻松调整和调整生产环境,使我们能够最小化代码。这里介绍的一些概念植根于十二因子应用程序 (https://12factor.net/) 以及外部化配置设置的能力。我们已经通过配置服务器看到了 parts。但是,现在我们将在生产过程中深入挖掘并应用更多内容。
许多基于云的平台使用包裹在应用程序周围的代理。这使平台能够支持许多功能,包括缓存、内容交付网络 (CDN)、负载平衡和 SSL 终止。毕竟,为什么要对开发人员提出如此通用的基础设施要求?
但是,副作用可能会破坏旨在保护我们在网络中的安全协议。例如,我们的应用程序可能在一个私有 IP 地址上运行,而原始请求来自一个面向公众的 URL。当我们的应用程序看到转发的 Web 请求时,我们如何区分正确的请求和利用我们服务的恶意跨站点脚本攻击?
这会影响我们的应用程序的第一个地方是 chat
服务的 WebSocket 处理。它需要显式配置来处理这样的跃点。但是,我们只希望在生产环境中应用我们的代码,而不是在我们的工作站上运行开发中的东西时应用。
解决方案是基于配置文件的bean。 Spring 允许 us 将 bean 配置为仅在启用某些配置文件时才创建。
在上一章中,我们将整个 WebSocket 配置放在一个顶级类中。我们需要更改该配置类并将其转换为具有不同选项的容器类,具体取决于我们是否在生产中。
第一步是将现有的 bean 定义移动到一个新的静态内部类中,如下所示:
到目前为止,我们并没有太大变化。我们所拥有的,可以描述如下:
- The outer class,
WebSocketConfig
, looks the same - This new inner class,
LocalWebSocketConfig
, is annotated@Profile("!cloud")
, meaning it only runs if there is nocloud
profile - The new class is called
LocalWebSocketConfig
to clarify that it only operates when we run things locally
Note
什么是 cloud 配置文件? Spring 允许通过 spring.profiles.active
应用程序属性设置各种配置文件。我们可以创建我们想要的所有配置文件,甚至是重叠的配置文件。但是,部署到 Cloud Foundry 的任何应用程序都会自动拥有一个额外的配置文件,即 cloud
,已应用。
由于我们计划both 本地配置和基于云的配置,因此到 区分什么是相同的,什么是不同的。 WebSocket 路由映射是相同的。
为了支持这一点,我们需要一个 configureUrlMappings()
方法来配置这个 SimpleUrlHandlerMapping
:
这与我们在上一章看到的代码相同,只是稍微移动了一下:
- The three endpoints are tied to their respective services in
Map
of routes-to-WebSocketHandlers
- A
SimpleUrlHandlerMapping
is defined with this map of handlers - The order is set to
10
- The method is static since it will be placed outside our new
LocalWebSocketConfig
(but insideWebSocketConfig
)
要利用它,我们只需在 LocalWebSocketConfig
中编写一个 bean 定义,如下所示:
这个方法只不过是 invoke 我们的 WebSocket 配置方法。
设置好本地配置后,我们现在可以将注意力转向配置 WebSocket 代理以在云中工作。为此,我们需要在 WebSocketConfig
中添加另一个内部静态类,如下所示:
可以解释如下:
- It's marked as
@Profile("cloud")
, meaning this only applies if thecloud
profile is in force, the opposite ofLocalWebSocketConfig
- It contains
@EnableConfigurationProperties(ChatConfigProperties.class)
, used to provide an extra set of properties - It's named
CloudBasedWebSocketConfig
to point out its role
如果您想知道 @EnableConfigurationProperties
是什么意思,它会引导我们进入下一部分。
@EnableConfigurationProperties
,应用于我们应用程序的任何地方,将导致一个名为 ChatConfigProperties
的 bean 被添加到应用程序中语境。 配置属性 bean 旨在保存各种设置,这些设置可以使用可选默认值进行配置,并且可以通过 各种方式。
还记得像 server.port
这样的属性,我们在其中调整了 Netty Web 容器侦听 Web 请求的默认端口吗?我们通过本书看到的所有属性都是配置属性bean。该注释只是为我们提供了定义特定于我们应用程序的属性设置的方法。
在这种情况下,ChatConfigProperties
旨在配置 WebSocket 代理。
尽管启用了这样的属性设置,我们仍然需要将 bean 注入到我们的 CloudBasedWebSocketConfig
配置类中,如下所示:
使用构造函数注入,我们现在可以访问此配置属性 bean 提供的任何属性设置。
深入研究 WebSocket 代理配置,我们需要的是我们愿意接受来自的 WebSocket 连接请求的远程主机。本质上,我们的 chat
微服务的面向公众的 URL。为此,我们将定义一个名为 origin
的属性并使用它,如下所示:
由于 configureUrlMappings
方法,此代码具有与 LocalWebSocketConfig
相同的端点。它还创建了一个 CORS 映射,就像我们在 Chapter 17, WebSockets with Spring 中所做的那样启动。只是,这一次,它使用注入的 getOrigin()
来插入 chat
服务的面向公众的 URL(保持紧——我们很快就会看到)。
缺少的是这个配置属性 bean 的定义。它显示在这里:
代码可以解释如下:
- Once again, we use Project Lombok's
@Data
annotation to avoid writing getters and setters. This is ideal for configuration property beans. @ConfigurationProperty(prefix="lsb")
flags this bean as a candidate for Spring Boot's property reading rules, starting with thelsb
prefix.- There is a single property named
origin
that is initialized using Spring's@Value()
annotation. - On Cloud Foundry,
vcap.application.uris
is a property applied to every application that lists publicly visible URLs. Assuming that the first is the one we wish to use, we are applying it to ourorigin
property. - By combining the prefix (
lsb
) and the name of the property (origin
), the full path of this property islsb.origin
, and it can be overridden at any time.
每次我们将 应用程序 带到一个新环境时,总会有一些设置需要调整。我们不想编辑代码。相反,如果我们可以覆盖各种属性会更容易。我们可以!
这在 第 10 章,Java 快速入门中简要介绍过,以覆盖 Spring Boot 的属性设置为幌子。然而,我们可以编写自己的自定义配置属性 beans 的事实使得它成为应用程序自定义的强大功能。
回顾第 10 章中列出的规则,Java 快速入门 ,可以按以下顺序覆盖属性设置,从高到低:
@TestPropertySource
annotations on test classes.- Command-line arguments.
- Properties found inside
SPRING_APPLICATION_JSON
(inline JSON embedded in an env variable or system property). ServletConfig
init parameters.
ServletContext
init parameters.- JNDI attributes from
java:comp/env
. - Java System properties (
System.getProperties()
). - OS environment variables.
RandomValuePropertySource
that only has properties inrandom.*
.- Profile-specific properties outside the packaged JAR file (
application-{profile}.properties
and YAML variants). - Profile-specific properties inside the packaged JAR file (
application-{profile}.properties
and YAML variants). - Application properties outside the package JAR file (
application.properties
and YAML variants).
- Application properties inside the packaged JAR file (
application.properties
and YAML variants). @PropertySource
annotations on any@Configuration
classes.- Default properties (specified using
SpringApplication.setDefaultProperties
).
Note
默认情况下,我们可以使用 vcap.application.uris[0]
运行。但是,如果我们将它带到另一个云解决方案,我们可以简单地插入对 lsb.origin
的覆盖,并利用新云提供的任何环境变量。这让我们不必再次修改代码,而是专注于让事情运行起来。
最常见的策略之一是创建 beapplication-{profile}.yml 文件indexterm"> 在
生效时自动应用。由于 Cloud Foundry 应用程序获得 cloud 配置文件,它会是 自然地创建一个 application-cloud.yml
文件。
但是,由于我们采用 Spring Cloud Config Server 并指定 chat
服务由 chat.yml
管理,因此我们只需要添加一个 chat-cloud.yml
文件。然后我们知道在部署到 Cloud Foundry 时将应用以下特定于云的设置:
这些设置可以解释如下:
- The
server.port
is the same as before - The
eureka.client.serviceUrl.defaultZone
is changed to the public-facing URL for our Eureka service, so thechat
service can find it - Since the public-facing URL for our
chat
service is terminated by a proxy, we have to overrideeureka.instance.hostname
with${vcap.application.uris[0]}
to avoid registering an unreachable IP address with Eureka - We must also register that we are visible (non-secure) on port
80
comments-cloud.yml
的以下设置相同:
images-cloud.yml
也是如此:
最后,我们需要通过 eureka-cloud.yml
为 Eureka 服务本身设置相同的实例详细信息,如下所示:
如果您注意到,没有 eureka.client.serviceUrl.defaultZone
,因为这是 Eureka 服务!
这些附加设置添加到 https: //github.com/gregturn/learning-spring-boot-config-repo/tree/production 将确保我们的应用程序在云中顺利运行。
如果我们想查看我们新创建的属性设置,我们可以访问 http://learning-spring-boot.cfapps.io/configprops
并查找 ChatConfigProperties
。
从中,我们可以很容易地发现 lsb.origin
是要覆盖的属性,如果我们有一些理由来调整它。
在我们的代码中,我们使用了基于强类型 @ConfigurationProperties
的类以及 @Value
标记的属性。 to在你的 应用程序。
@Value
很旧,比 Spring Boot 早了几年。它是一个强大的注解,能够注入值以及接受默认值。但是,它遗漏了我们许多人在 编写 Boot 应用程序时所依赖的几个功能,如下表所示:
功能 |
@ConfigurationProperties |
@Value |
轻松绑定 |
是的 |
不 |
元数据支持 |
是的 |
不 |
SpEL评估 |
不 |
是的 |
该矩阵记录了三个关键特征:
- Relaxed binding: The ability to match
server.port
,SERVER_PORT
, andsErVeR.pOrT
to the same attribute is quite valuable. - Meta-data support: The ability to include code completion for property settings is also of incredible value, along with hover-over tips. Anything that speeds up developer effort cannot be understated in value.
- SpEL evaluation: The ability to write SpEL expressions to populate properties.
强烈建议从 @ConfigurationProperties
开始。当您在 POJO 中捆绑一组属性时,它确实是一个充满 @Value
属性的拳头的快捷方式。并且属性绑定是增压的。
但是,当您需要 SpEL 表达式支持时,就像我们获取应用程序的 URI (${ vcap.application.uris[0]}
),那么就可以从@ConfigurationProperties
中断,切换到@Value
。
但是,如果您注意到的话,我们会继续在 @ConfigurationProperties
中使用它。做错的真正暗示是,如果我们尝试使用 @Value
构造属性集合。配置属性是一种轻松构建属性层次结构的好方法。
保持冷静,看推力。
- 互联网的居民
如果说对于任何平稳运行的 Ops 中心来说有一件至关重要的事情,那就是对自动化的需求。如果我们手工操作,我们会在生产中的各个组件之间引入漂移的风险。
以下部分展示了一些用于部署 our 基于微服务的 social 媒体平台,迈向自动化部署的第一步。
假设我们已经使用 Gradle 构建了所有东西,让我们通过将Spring Boot uber JAR 部署到Cloud Foundry 来开始:
可以描述如下:
- Each module is deployed using the CF CLI (https://github.com/cloudfoundry/cli), deploying with both a name and the JAR file
- Each command is backgrounded to speed up release
让我们开始和部署!控制台 output 显示我们正在运行 our 部署脚本:
我们社交媒体平台的所有组件现已部署到云端。
被警告!这还不够。上传位后必须应用一些自定义设置。
让我们从配置我们的 Eureka 服务器的部分开始,如下所示:
Eureka 需要配置一个 Config Server URI 和从 GitHub 获取的标签,就像使用 cf set-env
所做的那样。
接下来,我们可以查看 chat
微服务的设置:
chat
服务 需要 配置服务器 URI(带有 GitHub 标签) ,一个MongoDB服务绑定和 URI设置,和一个RabbitMQ服务绑定。
接下来,我们可以查看 comments
微服务的 设置,如此处显示:
comments
服务需要一个配置服务器 URI(带有 GitHub 标签)、一个 MongoDB 服务绑定和 URI 设置,以及一个 RabbitMQ 服务绑定。
接下来,我们可以查看 images
微服务的设置,如下所示:
images
服务需要一个配置服务器 URI(带有 GitHub 标签)、一个 MongoDB 服务绑定和 URI 设置以及一个 RabbitMQ 服务绑定。
Note
虽然所有三个服务都绑定到同一个 MongoDB 服务,但它们实际上可以使用单独的 MongoDB 服务。代码经过仔细编写以避免集成到数据库中。每个服务都有单独的集合。但是,为简洁起见,此代码中仅使用了一项服务。
有了这个,让我们运行以下配置脚本:
应用这些设置后,我们需要重新启动一切。为此,我们需要以下脚本:
为什么重新启动配置服务器后延迟?重要的是,它有机会在其他应用程序之前启动并运行。所以,让我们按如下方式运行它:
我们可以像这样轻松地检查它们的状态:
让我们来看看。我们可以通过访问 http://learning-spring-boot.cfapps.io
来做到这一点(在隐身标签中以确保新会话):
我们将看到非常熟悉的登录页面。
如果我们以 greg
/turnquist
身份登录,删除默认图像并加载我们之前的收藏夹,我们可以期待看到这个:
我们最喜欢的聊天频道位于 thethe 底部"indexterm"> 页面,如 如下截图所示:
如需额外维护,可使用以下脚本删除all 应用程序(但不是相关的 AMQP 和 MongoDB 服务):
使用 CF CLI,在后台作业中删除所有服务。
在本章中,我们创建了特定于配置文件的配置设置,以在本地或基于云的环境中处理 WebSocket 代理。我们插入了一个自定义配置属性 bean,并使用它从我们的云提供商那里获取必要的详细信息,以便我们的聊天频道能够正常工作。然后我们构建了一些 BASH 脚本来将东西部署到云中,配置必要的属性,并在需要时重新启动/清理。
这仅仅是开始。我们在本书中涉及了很多内容,包括 Web 应用程序、数据访问、测试、工具、消息传递、微服务、安全性和生产。我们做到了所有反应,确保我们更有效地使用资源。
认为我们的社交媒体平台价值 10 亿美元?也许,也许不是。但是,通过使用 Spring Boot 2.0、Spring Framework 5 及其基于反应器的端到端范例的长度和广度,我们在如何构建可扩展系统方面学到了很多东西。
希望我已经激起了您的兴趣,让您在从事下一个大项目时发现 Spring Boot 还可以提供什么。
请访问 https://github .com/learning-spring-boot/learning-spring-boot-2nd-edition-code 并“加星标”。这样,当 Spring Boot 2.0 达到 GA 版本并且此代码库已升级以匹配时,您将收到警报。还可以在 http://greglturnquist.com/books/learning 注册更新-spring-boot 这样您就可以收到最新消息的提醒,包括代码模组、竞赛等!