This commit is contained in:
by931
2022-09-06 22:30:37 +08:00
parent 66970f3e38
commit 3d6528675a
796 changed files with 3382 additions and 3382 deletions

View File

@@ -179,8 +179,8 @@ function hide_canvas() {
<p>越来越强大的 Spring Boot 俨然是 Java EE 领域的标准开发框架了。因此,掌握 Spring Boot 成了 Java 工程师的必备技能,而熟练掌握 Spring Boot 中的各项技术组件,并能够在一定程度上掌握其内部运行机制,是你从事 Java 应用程序开发的基本要求,也是你将来学习 Spring Cloud 等微服务开发框架的基础。</p>
<p>我自己也有着多家大型互联网公司的从业经验,日常也经常面试来自不同公司的 Java 工程师,在面试过程中,我对开发人员的要求:<strong>掌握 Spring Boot 已经不是一个加分项,而是一个必备技能。</strong></p>
<p>你也可以上拉勾网站查看相关岗位职责,基本都有这条限制,以下是我截取的两份 Java 开发工程师岗的招聘要求:</p>
<p><img src="assets/Ciqc1F-7VTmAcByzAAGG1shcgSQ296.png" alt="Drawing 0.png" />
<img src="assets/CgqCHl-7VT2ABxE5AABvwZSxw-k496.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Ciqc1F-7VTmAcByzAAGG1shcgSQ296.png" alt="png" />
<img src="assets/CgqCHl-7VT2ABxE5AABvwZSxw-k496.png" alt="png" /></p>
<p>(职位信息来源:拉勾网)</p>
<p><strong>可以说,深入了解并掌握 Spring Boot 是你成功进入大公司、斩获高薪 Offer 的一个重要砝码。</strong></p>
<h3>这门课程是如何设计的?</h3>
@@ -193,7 +193,7 @@ function hide_canvas() {
<p><strong>虽然 Spring Boot 让你只花 20% 的时间就可解决 80% 的问题,但是剩下 20% 的问题需要我们通过系统性的学习去弄懂,而学习 Spring Boot 是有一定的方法和套路的</strong></p>
<p>为此,我根据个人多年的架构经验以及对 Spring Boot 的理解,整理出了一套系统化、由浅入深的学习路径,从中你不仅可以掌握 Spring Boot 的全局,更可以从学习三大难题入手一一突破,更加高效地掌握 Spring Boot 的使用方法和实战技巧。</p>
<p>基于如下图所示的 Web 应用程序的拆分维度,我把整个课程设计为 8 大部分,基于目前最主流的 Java EE 领域开发框架 Spring Boot向你介绍如何从零构建一个 Web 应用程序。</p>
<p><img src="assets/Ciqc1F-7VWqAIEwvAAFG005-D-w990.png" alt="Lark20201123-142325.png" /></p>
<p><img src="assets/Ciqc1F-7VWqAIEwvAAFG005-D-w990.png" alt="png" /></p>
<p>Web 应用程序的拆分维度</p>
<ul>
<li><strong>第 1 部分,开启 Spring 框架的学习之旅</strong>。这部分将介绍 Spring 家族的整个生态系统和技术体系,并提供一个 Spring Customer Service System简称 SpringCSS 案例来贯穿整个 Spring Boot 框架的学习过程。</li>

View File

@@ -173,14 +173,14 @@ function hide_canvas() {
<p>今天,我将通过一个课时的时间带领你梳理整个 Spring 家族中的技术体系,以及各种功能齐全的开发框架。让我们先来看一下 Spring 家族技术生态的全景图。</p>
<h3>Spring 家族技术生态全景图</h3>
<p>我们访问 Spring 的官方网站(<a href="https://spring.io/">https://spring.io/</a>)来对这个框架做宏观的了解。在 Spring 的主页中,展示了下面这张图:</p>
<p><img src="assets/Ciqc1F-7VlGAesWiAACRuk5Qiko663.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Ciqc1F-7VlGAesWiAACRuk5Qiko663.png" alt="png" /></p>
<p>Spring 家族技术体系(来自 Spring 官网)</p>
<p>从图中可以看到,这里罗列了 Spring 框架的七大核心技术体系分别是微服务架构、响应式编程、云原生、Web 应用、Serverless 架构、事件驱动以及批处理。</p>
<p>当然,这些技术体系各自独立但也有一定交集,例如微服务架构往往会与基于 Spring Cloud 的云原生技术结合在一起使用,而微服务架构的构建过程也需要依赖于能够提供 RESTful 风格的 Web 应用程序等。</p>
<p>另一方面,在具备特定的技术特点之外,这些技术体系也各有其应用场景。例如,如果我们想要实现日常报表等轻量级的批处理任务,而又不想引入 Hadoop 这套庞大的离线处理平台时,使用基于 Spring Batch 的批处理框架是一个不错的选择。再比方说,如果想要实现与 Kafka、RabbitMQ 等各种主流消息中间件之间的集成,但又希望开发人员不需要了解这些中间件在使用上的差别,那么使用基于 Spring Cloud Stream 的事件驱动架构是你的首选,因为这个框架对外提供了统一的 API从而屏蔽了内部各个中间件在实现上的差异性。</p>
<p>我们无意对 Spring 中的所有七大技术体系做全面的展开。在日常开发过程中,如果构建单块 Web 服务,可以采用 Spring Boot。如果想要开发微服务架构那么就需要使用基于 Spring Boot 的 Spring Cloud而 Spring Cloud 同样内置了基于 Spring Cloud Stream 的事件驱动架构。同时,在这里我想特别强调的是响应式编程技术。响应式编程是 Spring 5 引入的最大创新,代表了一种系统架构设计和实现的技术方向。因此,今天我们也将从 Spring Boot、Spring Cloud 以及 Spring 响应式编程这三个技术体系进行切入,看看 Spring 具体能够为我们解决开发过程中的哪些问题。</p>
<p>当然,所有我们现在能看到的 Spring 家族技术体系都是在 Spring Framework 基础上逐步演进而来的。在介绍上述技术体系之前,我们先简单了解下 Spring Framework 的整体架构,如下图所示:</p>
<p><img src="assets/Ciqc1F-7XBiAUUZ5AACAA4yJiFs420.png" alt="1.png" /></p>
<p><img src="assets/Ciqc1F-7XBiAUUZ5AACAA4yJiFs420.png" alt="png" /></p>
<p>Spring Framework 整体架构图</p>
<p>Spring 从诞生之初就被认为是一种容器,上图中的“核心容器”部分就包含了一个容器所应该具备的核心功能,包括容器中基于依赖注入机制的 JavaBean 处理、面向切面 AOP、上下文 Context及 Spring 自身所提供的表达式工具等一些辅助功能。</p>
<p>图中最上面的两个框就是构建应用程序所需要的最核心的两大功能组件,也是我们日常开发中最常用的组件,即数据访问和 Web 服务。这两大部分功能组件中包含的内容非常多,而且充分体现了 Spring Framework 的集成性也就是说框架内部整合了业界主流的数据库驱动、消息中间件、ORM 框架等各种工具,开发人员可以根据需要灵活地替换和调整自己想要使用的工具。</p>
@@ -212,20 +212,20 @@ public class DemoApplication {
<p>可以看到Spring Boot 的上述功能实际上从多个维度简化了 Web 应用程序的开关过程,这些维度包含编码、配置、部署和监控等。在 02 讲中,我们将通过一个具体的案例来对每个维度给出更为详细的描述。</p>
<h3>Spring Cloud 与微服务架构</h3>
<p>Spring Cloud 构建在 Spring Boot 基础之上,它的整体架构图如下所示:</p>
<p><img src="assets/CgqCHl-7VsqAU_-oAAA1B5IapO8742.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CgqCHl-7VsqAU_-oAAA1B5IapO8742.png" alt="png" /></p>
<p>Spring Cloud 与微服务整体架构图(来自 Spring 官网)</p>
<p>技术组件的完备性是 Spring Cloud 框架的主要优势它集成了业界一大批知名的微服务开发组件。Spring Cloud 的核心组件如下图所示:</p>
<p><img src="assets/Ciqc1F-7Vt-ANEHuAACBTY1pIak300.png" alt="Drawing 4.png" /></p>
<p><img src="assets/Ciqc1F-7Vt-ANEHuAACBTY1pIak300.png" alt="png" /></p>
<p>Spring Cloud 核心功能组件</p>
<p>可以看到,基于 Spring Boot 的开发便利性Spring Cloud 巧妙地简化了微服务系统基础设施的开发过程Spring Cloud 包含上图中所展示的服务发现注册、API 网关、配置中心、消息总线、负载均衡、熔断器、数据监控等。</p>
<h3>Spring 5 与响应式编程</h3>
<p>目前Spring 已经演进到 5.X 版本。随着 Spring 5 的正式发布我们迎来了响应式编程Reactive Programming的全新发展时期。Spring 5 中内嵌了与数据管理相关的响应式数据访问、与系统集成相关的响应式消息通信以及与 Web 服务相关的响应式 Web 框架等多种响应式组件,从而极大地简化了响应式应用程序的开发过程和开发难度。</p>
<p>下图展示了响应式编程的技术栈与传统的 Servlet 技术栈之间的对比:</p>
<p><img src="assets/Ciqc1F-7VumAJcdAAACWjgTTFkY645.png" alt="Drawing 5.png" /></p>
<p><img src="assets/Ciqc1F-7VumAJcdAAACWjgTTFkY645.png" alt="png" /></p>
<p>响应式编程技术栈与 Servlet 技术栈之间的对比图(来自 Spring 官网)</p>
<p>从上图可以看到,上图左侧为基于 Spring WebFlux 的技术栈,右侧为基于 Spring MVC 的技术栈。我们知道传统的 Spring MVC 构建在 Java EE 的 Servlet 标准之上,该标准本身就是阻塞式和同步的,而 Spring WebFlux 基于响应式流,因此可以用来构建异步非阻塞的服务。</p>
<p>在 Spring 5 中,选取了 Project Reactor 作为响应式流的实现库。由于响应式编程的特性Spring WebFlux 和 Project Reactor 的运行需要依赖于诸如 Netty 和 Undertow 等支持异步机制的容器。同时我们也可以选择使用较新版本的 Tomcat 和 Jetty 作为运行环境,因为它们支持异步 I/O 的 Servlet 3.1。下图更加明显地展示了 Spring MVC 和 Spring WebFlux 之间的区别和联系:</p>
<p><img src="assets/Ciqc1F-8pB6AReQhAADiHs1UMA4354.png" alt="3.png" /></p>
<p><img src="assets/Ciqc1F-8pB6AReQhAADiHs1UMA4354.png" alt="png" /></p>
<p>在基于 Spring Boot 以及 Spring Cloud 的应用程序中Spring WebFlux 和 Spring MVC 可以混合进行使用。</p>
<p>讲完 Spring 家族的技术体系,让我们回到课程。在 01 讲中,我们主要围绕 Spring Boot 框架展开讨论分别从配置体系、数据访问、Web 服务、消息通信、系统安全、系统监控、应用测试等维度对该框架进行深入的剖析,所采用的版本为 2.2.X 版。</p>
<h3>小结与预告</h3>

View File

@@ -172,26 +172,26 @@ function hide_canvas() {
<p>在 01 讲中,我们提到 Spring 家族具备很多款开源框架,开发人员可以基于这些开发框架实现各种 Spring 应用程序。在 02 讲中,我们无意对所有这些 Spring 应用程序的类型和开发方式过多展开,而是主要集中在基于 Spring Boot 开发面向 Web 场景的服务,这也是互联网应用程序最常见的表现形式。在介绍基于 Spring Boot 的开发模式之前,让我们先将它与传统的 Spring MVC 进行简单对比。</p>
<h3>Spring MVC VS Spring Boot</h3>
<p>在典型的 Web 应用程序中,前后端通常采用基于 HTTP 协议完成请求和响应,开发过程中需要完成 URL 地址的映射、HTTP 请求的构建、数据的序列化和反序列化以及实现各个服务自身内部的业务逻辑,如下图所示:</p>
<p><img src="assets/CgqCHl-7WIiAQimVAABHwO_CPqU821.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CgqCHl-7WIiAQimVAABHwO_CPqU821.png" alt="png" /></p>
<p>HTTP 请求响应过程</p>
<p>我们先来看基于 Spring MVC 完成上述开发流程所需要的开发步骤,如下图所示:</p>
<p><img src="assets/CgqCHl-7WI2AaDcrAABBSHVyAdE329.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHl-7WI2AaDcrAABBSHVyAdE329.png" alt="png" /></p>
<p>基于 Spring MVC 的 Web 应用程序开发流程</p>
<p>上图中包括使用 web.xml 定义 Spring 的 DispatcherServlet、完成启动 Spring MVC 的配置文件、编写响应 HTTP 请求的 Controller 以及将服务部署到 Tomcat Web 服务器等步骤。事实上,基于传统的 Spring MVC 框架开发 Web 应用逐渐暴露出一些问题,比较典型的就是配置工作过于复杂和繁重,以及缺少必要的应用程序管理和监控机制。</p>
<p>如果想优化这一套开发过程,有几个点值得我们去挖掘,比方说减少不必要的配置工作、启动依赖项的自动管理、简化部署并提供应用监控等。而这些优化点恰巧推动了以 Spring Boot 为代表的新一代开发框架的诞生,基于 Spring Boot 的开发流程见下图:</p>
<p><img src="assets/Ciqc1F-7WJeAHD-nAABRAXax5k4419.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Ciqc1F-7WJeAHD-nAABRAXax5k4419.png" alt="png" /></p>
<p>基于 Spring Boot 的 Web 应用程序开发流程</p>
<p>从上图中可以看到,它与基于 Spring MVC 的开发流程在配置信息的管理、服务部署和监控等方面有明显不同。作为 Spring 家族新的一员Spring Boot 提供了令人兴奋的特性,这些特性的核心价值在于确保了开发过程的简单性,具体体现在编码、配置、部署、监控等多个方面。</p>
<p>首先Spring Boot 使编码更简单。我们只需要在 Maven 中添加一项依赖并实现一个方法就可以提供微服务架构中所推崇的 RESTful 风格接口。</p>
<p>其次Spring Boot 使配置更简单。它把 Spring 中基于 XML 的功能配置方式转换为 Java Config同时提供了 .yml 文件来优化原有基于 .properties 和 .xml 文件的配置方案,.yml 文件对配置信息的组织更为直观方便,语义也更为强大。同时,基于 Spring Boot 的自动配置特性,对常见的各种工具和框架均提供了默认的 starter 组件来简化配置。</p>
<p>最后在部署方案上Spring Boot 也创造了一键启动的新模式。Spring Boot 部署包结构参考下图:</p>
<p><img src="assets/Ciqc1F-7WKCALlK_AAAvL2X5nlU081.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Ciqc1F-7WKCALlK_AAAvL2X5nlU081.png" alt="png" /></p>
<p>Spring Boot部署包结构</p>
<p>从图中我们可以看到,相较于传统模式下的 war 包Spring Boot 部署包既包含了业务代码和各种第三方类库,同时也内嵌了 HTTP 容器。这种包结构支持 java jar application.jar 方式的一键启动,不需要部署独立的应用服务器,通过默认内嵌 Tomcat 就可以运行整个应用程序。</p>
<p>最后,基于 Spring Boot 新提供的 Actuator 组件,开发和运维人员可以通过 RESTful 接口获取应用程序的当前运行时状态并对这些状态背后的度量指标进行监控和报警。例如可以通过“/env/{name}”端点获取系统环境变量、通过“/mapping”端点获取所有 RESTful 服务、通过“/dump”端点获取线程工作状态以及通过“/metrics/{name}”端点获取 JVM 性能指标等。</p>
<h3>剖析一个 Spring Web 应用程序</h3>
<p>针对一个基于 Spring Boot 开发的 Web 应用程序,其代码组织方式需要遵循一定的项目结构。在 02 讲中,如果不做特殊说明,我们都将使用 Maven 来管理项目工程中的结构和包依赖。一个典型的 Web 应用程序的项目结构如下图所示:</p>
<p><img src="assets/CgqCHl-7WKuAE6hIAABP4_ORBpU588.png" alt="Drawing 4.png" /></p>
<p><img src="assets/CgqCHl-7WKuAE6hIAABP4_ORBpU588.png" alt="png" /></p>
<p>Spring Boot Web 项目结构图</p>
<p>在上图中,有几个地方需要特别注意,我也在图中做了专门的标注,分别是包依赖、启动类、控制器类以及配置,让我们讲此部分内容分别做一些展开。</p>
<h4>包依赖</h4>
@@ -206,7 +206,7 @@ function hide_canvas() {
</ul>
<p>可以看到,这里包括了传统 Spring MVC 应用程序中会使用到的 spring-web 和 spring-webmvc 组件,因此 Spring Boot 在底层实现上还是基于这两个组件完成对 Web 请求响应流程的构建。</p>
<p>如果我们使用 Spring Boot 2.2.4 版本,你会发现它所依赖的 Spring 组件都升级到了 5.X 版本,如下图所示:</p>
<p><img src="assets/Ciqc1F-7WLOAHBZ_AABP1erBa_k988.png" alt="Drawing 5.png" /></p>
<p><img src="assets/Ciqc1F-7WLOAHBZ_AABP1erBa_k988.png" alt="png" /></p>
<p>Spring Boot 2.2.4 版本的包依赖示意图</p>
<p>在应用程序中引入 spring-boot-starter-web 组件就像引入一个普通的 Maven 依赖一样,如下所示。</p>
<pre><code>&lt;dependency&gt;
@@ -267,7 +267,7 @@ spring:
<h4>SpringCSS 整体架构</h4>
<p>在 SpringCSS 中,存在一个 customer-service这是一个 Spring Boot 应用程序,也是整个案例系统中的主体服务。在该服务中,我们可以将采用经典的分层架构,即将服务分成 Web 层、Service 层和 Repository 层。</p>
<p>在客服系统中我们知道其核心业务是生成客户工单。为此customer-service 一般会与用户服务 account-service 进行交互,但因为用户账户信息的更新属于低频事件,所以我们设计的实现方式是 account-service 通过消息中间件的方式将用户账户变更信息主动推送给 customerservice从而完成用户信息的获取操作。而针对 order-service其定位是订单系统customer-service 也需要从该服务中查询订单信息。SpringCSS 的整个系统交互过程如下图所示:</p>
<p><img src="assets/CgqCHl-7WMKAR1x_AACL4oIyVgU534.png" alt="Drawing 6.png" /></p>
<p><img src="assets/CgqCHl-7WMKAR1x_AACL4oIyVgU534.png" alt="png" /></p>
<p>SpringCSS 系统的整体架构图</p>
<p>在上图中,引出了构建 SpringCSS 的多项技术组件,在后续课程中我们会对这些技术组件做专题介绍。</p>
<h4>从案例实战到原理剖析</h4>

View File

@@ -173,7 +173,7 @@ function hide_canvas() {
<h3>创建第一个 Spring Boot Web 应用程序</h3>
<p>基于 Spring Boot 创建 Web 应用程序的方法有很多,但最简单、最直接的方法是使用 Spring 官方提供的 Spring Initializer 初始化模板。</p>
<p>初始化使用操作:直接访问 Spring Initializer 网站(<a href="https://start.spring.io/">http://start.spring.io/</a>),选择创建一个 Maven 项目并指定相应的 Group 和 Artifact然后在添加的依赖中选择 Spring Web点击生成即可。界面效果下图所示</p>
<p><img src="assets/Ciqc1F-83-KAYTQAAADMrUH44hQ767.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Ciqc1F-83-KAYTQAAADMrUH44hQ767.png" alt="png" /></p>
<p>使用 Spring Initializer 创建 Web 应用程序示意图</p>
<p>当然,对于有一定开发经验的同学而言,我们完全可以基于 Maven 本身的功能特性和结构,来生成上图中的代码工程。</p>
<p>接下来,我们参考 02 讲中关于 Controller 的创建基本方法,来为这个代码工程添加一些支持 RESTful 风格的 HTTP 端点,在这里我们同样创建一个 CustomerController 类,如下所示:</p>
@@ -201,7 +201,7 @@ public class CustomerController {
<p>那么,如何验证服务是否启动成功,以及 HTTP 请求是否得到正确响应呢?在 03 讲中,我们引入 Postman 来演示如何通过 HTTP 协议暴露的端点进行远程服务访问。</p>
<p>Postman 提供了强大的 Web API 和 HTTP 请求调试功能界面简洁明晰操作也比较方便快捷和人性化。Postman 能够发送任何类型的 HTTP 请求(如 GET、HEAD、POST、PUT 等),并能附带任何数量的参数和 HTTP 请求头Header</p>
<p>这时我们通过 Postman 访问“<a href="http://localhost:8083/customers/1">http://localhost:8083/customers/1</a>”端点可以得到如下图所示的HTTP响应结果说明整个服务已经启动成功。</p>
<p><img src="assets/Ciqc1F-83_CAMxyvAABNmxMQRXc212.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Ciqc1F-83_CAMxyvAABNmxMQRXc212.png" alt="png" /></p>
<p>好了,现在我们已经明白如何构建、打包以及运行一个简单的 Web 应用程序了,这是一切开发工作的起点,后续所有的案例代码我们都将通过这种方式展现在你面前,包括接下来要介绍的 Spring Boot 配置体系也是一样。</p>
<h3>Spring Boot 中的配置体系</h3>
<p>在 Spring Boot 中,其核心设计理念是对配置信息的管理采用约定优于配置。在这一理念下,则意味着开发人员所需要设置的配置信息数量比使用传统 Spring 框架时还大大减少。当然,今天我们关注的主要是如何理解并使用 Spring Boot 中的配置信息组织方式,这里就需要引出一个核心的概念,即 Profile。</p>
@@ -229,7 +229,7 @@ spring.datasource.username=root
spring.datasource.password=root
</code></pre>
<p>显然,类似这样的数据源通常会根据环境的不同而存在很多套配置。假设我们存在如下所示的配置文件集合:</p>
<p><img src="assets/CgqCHl-83_2AOKfAAAAKdvXQRn8485.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CgqCHl-83_2AOKfAAAAKdvXQRn8485.png" alt="png" /></p>
<p>多配置文件示意图</p>
<p>注意,这里有一个全局的 application.yml 配置文件以及多个局部的 profile 配置文件。那么,如何指定当前所使用的那一套配置信息呢?在 Spring Boot 中,我们可以在主 application.properties 中使用如下的配置方式来激活当前所使用的 Profile</p>
<pre><code>spring.profiles.active = test

View File

@@ -243,10 +243,10 @@ public class SpringCssConfig {
<p>可以看到这里通过创建一个 HashMap 来保存这些 Key-Value 对。类似的,我们也可以实现常见的一些数据结构的自动嵌入。</p>
<h4>为自定义配置项添加提示功能</h4>
<p>如果你已经使用过 Spring Boot 中的配置文件,并添加了一些内置的配置项,你就会发现,当我们输入某一个配置项的前缀时,诸如 IDEA、Eclipse 这样的IDE 就会自动弹出该前缀下的所有配置信息供你进行选择,效果如下:</p>
<p><img src="assets/Ciqc1F_AkJuAbymgAAA2lqR7sX4654.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Ciqc1F_AkJuAbymgAAA2lqR7sX4654.png" alt="png" /></p>
<p>IDE 自动提示配置项的效果图</p>
<p>上图的效果对于管理自定义的配置信息非常有用。如何实现这种效果呢?当我们在 application.yml 配置文件中添加一个自定义配置项时,会注意到 IDE 会出现一个提示,说明这个配置项无法被 IDE 所识别,如下所示:</p>
<p><img src="assets/CgqCHl_AkKuAfadcAAAT9yjkbDc611.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHl_AkKuAfadcAAAT9yjkbDc611.png" alt="png" /></p>
<p>IDE 无法识别配置项时的示意图</p>
<p>遇到这种提示时,我们是可以忽略的,因为它不会影响到任何执行效果。但为了达到自动提示效果,我们就需要生成配置元数据。生成元数据的方法也很简单,直接通过 IDE 的“Create metadata for 'springcss.order.point'”按钮,就可以选择创建配置元数据文件,这个文件的名称为 additional-spring-configuration-metadata.json文件内容如下所示</p>
<pre><code>{&quot;properties&quot;: [{
@@ -256,7 +256,7 @@ public class SpringCssConfig {
}]}
</code></pre>
<p>现在,假如我们在 application.properties 文件中输入“springcss”IDE 就会自动提示完整的配置项内容,效果如下所示:</p>
<p><img src="assets/CgqCHl_AkLOAOewrAAAZuAPdrEs491.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CgqCHl_AkLOAOewrAAAZuAPdrEs491.png" alt="png" /></p>
<p>IDE 自动提示 springcss 前缀的效果图</p>
<p>另外,假设我们需要为 springcss.order.point 配置项指定一个默认值,可以通过在元数据中添加一个&quot;defaultValue&quot;项来实现,如下所示:</p>
<pre><code>{&quot;properties&quot;: [{
@@ -267,7 +267,7 @@ public class SpringCssConfig {
}]}
</code></pre>
<p>这时候,在 IDE 中设置这个配置项时,就会提出该配置项的默认值为 10效果如下所示</p>
<p><img src="assets/Ciqc1F_AkL6AGrQwAAAa2ONwouI508.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Ciqc1F_AkL6AGrQwAAAa2ONwouI508.png" alt="png" /></p>
<p>IDE 自动提示包含默认值的 springcss 前缀效果图</p>
<h3>如何组织和整合配置信息?</h3>
<p>在上一课时中,我们提到了 Profile 概念Profile 可以认为是管理配置信息中的一种有效手段。今天,我们继续介绍另一种组织和整合配置信息的方法,这种方法同样依赖于前面介绍的 @ConfigurationProperties 注解。</p>
@@ -299,7 +299,7 @@ public class SpringCssConfig {
classpath:/
</code></pre>
<p>以下是按照优先级从高到低的顺序,如下所示:</p>
<p><img src="assets/CgqCHl_FvNiAAWTiAAAzJGEDupA763.png" alt="IiC6LiP3KXM49YyV__thumbnail.png" /></p>
<p><img src="assets/CgqCHl_FvNiAAWTiAAAzJGEDupA763.png" alt="png" /></p>
<p>Spring Boot 会全部扫描上图中的这四个位置,扫描规则是高优先级配置内容会覆盖低优先级配置内容。而如果高优先级的配置文件中存在与低优先级配置文件不冲突的属性,则会形成一种互补配置,也就是说会整合所有不冲突的属性。</p>
<h3>如何覆写内置的配置类?</h3>
<p>关于 Spring Boot 配置体系,最后值得介绍的就是如何覆写它所提供的配置类。在前面的课程中,我们已经反复强调 Spring Boot 内置了大量的自动配置如果我们不想使用这些配置就需要对它们进行覆写。覆写的方法有很多我们可以使用配置文件、Groovy 脚本以及 Java 代码。这里我们就以Java代码为例来简单演示覆写配置类的实现方法。</p>

View File

@@ -173,7 +173,7 @@ function hide_canvas() {
<p>数据访问层的构建可能会涉及多种不同形式的数据存储媒介本课程关注的是最基础也是最常用的数据存储媒介即关系型数据库针对关系型数据库Java 中应用最广泛的就是 JDBC 规范,今天我们将对这个经典规范展开讨论。</p>
<p>JDBC 是 Java Database Connectivity 的全称,它的设计初衷是提供一套能够应用于各种数据库的统一标准,这套标准需要不同数据库厂家之间共同遵守,并提供各自的实现方案供 JDBC 应用程序调用。</p>
<p>作为一套统一标准JDBC 规范具备完整的架构体系,如下图所示:</p>
<p><img src="assets/CgqCHl_J3f2AMaTEAADODtTLjeA995.png" alt="Drawing 2.png" /></p>
<p><img src="assets/CgqCHl_J3f2AMaTEAADODtTLjeA995.png" alt="png" /></p>
<p>JDBC 规范整体架构图</p>
<p>从上图中可以看到Java 应用程序通过 JDBC 所提供的 API 进行数据访问,而这些 API 中包含了开发人员所需要掌握的各个核心编程对象,下面我们一起来看下。</p>
<h3>JDBC 规范中有哪些核心编程对象?</h3>
@@ -311,7 +311,7 @@ connection.close();
</code></pre>
<p>这段代码中完成了对基于前面介绍的 JDBC API 中的各个核心编程对象的数据访问。上述代码主要面向查询场景,而针对用于插入数据的处理场景,我们只需要在上述代码中替换几行代码,即将“执行查询”和“获取查询结果进行处理”部分的查询操作代码替换为插入操作代码就行。</p>
<p>最后,我们梳理一下基于 JDBC 规范进行数据库访问的整个开发流程,如下图所示:</p>
<p><img src="assets/Ciqc1F_J3jmANBxqAADebgJ5BdU438.png" alt="Drawing 10.png" /></p>
<p><img src="assets/Ciqc1F_J3jmANBxqAADebgJ5BdU438.png" alt="png" /></p>
<p>基于 JDBC 规范进行数据库访问的开发流程图</p>
<p>针对前面所介绍的代码示例,我们明确地将基于 JDBC 规范访问关系型数据库的操作分成两大部分:一部分是准备和释放资源以及执行 SQL 语句,另一部分则是处理 SQL 执行结果。</p>
<p>而对于任何数据访问而言,前者实际上都是重复的。在上图所示的整个开发流程中,事实上只有“处理 ResultSet ”部分的代码需要开发人员根据具体的业务对象进行定制化处理。这种抽象为整个执行过程提供了优化空间。诸如 Spring 框架中 JdbcTemplate 这样的模板工具类就应运而生了,我们会在 07 讲中会详细介绍这个模板工具类。</p>

View File

@@ -179,7 +179,7 @@ function hide_canvas() {
}
</code></pre>
<p>在以上代码中,我们看到 Repository 接口只是一个空接口,通过泛型指定了领域实体对象的类型和 ID。在 Spring Data 中,存在一大批 Repository 接口的子接口和实现类,该接口的部分类层结构如下所示:</p>
<p><img src="assets/CgqCHl_TH_mAIaaLAABBNaldOqE595.png" alt="image" /></p>
<p><img src="assets/CgqCHl_TH_mAIaaLAABBNaldOqE595.png" alt="png" /></p>
<p>Repository 接口的部分类层结构图</p>
<p>可以看到 CrudRepository 接口是对 Repository 接口的最常见扩展,添加了对领域实体的 CRUD 操作功能,具体定义如下代码所示:</p>
<pre><code>public interface CrudRepository&lt;T, ID&gt; extends Repository&lt;T, ID&gt; {
@@ -284,11 +284,11 @@ public @interface Query {
<p>在上面的例子中,通过 findByFirstNameAndLastname 这样符合普通语义的方法名,并在参数列表中按照方法名中参数的顺序和名称(即第一个参数是 fistName第二个参数 lastName传入相应的参数Spring Data 就能自动组装 SQL 语句从而实现衍生查询。是不是很神奇?</p>
<p><strong>而想要使用方法名实现衍生查询,我们需要对 Repository 中定义的方法名进行一定约束。</strong></p>
<p>首先我们需要指定一些查询关键字,常见的关键字如下表所示:</p>
<p><img src="assets/Cip5yF_YhK6AcrMVAAQOamtdsF0627.png" alt="Lark20201215-174017.png" /></p>
<p><img src="assets/Cip5yF_YhK6AcrMVAAQOamtdsF0627.png" alt="png" /></p>
<p>方法名衍生查询中查询关键字列表</p>
<p>有了这些查询关键字后在方法命名上我们还需要指定查询字段和一些限制性条件。例如在前面的示例中我们只是基于“fistName”和“lastName”这两个字段做查询。</p>
<p>事实上,我们可以查询的内容非常多,下表列出了更多的方法名衍生查询示例,你可以参考下。</p>
<p><img src="assets/Cip5yF_YhLiAbg0pAAEzy-P0ZVA978.png" alt="Lark20201215-174023.png" /></p>
<p><img src="assets/Cip5yF_YhLiAbg0pAAEzy-P0ZVA978.png" alt="png" /></p>
<p>方法名衍生查询示例</p>
<p>在 Spring Data 中,方法名衍生查询的功能非常强大,上表中罗列的这些也只是全部功能中的一小部分而已。</p>
<p>讲到这里,你可能会问一个问题:如果我们在一个 Repository 中同时指定了 @Query 注解和方法名衍生查询,那么 Spring Data 会具体执行哪一个呢?要想回答这个问题,就需要我们对查询策略有一定的了解。</p>
@@ -316,7 +316,7 @@ public @interface Query {
<h3>Spring Data 中的组件</h3>
<p><strong>Spring Data 支持对多种数据存储媒介进行数据访问,表现为提供了一系列默认的 Repository包括针对关系型数据库的 JPA/JDBC Repository针对 MongoDB、Neo4j、Redis 等 NoSQL 对应的 Repository支持 Hadoop 的大数据访问的 Repository甚至包括 Spring Batch 和 Spring Integration 在内的系统集成的 Repository。</strong></p>
<p>在 Spring Data 的官方网站<a href="https://spring.io/projects/spring-data">https://spring.io/projects/spring-data</a> 中,列出了其提供的所有组件,如下图所示:</p>
<p><img src="assets/CgqCHl_TICWAOAMHAAAkcFfMwis206.png" alt="image" /></p>
<p><img src="assets/CgqCHl_TICWAOAMHAAAkcFfMwis206.png" alt="png" /></p>
<p>Spring Data 所提供的组件列表(来自 Spring Data 官网)</p>
<p>根据官网介绍Spring Data 中的组件可以分成四大类核心模块Main modules、社区模块Community modules、关联模块Related modules和正在孵化的模块Modules in Incubation。例如前面介绍的 Respository 和多样化查询功能就在核心模块 Spring Data Commons 组件中。</p>
<p><strong>这里,我特别想强调下的是正在孵化的模块,它目前只包含一个组件,即 Spring Data R2DBC。</strong> R2DBC 是<a href="https://github.com/r2dbc/">Reactive Relational Database Connectivity</a> 的简写,代表响应式关系型数据库连接,相当于是响应式数据访问领域的 JDBC 规范。</p>

View File

@@ -332,7 +332,7 @@ public class AccountController {
public void updateAccount(@RequestBody Account account) {
</code></pre>
<p>如果使用 @RequestBody 注解,我们可以在 Postman 中输入一个 JSON 字符串来构建输入对象,如下代码所示:</p>
<p><img src="assets/Cip5yF_YgIGACIM7AAAr5-G8i7M764.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cip5yF_YgIGACIM7AAAr5-G8i7M764.png" alt="png" /></p>
<p>使用 Postman 输入 JSON 字符串发起 HTTP 请求示例图</p>
<p>通过以上内容的讲解,我们发现使用注解的操作很简单,接下来我们有必要探讨下控制请求输入的规则。</p>
<p><strong>关于控制请求输入的规则,关键在于按照 RESTful 风格的设计原则设计 HTTP 端点,对于这点业界也存在一些约定。</strong></p>

View File

@@ -218,7 +218,7 @@ public RestTemplate customRestTemplate(){
<p>这里我们创建了一个 HttpComponentsClientHttpRequestFactory 工厂类,它是 ClientHttpRequestFactory 接口的一个实现类。通过设置连接请求超时时间 ConnectionRequestTimeout、连接超时时间 ConnectTimeout 等属性,我们对 RestTemplate 的默认行为进行了定制化处理。</p>
<h4>使用 RestTemplate 访问 Web 服务</h4>
<p>在远程服务访问上RestTemplate 内置了一批常用的工具方法,我们可以根据 HTTP 的语义以及 RESTful 的设计原则对这些工具方法进行分类,如下表所示。</p>
<p><img src="assets/CgqCHl_lfhWAWs-DAACna_DO-CA750.png" alt="Lark20201225-135202.png" /></p>
<p><img src="assets/CgqCHl_lfhWAWs-DAACna_DO-CA750.png" alt="png" /></p>
<p>RestTemplate 中的方法分类表</p>
<p>接下来,我们将基于该表对 RestTemplate 中的工具方法进行详细介绍并给出相关示例。不过在此之前,我们想先来讨论一下请求的 URL。</p>
<p>在一个 Web 请求中,我们可以通过请求路径携带参数。在使用 RestTemplate 时,我们也可以在它的 URL 中嵌入路径变量,示例代码如下所示:</p>

View File

@@ -179,12 +179,12 @@ function hide_canvas() {
<p>在用户账户信息变更时account-service 首先会发送一个消息告知某个用户账户信息已经发生变化,然后通知所有对该消息感兴趣的服务。而在 SpringCSS 案例中,这个服务就是 customer-service相当于是这个消息的订阅者和消费者。</p>
<p>通过这种方式customer-service 就可以快速获取用户账户变更消息,从而正确且高效地处理本地的用户账户数据。</p>
<p>整个场景的示意图见下图:</p>
<p><img src="assets/CgqCHl_ivJ2AZMUlAABJyFFnmMc174.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CgqCHl_ivJ2AZMUlAABJyFFnmMc174.png" alt="png" /></p>
<p>用户账户更新场景中的消息通信机制</p>
<p>上图中我们发现,消息通信机制使得我们不必花费太大代价即可实现整个交互过程,简单而方便。</p>
<h4>消息通信机制简介</h4>
<p>消息通信机制的整体工作流程如下图所示:</p>
<p><img src="assets/CgqCHl_ivKyAXQR_AABdUOvR5RQ298.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHl_ivKyAXQR_AABdUOvR5RQ298.png" alt="png" /></p>
<p>消息通信机制示意图</p>
<p>上图中位于流程中间的就是各种消息中间件,<strong>消息中间件</strong>一般提供了消息的发送客户端和接收客户端组件,这些客户端组件会嵌入业务服务中。</p>
<p><strong>消息的生产者</strong>负责产生消息,在实际业务中一般由业务系统充当生产者;而<strong>消息的消费者</strong>负责消费消息,在实际业务中一般是后台系统负责异步消费。</p>
@@ -196,7 +196,7 @@ function hide_canvas() {
<p>在讨论如何使用 KafkaTemplate 实现与 Kafka 之间的集成方法之前,我们先来简单了解 Kafka 的基本架构,再引出 Kafka 中的几个核心概念。</p>
<h4>Kafka 基本架构</h4>
<p>Kafka 基本架构参考下图,从中我们可以看到 Broker、Producer、Consumer、Push、Pull 等消息通信系统常见概念在 Kafka 中都有所体现,生产者使用 Push 模式将消息发布到 Broker而消费者使用 Pull 模式从 Broker 订阅消息。</p>
<p><img src="assets/Ciqc1F_ivLaAVULIAABdyI31l0s036.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Ciqc1F_ivLaAVULIAABdyI31l0s036.png" alt="png" /></p>
<p>Kafka 基本架构图</p>
<p><strong>在上图中我们注意到Kafka 架构图中还使用了 Zookeeper。</strong></p>
<p>Zookeeper 中存储了 Kafka 的元数据及消费者消费偏移量Offset其作用在于实现 Broker 和消费者之间的负载均衡。因此,如果我们想要运行 Kafka首先需要启动 Zookeeper再启动 Kafka 服务器。</p>
@@ -271,7 +271,7 @@ public @interface KafkaListener {
<p>设计消费者组的目的是应对集群环境下的多服务实例问题。显然,如果采用发布-订阅模式会导致一个服务的不同实例可能会消费到同一条消息。</p>
<p>为了解决这个问题Kafka 中提供了消费者组的概念。一旦我们使用了消费组,一条消息只能被同一个组中的某一个服务实例所消费。</p>
<p>消费者组的基本结构如下图所示:</p>
<p><img src="assets/Cip5yF_ivMqAG6llAAA6iqKiy-M353.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Cip5yF_ivMqAG6llAAA6iqKiy-M353.png" alt="png" /></p>
<p>Kafka 消费者组示意图</p>
<p>使用 @KafkaListener 注解时,我们把它直接添加在处理消息的方法上即可,如下代码所示:</p>
<pre><code>@KafkaListener(topics = “demo.topic”)

View File

@@ -315,7 +315,7 @@ public void handlerEvent(DemoEvent event) {
<p>首先,我们来回顾下《多维配置:如何使用 Spring Boot 中的配置体系?》的内容介绍,在 Spring Boot 中,我们可以通过 Profile 有效管理针对不同场景和环境的配置信息。</p>
<p>而在 SpringCSS 案例中Kafka、ActiveMQ 及 16 讲将要介绍的 RabbitMQ 都是消息中间件,在案例系统运行过程中,我们需要选择其中一种中间件演示消息发送和接收到过程,这样我们就需要针对不同的中间件设置不同的 Profile 了。</p>
<p>在 account-service 中,我们可以根据 Profile 构建如下所示的配置文件体系。</p>
<p><img src="assets/Cip5yF_ivRuAdYV0AAANYpYvrk4834.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cip5yF_ivRuAdYV0AAANYpYvrk4834.png" alt="png" /></p>
<p>account-service 中的配置文件</p>
<p>从以上图中可以看到:根据三种不同的中间件,我们分别提供了三个配置文件。以其中的 application-activemq.yml 为例,其包含的配置项如下代码所示:</p>
<pre><code>spring:

View File

@@ -178,22 +178,22 @@ function hide_canvas() {
<p><strong>所谓认证</strong>,即首先需要明确“你是谁”这个问题,也就是说系统能针对每次访问请求判断出访问者是否具有合法的身份标识。</p>
<p>一旦明确了 “你是谁”,我们就能判断出“你能做什么”,这个步骤就是<strong>授权</strong>。一般来说,通用的授权模型都是基于权限管理体系,即对资源、权限、角色和用户的进行组合处理的一种方案。</p>
<p>当我们把认证与授权结合起来后,即先判断资源访问者的有效身份,然后确定其对这个资源进行访问的合法权限,整个过程就形成了对系统进行安全性管理的一种常见解决方案,如下图所示:</p>
<p><img src="assets/Cip5yF_9eRGAaOFAAACdJ5DTEOU093.png" alt="Drawing 1.png" /></p>
<p><img src="assets/Cip5yF_9eRGAaOFAAACdJ5DTEOU093.png" alt="png" /></p>
<p>基于认证和授权机制的资源访问安全性示意图</p>
<p>上图就是一种通用方案,而在不同的应用场景及技术体系下,系统可以衍生出很多具体的实现策略,比如 Web 应用系统中的认证和授权模型虽然与上图类似,但是在具体设计和实现过程中有其特殊性。</p>
<p>在 Web 应用体系中,因为认证这部分的需求相对比较明确,所以我们需要构建一套完整的存储体系来保存和维护用户信息,并且确保这些用户信息在处理请求的过程中能够得到合理利用。</p>
<p>而授权的情况相对来说复杂些,比如对某个特定的 Web 应用程序而言,我们面临的第一个问题是如何判断一个 HTTP 请求具备访问自己的权限。解决完这个第一个问题后,就算这个请求具备访问该应用程序的权限,并不意味着它能够访问其所具有的所有 HTTP 端点,比如业务上的某些核心功能还是需要具备较高的权限才能访问,这就涉及我们需要解决的第二个问题——如何对访问的权限进行精细化管理?如下图所示:</p>
<p><img src="assets/Ciqc1F_9eRqANunuAAB5IYtX-6s596.png" alt="Drawing 3.png" /></p>
<p><img src="assets/Ciqc1F_9eRqANunuAAB5IYtX-6s596.png" alt="png" /></p>
<p>Web 应用程序访问授权效果示意图</p>
<p>在上图中,假设该请求具备对 Web 应用程序的访问权限,但不具备访问应用程序中端点 1 的权限,如果想实现这种效果,一般我们的做法是引入角色体系:首先对不同的用户设置不同等级的角色(即角色等级不同对应的访问权限也不同),再把每个请求绑定到某个角色(即该请求具备了访问权限)。</p>
<p>接下来我们把认证和授权进行结合,梳理出了 Web 应用程序访问场景下的安全性实现方案,如下图所示:</p>
<p><img src="assets/CgqCHl_9eSOANBbmAACWXd-B08c635.png" alt="Drawing 5.png" /></p>
<p><img src="assets/CgqCHl_9eSOANBbmAACWXd-B08c635.png" alt="png" /></p>
<p>认证和授权整合示意图</p>
<p>从上图我们可以看到,用户首先通过请求传递用户凭证完成用户认证,然后根据该用户信息中所具备的角色信息获取访问权限,最终完成对 HTTP 端点的访问授权。</p>
<p>对一个 Web 应用程序进行安全性设计时,我们首先需要考虑认证和授权,因为它们是核心考虑点。在技术实现场景中,只要涉及用户认证,势必会涉及用户密码等敏感信息的加密。针对用户密码的场景,我们主要使用单向散列加密算法对敏感信息进行加密。</p>
<p>关于单向散列加密算法它常用于生成消息摘要Message Digest主要特点为单向不可逆和密文长度固定同时具备“碰撞”少的优点即明文的微小差异会导致生成的密文完全不同。其中常见的单向散列加密实现算法为 MD5Message Digest 5和 SHASecure Hash Algorithm。而在 JDK 自带的 MessageDigest 类中,因为它已经包含了这些算法的默认实现,所以我们直接调用方法即可。</p>
<p>在日常开发过程中,对于密码进行加密的典型操作时序图如下所示:</p>
<p><img src="assets/CgpVE1_24-6ACO9pAABbleIQe14786.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CgpVE1_24-6ACO9pAABbleIQe14786.png" alt="png" /></p>
<p>单向散列加密与加盐机制</p>
<p>上图中我们引入了加盐Salt机制进一步提升了加密数据的安全性。所谓加盐就是在初始化明文数据时系统自动往明文中添加一些附加数据然后再进行散列。</p>
<p>目前,单向散列加密及加盐思想已被广泛用于系统登录过程中的密码生成和校验过程中,比如接下来我们将要引入的 Spring Security 框架。</p>
@@ -203,11 +203,11 @@ function hide_canvas() {
<p>这一讲我们先不对如何使用 Spring Security 框架展开说明,而是先从高层次梳理该框架对前面提到的各项安全性需求提供的架构设计。</p>
<h4>Spring Security 中的过滤器链</h4>
<p>与业务中大多数处理 Web 请求的框架对比后,我们发现 Spring Security 中采用的是管道-过滤器Pipe-Filter架构模式如下图所示</p>
<p><img src="assets/CgqCHl_9eTCACoPnAACF6H2W2KI357.png" alt="Drawing 8.png" /></p>
<p><img src="assets/CgqCHl_9eTCACoPnAACF6H2W2KI357.png" alt="png" /></p>
<p>管道-过滤器架构模式示意图</p>
<p>在上图中我们可以看到,处理业务逻辑的组件称为过滤器,而处理结果的相邻过滤器之间的连接件称为管道,它们构成了一组过滤器链,即 Spring Security 的核心。</p>
<p>项目一旦启动,过滤器链将会实现自动配置,如下图所示:</p>
<p><img src="assets/Ciqc1F_9eTmAM6OYAAD0pSOc1Xg188.png" alt="Drawing 10.png" /></p>
<p><img src="assets/Ciqc1F_9eTmAM6OYAAD0pSOc1Xg188.png" alt="png" /></p>
<p>Spring Security 中的过滤器链</p>
<p>在上图中,我们看到了 BasicAuthenticationFilter、UsernamePasswordAuthenticationFilter 等几个常见的 Filter这些类可以直接或间接实现 Servlet 类中的 Filter 接口,并完成某一项具体的认证机制。例如,上图中的 BasicAuthenticationFilter 用来认证用户的身份,而 UsernamePasswordAuthenticationFilter 用来检查输入的用户名和密码,并根据认证结果来判断是否将结果传递给下一个过滤器。</p>
<p>这里请注意,整个 Spring Security 过滤器链的末端是一个 FilterSecurityInterceptor本质上它也是一个 Filter但它与其他用于完成认证操作的 Filter 不同,因为它的核心功能是用来实现权限控制,即判定该请求是否能够访问目标 HTTP 端点。因为我们可以把 FilterSecurityInterceptor 对权限控制的粒度划分到方法级别,所以它能够满足前面提到的精细化访问控制。</p>
@@ -250,7 +250,7 @@ function hide_canvas() {
}
</code></pre>
<p>围绕上述方法,通过翻阅 Spring Security 源代码,我们引出了该框架中一系列核心类,并梳理了它们之间的交互结构,如下图所示:</p>
<p><img src="assets/Cip5yF_9edmAN_FuAAD0M-rwB1I928.png" alt="Lark20210112-182830.png" /></p>
<p><img src="assets/Cip5yF_9edmAN_FuAAD0M-rwB1I928.png" alt="png" /></p>
<p>Spring Security 核心类图</p>
<p>上图中的很多类,通过名称我们就能明白它的含义和作用。</p>
<p>以位于左下角的 SecurityContextHolder 为例,它是一个典型的 Holder 类,存储了应用的安全上下文对象 SecurityContext包含系统请求中最近使用的认证信息。这里我们大胆猜想它的内部肯定使用了 ThreadLocal 来确保线程访问的安全性。</p>

View File

@@ -180,14 +180,14 @@ function hide_canvas() {
<hr />
<p><strong>请注意,只要我们在代码工程中添加了上述依赖,包含在该工程中的所有 HTTP 端点都将被保护起来。</strong></p>
<p>例如,在 SpringCSS 案例的 account-service 中,我们知道存在一个 AccountController 且它暴露了一个“accounts/ /{accountId}”端点。现在,我们启动 account-service 服务并访问上述端点,弹出了如下图所示的界面内容:</p>
<p><img src="assets/CgqCHmABN02AZfgTAAAR0uEiE5U744.png" alt="Drawing 0.png" /></p>
<p><img src="assets/CgqCHmABN02AZfgTAAAR0uEiE5U744.png" alt="png" /></p>
<p>添加 Spring Security 之后自动出现的登录界面</p>
<p>同时,在系统的启动控制台日志中,我们发现了如下所示的新的日志信息。</p>
<pre><code>Using generated security password: 17bbf7c4-456a-48f5-a12e-a680066c8f80
</code></pre>
<p>在这里可以看到Spring Security 为我们自动生成了一个密码我们可以基于“user”这个账号及上述密码登录这个界面抽空你也可以尝试下。</p>
<p>如果我们使用了 Postman 可视化 HTTP 请求工具可以设置授权类型为“Basic Auth”并输入对应的用户名和密码完成对 HTTP 端点的访问,设置界面如下图所示:</p>
<p><img src="assets/CgqCHmABN1uAC5vsAAA3VhGEBJY812.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHmABN1uAC5vsAAA3VhGEBJY812.png" alt="png" /></p>
<p>使用 Postman 来完成认证信息的设置</p>
<p>事实上,在引入 spring-boot-starter-security 依赖之后Spring Security 会默认创建一个用户名为“user”的账号。很显然每次启动应用时通过 Spring Security 自动生成的密码都会有所变化,因此它不适合作为一种正式的应用方法。</p>
<p>如果我们想设置登录账号和密码,最简单的方式是通过配置文件。例如,我们可以在 account-service 的 application.yml 文件中添加如下代码所示的配置项:</p>
@@ -336,7 +336,7 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
</code></pre>
<p>Spring Security 中内置了一大批 PasswordEncoder 接口的实现类,如下图所示:</p>
<p><img src="assets/CgqCHmABN4qACniMAAB2uCwQA6M741.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CgqCHmABN4qACniMAAB2uCwQA6M741.png" alt="png" /></p>
<p>Spring Security 中的 PasswordEncoder 实现类</p>
<p>上图中,比较常用的算法如 SHA-256 算法的 StandardPasswordEncoder、bcrypt 强哈希算法的 BCryptPasswordEncoder 等。而在实际案例中,我们使用的是 BCryptPasswordEncoder它的 encode 方法如下代码所示:</p>
<pre><code>public String encode(CharSequence rawPassword) {

View File

@@ -197,7 +197,7 @@ function hide_canvas() {
<p>最后, httpBasic() 语句使用 HTTP 协议中的 Basic Authentication 方法完成认证。</p>
<p>18 讲中我们也演示了如何使用 Postman 完成认证的方式,这里就不过多赘述了。</p>
<p>当然Spring Security 中还提供了很多其他有用的配置方法供开发人员灵活使用,下表中我们进行了列举,一起来看下。</p>
<p><img src="assets/CgpVE2AGpjqARlFfAAE9RHgwruA457.png" alt="Lark20210119-172757.png" /></p>
<p><img src="assets/CgpVE2AGpjqARlFfAAE9RHgwruA457.png" alt="png" /></p>
<p><strong>基于上表中的配置方法,我们就可以通过 HttpSecurity 实现自定义的授权策略。</strong></p>
<p>比方说,我们希望针对“/orders”根路径下的所有端点进行访问控制且只允许认证通过的用户访问那么可以创建一个继承了 WebSecurityConfigurerAdapter 类的 SpringCssSecurityConfig并覆写其中的 configure(HttpSecurity http) 方法来实现,如下代码所示:</p>
<pre><code>@Configuration

View File

@@ -291,7 +291,7 @@ function hide_canvas() {
<li><strong>操作控制类:</strong> 在原生端点中只提供了一个关闭应用的端点,即 /shutdown 端点。</li>
</ul>
<p>根据 Spring Boot Actuator 默认提供的端点列表,我们将部分常见端点的类型、路径和描述梳理在如下表格中,仅供参考。</p>
<p><img src="assets/Cip5yGAKfl6Af_yWAAIDoRxLU2E765.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Cip5yGAKfl6Af_yWAAIDoRxLU2E765.png" alt="png" /></p>
<p>通过访问上表中的各个端点,我们就可以获取自己感兴趣的监控信息了。例如访问了<a href="http://localhost:8082/actuator/health">http://localhost:8082/actuator/health</a>端点,我们就可以得到如下所示的 account-service 基本状态。</p>
<pre><code>{
&quot;status&quot;:&quot;UP&quot;
@@ -328,7 +328,7 @@ function hide_canvas() {
<h4>扩展 Info 端点</h4>
<p>Info 端点用于暴露 Spring Boot 应用的自身信息。在 Spring Boot 内部,它把这部分工作委托给了一系列 <a href="https://github.com/spring-projects/spring-boot/tree/v1.4.1.RELEASE/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java">InfoContributor</a> 对象,而 Info 端点会暴露所有 <a href="https://github.com/spring-projects/spring-boot/tree/v1.4.1.RELEASE/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/info/InfoContributor.java">InfoContributor</a> 对象所收集的各种信息。</p>
<p>在Spring Boot 中包含了很多自动配置的 InfoContributor 对象,常见的 InfoContributor 及其描述如下表所示:</p>
<p><img src="assets/CgqCHmAKfoOARrjaAADoOGMdQb4610.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHmAKfoOARrjaAADoOGMdQb4610.png" alt="png" /></p>
<p>以上表中的 EnvironmentInfoContributor 为例通过在配置文件中添加格式以“info”作为前缀的配置段我们就可以定义 Info 端点暴露的数据。添加完成后我们将看到所有在“info”配置段下的属性都将被自动暴露。</p>
<p>比如你可以将如下所示配置信息添加到配置文件 application.yml 中:</p>
<pre><code>info:

View File

@@ -202,7 +202,7 @@ public class AdminApplication {
</code></pre>
<p>此时,我们会发现使用这种方式构建 Spring Boot Admin Server 就是这么简单。</p>
<p>接下来我们启动这个 Spring Boot 应用程序,并打开 Web 界面,就能看到如下所示的效果:</p>
<p><img src="assets/CgqCHmAKf-WAFILtAAAsRr3Jgfg085.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgqCHmAKf-WAFILtAAAsRr3Jgfg085.png" alt="png" /></p>
<p>Spring Boot Admin Server 启动效果图</p>
<p>从图中我们可以看到,目前还没有一个应用程序与 Admin Server 有关联。如果想将应用程序与 Admin Server 进行关联,我们还需要对原有的 Spring Boot 应用程序做一定的改造。</p>
<p>首先,我们在 Maven 依赖中引入对 Spring Boot Admin Client 组件的依赖,如下代码所示:</p>
@@ -220,7 +220,7 @@ public class AdminApplication {
</code></pre>
<p><strong>注意:这里的 9000 就是 Admin Server 的服务器端口。</strong></p>
<p>现在我们启动这个应用程序,就会发现 Admin Server 中已经出现了这个应用的名称和地址,如下图所示:</p>
<p><img src="assets/Ciqc1GAKf--ATR-aAAAxAFlSEVc094.png" alt="Drawing 2.png" /></p>
<p><img src="assets/Ciqc1GAKf--ATR-aAAAxAFlSEVc094.png" alt="png" /></p>
<p>Spring Boot Admin Server 添加了应用程序之后的效果图</p>
<p>在图中,我们看到 APPLICATIONS 和 INSTANCES 的数量都是 1代表 Admin Server 管理着一个应用程序,而该应用程序只有一个运行实例。在界面的下方,我们还能看到这个应用的名称及实例地址。这里你可以尝试使用不同的端口启动应用程序的不同实例,然后观察这个列表的变化。</p>
<h4>基于注册中心构建 Admin Server</h4>
@@ -249,7 +249,7 @@ public class EurekaServerApplication {
</code></pre>
<p>注意:在上面的代码中,我们在启动类上加了一个@EnableEurekaServer 注解。在 SpringCloud 中,包含 @EnableEurekaServer 注解的服务也就是一个 Eureka 服务器组件。这样Eureka 服务就构建完毕了。</p>
<p>同样Eureka 服务还为我们提供了一个可视化的 UI 界面,它可以用来观察当前注册到 Eureka 中的应用程序信息,如下图所示:</p>
<p><img src="assets/Cip5yGAKgDuAcMmLAAB_2n8YYlw199.png" alt="Drawing 4.png" /></p>
<p><img src="assets/Cip5yGAKgDuAcMmLAAB_2n8YYlw199.png" alt="png" /></p>
<p>Eureka 服务监控页面</p>
<p>接下来,我们需要 Admin Server 也做相应调整。首先,我们在 pom 文件中添加一个对 spring-cloud-starter-netflix-eureka-client 这个 Eureka 客户端组件的依赖:</p>
<pre><code>&lt;dependency&gt;
@@ -279,7 +279,7 @@ public class EurekaServerApplication {
<p>根据 Spring Boot Admin 官方 Github 上的介绍Admin Server 监控系统提供了一套完整的可视化方案。基于 Admin Server健康状态、JVM、内存、Micrometer 的度量、线程、HTTP 跟踪等核心功能都可以通过可视化的 UI 界面进行展示。</p>
<h4>监控系统运行时关键指标</h4>
<p>注意到 Admin Server 菜单中有一个“Wallboard”点击该菜单我们就可以看到一面应用墙如下图所示</p>
<p><img src="assets/Cip5yGAKgE2AdGRqAABpdUogwxw880.png" alt="Drawing 5.png" /></p>
<p><img src="assets/Cip5yGAKgE2AdGRqAABpdUogwxw880.png" alt="png" /></p>
<p>Admin Server 应用墙</p>
<p>点击应用墙中的某个应用,我们就能进入针对该应用的监控信息主界面。在该界面的左侧,包含了监控功能的各级目录,如下图所示:</p>
<p><img src="assets/Ciqc1GATr3-ATLpGAAOgyIEu7Sk069.png" alt="图片6.png" /></p>
@@ -290,11 +290,11 @@ public class EurekaServerApplication {
<p>Admin Server 中的 JVM 监控信息</p>
<p>这些 JVM 数据都是通过可视化的方式进行展现,并随着运行时状态的变化而实时更新。</p>
<p>在 21 讲中,我们详细讨论了 Spring Boot Actuator 中的度量指标。而在 Admin Server 中同样存在一个“Metrics”菜单展示效果如下图所示</p>
<p><img src="assets/CgqCHmAKgGSAaC9sAAA-CBnX4LI723.png" alt="Drawing 8.png" /></p>
<p><img src="assets/CgqCHmAKgGSAaC9sAAA-CBnX4LI723.png" alt="png" /></p>
<p>Admin Server 中的 Metrics 信息</p>
<p>在“Metrics”菜单中开发人员可以通过对各种条件进行筛选然后添加对应的度量指标。比如上图中我们针对 HTTP 请求中 /actuator/health 端点进行了过滤,从而得到了度量结果。</p>
<p>接着我们一起看看系统环境方面的属性,因为这方面的属性非常之多,所以 Admin Server 也提供了一个过滤器,如下图所示:</p>
<p><img src="assets/Ciqc1GAKgGuAaRNpAABIghaOFVg132.png" alt="Drawing 9.png" /></p>
<p><img src="assets/Ciqc1GAKgGuAaRNpAABIghaOFVg132.png" alt="png" /></p>
<p>Admin Server 中的 Environment 信息</p>
<p>在上图中通过输入“spring.”参数,我们就能获取一系列与该参数相关的环境属性。</p>
<p>日志也是我们监控系统的一个重要途径,在 Admin Server 的“Loggers”菜单中可以看到该应用程序的所有日志信息如下图所示</p>
@@ -303,7 +303,7 @@ public class EurekaServerApplication {
<p>通过”springcss”关键词对这些日志进行过滤我们就可以获取 SpringCSS 案例中的日志详细了,图中也显示了每个日志记录器对应的日志级别。</p>
<p>最后,我们来看一下 Admin Server 中的“JVM”菜单该菜单下存在两个子菜单“Thread Dump”和“Heap Dump”。</p>
<p>以“Thread Dump”为例尽管 Actuator 提供了 /threaddump 端点,但开发人员只能获取触发该端点时的 Dump 信息,而 Admin Server 则提供了一个连续性的可视化监控界面,如下图所示:</p>
<p><img src="assets/CgqCHmAKgHuAcsFSAABDhuAgJBY760.png" alt="Drawing 11.png" /></p>
<p><img src="assets/CgqCHmAKgHuAcsFSAABDhuAgJBY760.png" alt="png" /></p>
<p>Admin Server 中的 Thread Dump 信息</p>
<p>点击图中的色条,我们就可以获取每一个线程的详细信息了,这里你可以尝试做一些分析。</p>
<h4>控制访问安全性</h4>
@@ -323,7 +323,7 @@ public class EurekaServerApplication {
password: &quot;springcss_password&quot;
</code></pre>
<p>重启 Admin Server 后,再次访问 Web 界面时,就需要我们输入用户名和密码了,如下图所示:</p>
<p><img src="assets/CgpVE2AKgImAOicJAAAiQ2MCOts677.png" alt="Drawing 12.png" /></p>
<p><img src="assets/CgpVE2AKgImAOicJAAAiQ2MCOts677.png" alt="png" /></p>
<p>Admin Server 的安全登录界面</p>
<h3>小结与预告</h3>
<p>可视化监控一直是开发和运维人员管理应用程序运行时状态的基础诉求,而 Spring Boot Admin 组件正是这样一款可视化的工具。它基于 Spring Boot Actuator 中各个端点所暴露的监控信息,并加以整合和集成。今天的内容首先介绍了构建 Admin Server 以及 Admin Client 的方法,并剖析了 Admin Server 中所具有的一整套的可视化解决方案。</p>

View File

@@ -187,7 +187,7 @@ function hide_canvas() {
</code></pre>
<p>其中,最后一个依赖用于导入与 JUnit 相关的功能组件。</p>
<p>然后,通过 Maven 查看 spring-boot-starter-test 组件的依赖关系,我们可以得到如下所示的组件依赖图:</p>
<p><img src="assets/CgpVE2AYyZ6ADMDGAAVPdtkysNI580.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CgpVE2AYyZ6ADMDGAAVPdtkysNI580.png" alt="png" /></p>
<p>spring-boot-starter-test 组件的依赖关系图</p>
<p>在《案例驱动:如何剖析一个 Spring Web 应用程序》中我们提到Spring Boot 使得编码、配置、部署和监控工作更简单。事实上Spring Boot 也能让测试工作更加简单。</p>
<p>从上图中可以看到,在代码工程的构建路径中,我们引入了一系列组件初始化测试环境。比如 JUnit、JSON Path、AssertJ、Mockito、Hamcrest 等,这里我们有必要对这些组件进行展开说明。</p>
@@ -216,7 +216,7 @@ public class CustomerApplication {
</code></pre>
<p>针对上述 Bootstrap 类,我们可以通过编写测试用例的方式,验证 Spring 容器能否正常启动。</p>
<p>为了添加测试用例我们有必要梳理一下代码的组织结构梳理完后就呈现了如下图所示的customer-service 工程中代码的基本目录结构。</p>
<p><img src="assets/CgpVE2AYycmAXHkdAAdjApgDr7s414.png" alt="Drawing 3.png" /></p>
<p><img src="assets/CgpVE2AYycmAXHkdAAdjApgDr7s414.png" alt="png" /></p>
<p>customer-service 工程代码目录结构</p>
<p>基于 Maven 的默认风格,我们将在 src/test/java 和 src/test/resources 包下添加各种测试用例代码和配置文件,正如上图所示。</p>
<p>打开上图中的 ApplicationContextTests.java 文件,我们可以得到如下所示的测试用例代码:</p>

View File

@@ -201,14 +201,14 @@ function hide_canvas() {
<p>目前 Spring 已经演进到 5.X 版本,随着 Spring 5 的正式发布我们迎来了响应式编程Reactive Programming的全新发展时期。</p>
<p>Spring 5 中内嵌了与数据管理相关的响应式数据访问、与系统集成相关的响应式消息通信,以及与 Web 服务相关的响应式 Web 框架等多种响应式组件,从而极大简化了响应式应用程序的开发过程和难度。</p>
<p>以支持响应式 Web 的 Spring WebFlux 为例,这里我们给出它的架构图,如下图所示:</p>
<p><img src="assets/Cgp9HWAdAZCAB17fAACQeZA8Cyk925.png" alt="Drawing 0.png" /></p>
<p><img src="assets/Cgp9HWAdAZCAB17fAACQeZA8Cyk925.png" alt="png" /></p>
<p>Spring WebFlux 架构图(来自 Spring 官网)</p>
<p>在图中我们可以看到,上图左侧为基于 Spring Webflux 的技术栈,右侧为基于 Spring MVC 的技术栈。我们知道传统的 Spring MVC 是在 Java EE 的 Servlet 标准之上进行构建的,该标准本身就是阻塞式和同步式。而 Spring WebFlux 基于响应式流进行构建,因此我们可以使用它来构建异步非阻塞的服务。</p>
<p>随着 WebFlux 等响应式编程技术的兴起,它为构建具有即时响应性和回弹性的应用程序提供了一个很好的技术基础。</p>
<p>我们知道一个分布式系统中,可能存在数十乃至数百个独立的 Web 应用程序,它们之间互相通信以完成复杂的业务流程,而这个过程势必涉及大量的 I/O 操作。</p>
<p>一旦涉及 I/O 操作,尤其是阻塞式 I/O 操作将会整体增加系统的延迟并降低吞吐量。如果我们能够在复杂的流程中集成非阻塞、异步通信机制就可以高效处理跨服务之间的网络请求。针对这种场景WebFlux 也是一种非常有效的解决方案。</p>
<p>下面我们再来看一下 Spring Boot 2 的另一张官网架构图,如下图所示:</p>
<p><img src="assets/CioPOWAdAZ2AMVhnAACWjgTTFkY662.png" alt="Drawing 1.png" /></p>
<p><img src="assets/CioPOWAdAZ2AMVhnAACWjgTTFkY662.png" alt="png" /></p>
<p>Spring Boot 2 架构图(来自 Spring 官网)</p>
<p>从图中我们可以看到,上图底部将 Spring Data 明确划分为两大类型:一类是支持 JDBC、JPA 和部分 NoSQL 的传统 Spring Data Repository另一类则是支持 Mongo、Cassandra、Redis、Couchbase 等的响应式 Spring Data Reactive Repository。</p>
<p>这张图背后的意义在于Spring Boot 可以帮助我们构建从 Web 服务层到数据访问层的全栈式响应式编程技术,从而确保系统的各个环节都具备即时响应性。</p>