<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://spring.didispace.com/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <atom:link href="https://spring.didispace.com/rss.xml" rel="self" type="application/rss+xml"/>
    <title>程序猿DD - Spring专区</title>
    <link>https://spring.didispace.com/</link>
    <description>分享关于Spring、Spring Boot、Spring Cloud、Spring Data等关于Spring框架的学习总结和实践经验分享</description>
    <language>zh-CN</language>
    <pubDate>Tue, 24 Jun 2025 02:11:09 GMT</pubDate>
    <lastBuildDate>Tue, 24 Jun 2025 02:11:09 GMT</lastBuildDate>
    <generator>vuepress-plugin-feed2</generator>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <category>Spring Boot</category>
    <category>Spring AI</category>
    <category>Spring Data</category>
    <category>Spring</category>
    <category>Java</category>
    <item>
      <title>使用 Spring AI 实现文本转 SQL</title>
      <link>https://spring.didispace.com/article/20250624-spring-ai-text-to-sql.html</link>
      <guid>https://spring.didispace.com/article/20250624-spring-ai-text-to-sql.html</guid>
      <source url="https://spring.didispace.com/rss.xml">使用 Spring AI 实现文本转 SQL</source>
      <description>使用 Spring AI 实现文本转 SQL 1. 概述 随着技术的发展，现代应用程序越来越多地采用自然语言界面来简化用户与系统的交互。这种方式在数据检索场景中尤其实用，让非技术用户也能通过简单的自然语言提问来获取所需信息。 文本转 SQL 聊天机器人就是这样一个典型应用。它就像是人类语言和数据库之间的翻译官，通过大型语言模型（LLM）将用户的自然语言问题转换为可执行的 SQL 查询，然后在数据库中执行查询并返回结果。 本文将带你使用 Spring AI 构建一个文本转 SQL 聊天机器人。我们会从零开始配置数据库模式和初始数据，然后实现一个能够理解自然语言并生成 SQL 查询的智能聊天机器人。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 24 Jun 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 使用 Spring AI 实现文本转 SQL</h1>
<h2> 1. 概述</h2>
<p>随着技术的发展，现代应用程序越来越多地采用自然语言界面来简化用户与系统的交互。这种方式在数据检索场景中尤其实用，让非技术用户也能通过简单的自然语言提问来获取所需信息。</p>
<p>文本转 SQL 聊天机器人就是这样一个典型应用。它就像是人类语言和数据库之间的翻译官，通过大型语言模型（LLM）将用户的自然语言问题转换为可执行的 SQL 查询，然后在数据库中执行查询并返回结果。</p>
<p><strong>本文将带你使用 Spring AI 构建一个文本转 SQL 聊天机器人</strong>。我们会从零开始配置数据库模式和初始数据，然后实现一个能够理解自然语言并生成 SQL 查询的智能聊天机器人。</p>
<h2> 2. 项目搭建</h2>
<p>在开始实现聊天机器人之前，我们需要先添加必要的依赖项并完成应用程序的基础配置。</p>
<p><strong>本文将使用 Anthropic 的 Claude 模型来构建文本转 SQL 聊天机器人</strong>。当然，你也可以选择其他 AI 模型，比如通过 Hugging Face 或 Ollama 使用本地 LLM，因为具体的 AI 模型选择并不影响整体实现方案。</p>
<h3> 2.1. 添加依赖</h3>
<p>首先，我们需要在项目的 <code>pom.xml</code> 文件中添加必要的依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个 Anthropic starter 依赖是 Anthropic Message API 的封装，我们将通过它来与 Claude 模型进行交互。</p>
<p>接下来，在 <code>application.yaml</code> 文件中配置 Anthropic API 密钥和聊天模型：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里使用 <code>${}</code> 属性占位符从环境变量中加载 API 密钥，这是一种安全的配置方式。</p>
<p>我们选择了 <strong>Claude 4 Opus</strong> 模型，这是 Anthropic 目前最智能的模型，对应的模型 ID 是 <code>claude-opus-4-20250514</code>。你也可以根据实际需求选择其他模型。</p>
<p>完成上述配置后，<strong>Spring AI 会自动创建 <code>ChatModel</code> 类型的 Bean，这样我们就可以直接与指定的模型进行交互了</strong>。</p>
<h3> 2.2. 使用 Flyway 设计数据库表</h3>
<p>接下来，我们需要设计数据库模式。这里使用 Flyway 来管理数据库迁移脚本，确保数据库结构的版本控制。</p>
<p><strong>我们将在 MySQL 数据库中创建一个简单的巫师管理系统数据库</strong>。和 AI 模型选择一样，具体的数据库类型并不影响我们的实现方案。</p>
<p>首先，在 <code>src/main/resources/db/migration</code> 目录下创建名为 <code>V01__creating_database_tables.sql</code> 的迁移脚本，用于创建核心数据表：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里创建了两个核心表：<code>hogwarts_houses</code> 表用于存储霍格沃茨各学院的信息，<code>wizards</code> 表用于存储巫师的详细信息。<strong><code>wizards</code> 表通过外键约束与 <code>hogwarts_houses</code> 表关联，形成一对多的关系</strong>。</p>
<p>接下来，创建 <code>V02__adding_hogwarts_houses_data.sql</code> 文件来初始化 <code>hogwarts_houses</code> 表的数据：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里通过 INSERT 语句插入四个霍格沃茨学院的基础信息，包括创始人、学院颜色和象征动物。</p>
<p>同样地，创建 <code>V03__adding_wizards_data.sql</code> 迁移脚本来初始化 <code>wizards</code> 表的数据：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>定义好这些迁移脚本后，<strong>Flyway 会在应用程序启动时自动发现并按顺序执行这些脚本</strong>。</p>
<h2> 3. 配置 AI 提示词</h2>
<p>为了让 LLM 能够针对我们的数据库模式生成准确的 SQL 查询，我们需要精心设计一个系统提示词。</p>
<p>在 <code>src/main/resources</code> 目录下创建 <code>system-prompt.st</code> 文件：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在这个系统提示词中，<strong>我们明确指示 LLM 只能生成 SELECT 查询，并且要能识别和拒绝 SQL 注入和 DoS 攻击</strong>。</p>
<p>注意提示词模板中的 <code>ddl</code> 占位符，<strong>我们会在后续步骤中将其替换为实际的数据库结构信息</strong>。</p>
<p>另外，为了进一步保障数据库安全，<strong>建议为应用程序创建一个只读权限的 MySQL 用户</strong>：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上述 SQL 命令创建了一个只读用户，并仅授予其对相关数据表的查询权限，这样即使出现意外情况也不会对数据造成破坏。</p>
<h2> 4. 构建文本转 SQL 聊天机器人</h2>
<p>完成基础配置后，现在我们可以开始构建文本转 SQL 聊天机器人了。</p>
<h3> 4.1. 定义核心 Bean</h3>
<p>首先，我们需要为聊天机器人定义必要的 Bean 组件：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>首先定义 <code>PromptTemplate</code> Bean，通过 <code>@Value</code> 注解注入系统提示模板文件和数据库 DDL 脚本。<strong>关键是要用实际的数据库结构信息填充 <code>ddl</code> 占位符，这样 LLM 就能准确理解我们的数据库结构并生成正确的 SQL 查询</strong>。</p>
<p>然后基于 <code>ChatModel</code> 和 <code>PromptTemplate</code> 创建 <code>ChatClient</code> Bean。<strong><code>ChatClient</code> 是我们与 Claude 模型交互的核心组件</strong>。</p>
<h3> 4.2. 实现业务服务</h3>
<p>接下来，<strong>我们需要实现核心的业务服务来处理 SQL 生成和执行流程</strong>。</p>
<p>首先创建 <code>SqlGenerator</code> 服务类，负责将自然语言转换为 SQL 查询：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在 <code>generate()</code> 方法中，我们接收用户的自然语言问题，通过 <code>chatClient</code> 发送给 LLM 进行处理。</p>
<p>为了确保安全性，我们会验证返回的结果确实是 SELECT 查询。<strong>如果 LLM 返回的不是 SELECT 查询，就抛出自定义的 <code>InvalidQueryException</code> 异常</strong>。</p>
<p>接下来创建 <code>SqlExecutor</code> 服务类，负责执行生成的 SQL 查询：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>在 <code>execute()</code> 方法中，我们使用 <code>EntityManager</code> 来执行原生 SQL 查询并返回结果。如果查询结果为空，则抛出自定义的 <code>EmptyResultException</code> 异常</strong>。</p>
<h3> 4.3. 提供 REST API</h3>
<p>完成服务层的实现后，<strong>我们需要提供 REST API 接口供外部调用</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>POST <code>/query</code> 接口的处理流程是：接收自然语言问题 → 通过 <code>sqlGenerator</code> 生成 SQL 查询 → 使用 <code>sqlExecutor</code> 执行查询获取结果 → 将结果封装在 <code>QueryResponse</code> 中返回。</p>
<h2> 5. 测试聊天机器人</h2>
<p>现在我们可以通过 API 接口来测试文本转 SQL 聊天机器人的功能了。</p>
<p>为了便于调试，先在 <code>application.yaml</code> 文件中开启 SQL 日志，这样可以在控制台看到生成的 SQL 语句：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来，<strong>使用 HTTPie CLI 工具来调用 API 接口，测试聊天机器人的功能</strong>：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这里我们向聊天机器人发送了一个自然语言问题，来看看它的响应结果：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，<strong>聊天机器人成功理解了我们关于斯莱特林学院巫师的查询需求，并准确返回了三个巫师的姓名和血统信息</strong>。</p>
<p>我们再来看看应用程序日志中 LLM 生成的 SQL 查询语句：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>生成的 SQL 查询完美地理解了我们的自然语言需求，<strong>通过 JOIN 操作关联 <code>wizards</code> 和 <code>hogwarts_houses</code> 表来查找斯莱特林学院的巫师，并使用 LIMIT 限制返回三条记录</strong>。</p>
<h2> 6. 总结</h2>
<p>本文详细介绍了如何使用 Spring AI 构建一个文本转 SQL 聊天机器人。</p>
<p>我们从项目搭建开始，完成了 AI 模型和数据库的配置工作。然后构建了一个能够理解自然语言并生成准确 SQL 查询的智能聊天机器人，并基于霍格沃茨巫师管理系统进行了实际演示。最后通过 REST API 提供服务接口，并验证了整个系统的可用性。</p>
<p>这种文本转 SQL 的解决方案可以大大降低数据查询的技术门槛，让非技术用户也能轻松获取数据库中的信息，在实际业务场景中具有很高的应用价值。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 虚拟线程 vs WebFlux：谁更胜一筹？</title>
      <link>https://spring.didispace.com/article/20250619-spring-boot-webflux-virtural-threads.html</link>
      <guid>https://spring.didispace.com/article/20250619-spring-boot-webflux-virtural-threads.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 虚拟线程 vs WebFlux：谁更胜一筹？</source>
      <description>Spring Boot 虚拟线程 vs WebFlux：谁更胜一筹？ Spring Boot 作为构建现代 Java 应用程序的强大框架，为开发者提供了多种处理并发和可扩展性的解决方案。其中最受关注的两种方案是 Spring Boot 虚拟线程（Java 21 引入）和 Spring Boot WebFlux（基于响应式编程）。虽然两者都致力于优化资源利用率和提升高并发处理能力，但在编程范式、复杂度和适用场景方面却存在显著差异。本文将深入对比这两种技术方案，帮助您为项目选择最合适的解决方案。</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 19 Jun 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 虚拟线程 vs WebFlux：谁更胜一筹？</h1>
<p>Spring Boot 作为构建现代 Java 应用程序的强大框架，为开发者提供了多种处理并发和可扩展性的解决方案。其中最受关注的两种方案是 <strong>Spring Boot 虚拟线程</strong>（Java 21 引入）和 <strong>Spring Boot WebFlux</strong>（基于响应式编程）。虽然两者都致力于优化资源利用率和提升高并发处理能力，但在编程范式、复杂度和适用场景方面却存在显著差异。本文将深入对比这两种技术方案，帮助您为项目选择最合适的解决方案。</p>
<h2> 技术方案概览</h2>
<h3> Spring Boot 虚拟线程</h3>
<p>虚拟线程是 Java 21 中 Project Loom 项目的核心成果，它是一种由 JVM 管理的轻量级线程。相比于直接映射到操作系统线程、资源开销较大的传统平台线程，虚拟线程能够让数百万个线程在少量操作系统线程池上高效运行。这一特性使其特别适合 I/O 密集型工作场景，比如包含数据库调用、API 请求或文件操作的 Web 应用程序。</p>
<p>在 Spring Boot 3.2+ 版本中，虚拟线程实现了无缝集成。只需简单配置应用程序启用虚拟线程（如设置 <code>spring.threads.virtual.enabled=true</code>），每个 HTTP 请求就能在独立的虚拟线程上运行，既简化了并发处理，又无需改变传统的阻塞式编程模型。</p>
<h3> Spring Boot WebFlux</h3>
<p>Spring WebFlux 于 Spring 5 版本引入，基于 Project Reactor 构建的响应式编程理念。它专门针对非阻塞、异步处理场景设计，通过事件循环和背压机制，让单个线程能够处理多个请求。WebFlux 在高并发、低延迟场景下表现卓越，特别适合流数据处理或大量 I/O 操作的微服务架构。</p>
<p>WebFlux 要求开发者转向响应式编程模式，需要使用 <code>Mono</code> 和 <code>Flux</code> 等响应式类型，并通过函数式编程进行操作链接。相比传统 Spring MVC 的命令式编程风格，这种转变会带来一定的学习成本和复杂度。</p>
<h2> 核心对比维度</h2>
<h3> 1. 编程模式</h3>
<ul>
<li><strong>虚拟线程</strong>：延续了熟悉的命令式、阻塞编程模式。开发者可以编写传统的顺序代码（如使用 <code>Thread.sleep</code>、JDBC 或阻塞式 HTTP 客户端），无需关心并发处理细节。虚拟线程在底层自动处理可扩展性问题，让代码更易于维护和调试。</li>
<li><strong>WebFlux</strong>：必须采用响应式、非阻塞编程模式。开发者需要从流处理、背压控制和异步操作的角度思考问题。这对不熟悉响应式编程的团队来说学习曲线较陡，链式操作（如 <code>flatMap</code>、<code>subscribe</code>）也会增加代码阅读难度。</li>
</ul>
<p><strong>优势方</strong>：虚拟线程，凭借其简洁性和易用性胜出。</p>
<h3> 2. 性能与可扩展性</h3>
<ul>
<li><strong>虚拟线程</strong>：在 I/O 密集型场景下表现优异，如包含数据库查询或外部 API 调用的 Web 应用。虚拟线程大幅降低了线程管理开销，使应用能够处理成千上万甚至数百万的并发请求。早期基准测试（如 Spring Boot 3.2 配合 Java 21）显示，虚拟线程在典型 Web 场景中能达到与 WebFlux 相当的吞吐量，某些情况下内存占用更低。</li>
<li><strong>WebFlux</strong>：专为高并发、低延迟场景优化，特别是在处理流数据或长连接（如 WebSockets）时。在特定场景下，WebFlux 能够超越虚拟线程，比如在有限硬件资源上处理海量并发连接时，其事件循环模型优势明显。不过，响应式技术栈通常需要精心调优，以避免回调地狱或资源泄漏等问题。</li>
</ul>
<p><strong>优势方</strong>：平分秋色，具体取决于应用场景。虚拟线程适合通用 Web 应用；WebFlux 更适合流处理或超低延迟场景。</p>
<h3> 3. 生态兼容性</h3>
<ul>
<li><strong>虚拟线程</strong>：与现有 Spring MVC 应用和阻塞式类库（如 JPA、JDBC、RestTemplate）完全兼容。开发者只需最少的代码修改就能迁移到虚拟线程，是遗留项目的理想升级方案。不过，虚拟线程仅支持 Java 21+，需要升级 JVM 版本。</li>
<li><strong>WebFlux</strong>：需要配套的响应式类库（如 R2DBC、WebClient），必须摒弃阻塞式 API。许多传统类库（如 JPA、JDBC）无法直接使用，开发者被迫采用响应式替代方案或寻找变通办法。这会限制生态系统支持，增加现有项目的重构工作量。</li>
</ul>
<p><strong>优势方</strong>：虚拟线程，凭借更广泛的兼容性和更低的迁移成本获胜。</p>
<h3> 4. 开发效率</h3>
<ul>
<li><strong>虚拟线程</strong>：让开发者专注于业务逻辑，无需纠结并发处理或响应式编程模式，显著提升开发效率。调试过程简单直观，堆栈跟踪清晰易读。虚拟线程还减少了复杂线程池配置的需求。</li>
<li><strong>WebFlux</strong>：响应式编程的学习曲线和额外复杂度可能拖慢开发进度。调试响应式代码更具挑战性，堆栈跟踪往往涉及反应器操作符和异步边界。团队可能需要专门培训才能熟练使用 WebFlux。</li>
</ul>
<p><strong>优势方</strong>：虚拟线程，在开发速度和维护便利性方面更胜一筹。</p>
<h3> 5. 适用场景</h3>
<ul>
<li><strong>虚拟线程</strong>：非常适合传统 Web 应用、REST API 和包含 I/O 密集型操作（如数据库查询、HTTP 调用）的微服务。对于需要升级现有 Spring MVC 应用或构建低复杂度新应用的团队来说，是绝佳选择。</li>
<li><strong>WebFlux</strong>：最适合高并发、低延迟应用场景，如实时数据流、即时通讯应用或需要长连接的系统。在非阻塞 I/O 至关重要的微服务架构中也表现出色。</li>
</ul>
<p><strong>优势方</strong>：因场景而异，需要根据具体项目需求判断。</p>
<h2> 实战代码对比</h2>
<p>让我们通过简单的 REST 接口来对比两种技术方案。</p>
<h3> 虚拟线程（Spring MVC）</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>代码简洁，采用命令式风格。</li>
<li>配置后自动运行在虚拟线程上。</li>
<li>兼容 JPA 等阻塞式 API。</li>
</ul>
<h3> WebFlux 方案</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>需要响应式服务和 <code>Mono</code> 返回类型。</li>
<li>非阻塞处理，但编写和维护更复杂。</li>
<li>需要配套的响应式类库（如 R2DBC）。</li>
</ul>
<h2> 技术选型指南</h2>
<p><strong>选择虚拟线程的情况</strong>：</p>
<ul>
<li>构建或维护传统 Web 应用或 REST API</li>
<li>团队偏好简单的命令式编程模式</li>
<li>需要升级现有 Spring MVC 应用</li>
<li>工作负载以中高并发的 I/O 密集型为主</li>
<li>希望保持广泛的生态兼容性，避免大规模重构</li>
</ul>
<p><strong>选择 WebFlux 的情况</strong>：</p>
<ul>
<li>构建高并发、低延迟应用（如流处理、WebSocket 应用）</li>
<li>架构要求端到端的非阻塞 I/O</li>
<li>团队具备响应式编程经验</li>
<li>启动全新项目，可以采用响应式技术栈</li>
</ul>
<h2> 总结</h2>
<p>Spring Boot 虚拟线程和 WebFlux 都是构建可扩展应用的强大工具，但各自适用于不同的需求场景。<strong>虚拟线程</strong> 凭借其简洁性、兼容性和以最小改动实现高并发处理的能力，成为大多数项目的首选方案。它为传统 Spring MVC 应用带来了革命性改变，在保持简单性的同时提供了接近响应式的性能表现。而 <strong>WebFlux</strong> 在需要超低延迟或流处理的特殊场景中仍然具有不可替代的价值，其响应式模型在这些领域表现卓越。</p>
<p>对于 2025 年的大多数开发者而言，虚拟线程在性能和开发效率之间找到了最佳平衡点，使其成为默认选择。除非项目有特殊需求必须使用 WebFlux 的响应式特性，否则虚拟线程都是更明智的选择。建议根据项目需求、团队技术储备和生态系统约束来做出最终决策。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot + Multipart 文件上传：为什么你的应用在 10MB 后会失败</title>
      <link>https://spring.didispace.com/article/20250612-spring-boot-multipart-file.html</link>
      <guid>https://spring.didispace.com/article/20250612-spring-boot-multipart-file.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot + Multipart 文件上传：为什么你的应用在 10MB 后会失败</source>
      <description>Spring Boot + Multipart 文件上传：为什么你的应用在 10MB 后会失败 说实话，文件上传问题是后端开发中最令人头疼的 bug 之一。我至今还记得第一次遇到 Spring Boot 应用拒绝上传大于 10MB 文件的情况。小文件上传一切正常，但当我尝试上传更大的文件时——砰！各种错误接踵而至。如果你也遇到过这种情况，相信我，你绝对不是一个人在战斗。 接下来我会详细解释问题的原因、解决方案，以及如何让你的 Spring Boot 应用顺利处理大文件上传。 问题一：&amp;quot;为什么超过 10MB 就报错？&amp;quot; 用户们愉快地上传头像和文档，一切都很顺利。直到有人尝试上传一个大约 12MB 的 PDF 文件，他们没有看到成功提示，而是收到了 &amp;quot;413 Payload Too Large&amp;quot; 错误。日志中显示：</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 12 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot + Multipart 文件上传：为什么你的应用在 10MB 后会失败</h1>
<p>说实话，文件上传问题是后端开发中最令人头疼的 bug 之一。我至今还记得第一次遇到 Spring Boot 应用拒绝上传大于 10MB 文件的情况。小文件上传一切正常，但当我尝试上传更大的文件时——砰！各种错误接踵而至。如果你也遇到过这种情况，相信我，你绝对不是一个人在战斗。</p>
<p>接下来我会详细解释问题的原因、解决方案，以及如何让你的 Spring Boot 应用顺利处理大文件上传。</p>
<h2> 问题一："为什么超过 10MB 就报错？"</h2>
<p>用户们愉快地上传头像和文档，一切都很顺利。直到有人尝试上传一个大约 12MB 的 PDF 文件，他们没有看到成功提示，而是收到了 "413 Payload Too Large" 错误。日志中显示：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>当时想："小文件明明可以正常上传，为什么大文件就不行了？"</p>
<p><strong>经验总结：</strong>
Spring Boot 默认将文件上传大小限制为 10MB。</p>
<h2> 问题二：如何解决上传限制？</h2>
<p>经过一番搜索，我找到了解决方案：需要调整应用配置。在 <code>application.properties</code> 或 <code>application.yaml</code> 中添加：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>或者使用 YAML 格式：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>配置完成后，50MB 以内的文件上传就能正常工作了。</p>
<p><strong>经验总结：</strong>
你可以自由控制上传限制，只需要将配置属性调整为所需的大小即可。</p>
<h2> 问题三："配置了还是不行，为什么？"</h2>
<p>几周后，有用户尝试上传一个 20MB 的文件，应用又崩溃了，出现了奇怪的 "connection reset" 错误，后端没有明确的错误信息。这次我意识到问题出在服务器层面。如果你的 Spring Boot 应用运行在反向代理（如 Nginx 或 Apache）后面，这些服务器也有自己的文件大小限制。</p>
<p>对于 Nginx，需要设置：</p>
<div class="language-nginx line-numbers-mode" data-ext="nginx"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>对于 Apache，需要设置：</p>
<div class="language-apache line-numbers-mode" data-ext="apache"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>解决这个问题后，文件上传终于对所有用户都正常工作了。</p>
<p><strong>经验总结：</strong>
不仅仅是 Spring Boot 的问题——你的 Web 服务器或代理服务器也可能有文件大小限制。</p>
<h2> 问题四：上传大文件时出现 "OutOfMemoryError"</h2>
<p>最后，我发现当有人尝试上传真正的大文件（比如 200MB）时，应用会因为 "OutOfMemoryError" 而崩溃。即使提高了文件大小限制，应用也无法正常处理。</p>
<p>解决方案很简单：</p>
<ul>
<li>保持文件大小限制在合理范围内。除非确实需要，否则不要让用户上传超大文件。</li>
<li>直接将上传的文件保存到磁盘，而不是加载到内存中。使用<a href="https://docs.spring.io/spring-framework/reference/web/webmvc/multipart.html#webmvc-multipart-forms-streaming" target="_blank" rel="noopener noreferrer">流式处理</a>。</li>
</ul>
<p><strong>经验总结：</strong>
大文件上传会消耗服务器内存，需要谨慎处理！</p>
<h2> 核心要点</h2>
<ul>
<li><strong>Spring Boot</strong> 有默认的文件大小限制（10MB）</li>
<li>可以通过 <code>application.properties</code> 或 <code>application.yaml</code> 提高限制</li>
<li><strong>Web 服务器</strong>（如 Nginx 或 Apache）也有自己的限制——记得一并配置！</li>
<li>对于超大文件，使用流式处理并注意内存使用情况</li>
</ul>
<p><strong>总结</strong></p>
<p>如果你的文件上传在 10MB 后持续失败，不要慌张。检查你的 Spring Boot 配置、Web 服务器设置和内存使用情况。调整相关配置，并用真实文件进行测试。这样可以为你（和你的用户）节省大量的麻烦！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 中为什么不要将 DTO 与 Entity 混合使用</title>
      <link>https://spring.didispace.com/article/20250613-spring-boot-dtos-vs-entities.html</link>
      <guid>https://spring.didispace.com/article/20250613-spring-boot-dtos-vs-entities.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 中为什么不要将 DTO 与 Entity 混合使用</source>
      <description>Spring Boot 中为什么不要将 DTO 与 Entity 混合使用 刚开始学习 Spring Boot 的时候，我对 DTOs（数据传输对象）和 Entities（实体类）没有太深入的思考。通常的做法就是创建一个类，然后到处使用——数据库操作、API 接口、业务服务等等。看起来一切都很顺利……直到问题出现。 在这篇文章中，我想分享一下关于 DTOs 和 Entities 的一些经验教训。我会解释它们各自的作用，为什么需要将它们分离，以及忽略这一点会带来哪些实际问题。 最初的错误做法</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 13 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 中为什么不要将 DTO 与 Entity 混合使用</h1>
<p>刚开始学习 Spring Boot 的时候，我对 <strong>DTOs</strong>（数据传输对象）和 <strong>Entities</strong>（实体类）没有太深入的思考。通常的做法就是创建一个类，然后到处使用——数据库操作、API 接口、业务服务等等。看起来一切都很顺利……直到问题出现。</p>
<p>在这篇文章中，我想分享一下关于 DTOs 和 Entities 的一些经验教训。我会解释它们各自的作用，为什么需要将它们分离，以及忽略这一点会带来哪些实际问题。</p>
<h2> 最初的错误做法</h2>
<p>在某个项目的初期，我创建了一个 <code>User</code> 类，包含 <code>id</code>、<code>name</code>、<code>email</code> 和 <code>password</code> 等字段。然后在以下所有场景中都使用这同一个类：</p>
<ul>
<li>数据库持久化（作为 JPA 实体）</li>
<li>接收前端传递的数据</li>
<li>返回 API 响应结果</li>
</ul>
<p>开发速度确实很快，代码量也不多。但随着项目的发展，问题逐渐暴露出来。</p>
<h2> 问题的出现</h2>
<p>有一天，产品经理提出了新需求：前端需要<strong>展示用户的公开资料</strong>，但<strong>不能包含邮箱和密码信息</strong>。</p>
<p>听起来很简单，对吧？</p>
<p>但问题是，我的 API 直接返回完整的 <code>User</code> 实体对象，响应中包含了<strong>敏感数据</strong>，比如密码哈希值。</p>
<p>我们只能临时用 <code>@JsonIgnore</code> 注解来解决，但接下来另一个确实需要返回邮箱的 API 又出现了问题。</p>
<p>这时候才意识到，一个类已经无法满足所有需求了。</p>
<h2> DTO 的解决方案</h2>
<p>我创建了一个新的 <code>UserDTO</code> 类，只包含需要在响应中返回的字段——比如 <code>id</code>、<code>name</code>，可能还有 <code>profilePicture</code>。</p>
<p>现在可以精确控制<strong>哪些数据可以进入或离开</strong>系统。</p>
<p>示例代码：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>数据映射的方式：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>后来，我开始使用 <strong>MapStruct</strong> 来自动化这个映射过程，不过那是后话了。</p>
<h2> 为什么混合使用 DTO 和 Entity 是个坏习惯</h2>
<p>以下是我在混合使用 DTOs 和 Entities 时遇到的实际问题：</p>
<ol>
<li>在 API 接口中<strong>意外暴露敏感数据</strong>（如密码或内部 ID）</li>
<li>修改某个字段时<strong>影响多个 API 接口</strong></li>
<li>数据库重构时<strong>难以避免对前端的影响</strong></li>
<li><strong>接受了不应该直接保存到数据库的用户输入</strong></li>
</ol>
<h2> 另一个案例：输入 DTO vs. Entity</h2>
<p>在处理 API 输入时我也遇到了类似问题。我有一个实体类包含 <code>createdAt</code>、<code>isAdmin</code> 和 <code>status</code> 等字段，但我不希望用户能够为这些字段传值。</p>
<p>由于在 API 请求中直接使用了实体类，结果有人发送了一个包含 <code>"isAdmin": true</code> 的 <code>POST</code> 请求，而系统竟然真的生效了！</p>
<p>从那时起，我为用户创建操作单独创建了一个<strong>输入 DTO</strong>。它只包含我们期望用户提供的字段：<code>name</code>、<code>email</code>、<code>password</code>。其他字段都在后端自动设置。</p>
<h2> 血的教训</h2>
<ul>
<li>Entities 专门用于<strong>数据库操作</strong></li>
<li>DTOs 专门用于 <strong>API 接口和业务逻辑</strong></li>
<li>不要混合使用，即使看起来更简单</li>
<li>根据需要为输入和输出创建不同的 DTOs</li>
<li>如果不想手写映射代码，可以使用 <strong>MapStruct</strong> 或 <strong>ModelMapper</strong> 等工具</li>
</ul>
<p>刚开始的时候，创建单独的 DTO 类确实感觉像是额外的工作量。但后来发现，它为我节省了大量的调试时间，并且让我对应用程序的行为有了更好的控制。</p>
<h2> 总结</h2>
<p>刚入门的时候，没有人告诉我要分离 DTOs 和 Entities。我只能通过踩坑来学习——各种 bug、奇怪的行为、接口异常等等。</p>
<p>如果你正在开发 Spring Boot 应用，建议<strong>尽早创建专门的 DTOs</strong>。相信我，未来的你会感谢现在的决定。</p>
<p>你在混合使用 DTOs 和 Entities 时遇到过类似问题吗？欢迎在评论区分享你的经验。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring @Transactional 详解：何时使用、为什么使用、如何使用</title>
      <link>https://spring.didispace.com/article/20250611-spring-transactional.html</link>
      <guid>https://spring.didispace.com/article/20250611-spring-transactional.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring @Transactional 详解：何时使用、为什么使用、如何使用</source>
      <description>Spring @Transactional 详解：何时使用、为什么使用、如何使用 本文是关于 Spring Framework 中 @Transactional 注解的完整教程，面向有一定基础的开发者，将详细介绍注解功能、使用场景、核心特性、优缺点，并配有清晰的图表说明。 ✅ @Transactional 是什么？</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 11 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring @Transactional 详解：何时使用、为什么使用、如何使用</h1>
<p>本文是关于 <strong>Spring Framework</strong> 中 <code>@Transactional</code> 注解的完整教程，面向有一定基础的开发者，将详细介绍<strong>注解功能</strong>、<strong>使用场景</strong>、<strong>核心特性</strong>、<strong>优缺点</strong>，并配有清晰的<strong>图表说明</strong>。</p>
<h2> ✅ <code>@Transactional</code> 是什么？</h2>
<p><code>@Transactional</code> 是 Spring 提供的事务注解，用于<strong>声明式事务管理</strong>。它可以确保被标记的方法（或代码块）<strong>运行在数据库事务环境中</strong>。</p>
<h2> 🛠️ 什么时候使用</h2>
<p>以下场景建议使用 <code>@Transactional</code>：</p>
<p>当你需要执行<strong>多个数据库操作</strong>，且这些操作必须具备<strong>原子性</strong>（要么全部成功，要么全部失败）。</p>
<ul>
<li>需要在出现错误或异常时<strong>自动回滚</strong>操作。</li>
<li>希望避免<strong>手动管理事务</strong>（比如直接使用 JDBC 或 EntityManager 的事务 API）。</li>
</ul>
<h2> ⚙️ <code>@Transactional</code> 的核心特性</h2>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>🔁 事务传播机制（常用类型）</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>🔒 事务隔离级别</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> ✅ 主要优点</h2>
<ul>
<li><strong>简单易用</strong>，支持声明式事务管理。</li>
<li>与 <strong>JPA</strong>、<strong>Hibernate</strong>、<strong>Spring Data</strong> 等框架<strong>完美集成</strong>。</li>
<li><strong>异常时自动回滚</strong>，无需手动处理。</li>
<li><strong>减少样板代码</strong>，提高开发效率。</li>
<li>支持<strong>嵌套事务</strong>等高级特性。</li>
</ul>
<h2> ❌ 使用限制</h2>
<ul>
<li>使用不当容易<strong>踩坑</strong>，需要充分理解其原理。</li>
<li><strong>基于代理实现</strong>：对 <code>private</code> 方法和<strong>同类内部调用</strong>无效。</li>
<li><strong>默认只对运行时异常回滚</strong>，检查异常需要额外配置。</li>
<li>复杂的嵌套调用中<strong>排查回滚原因比较困难</strong>。</li>
</ul>
<p>📦 代码示例</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>🔁 自定义回滚配置</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 📉 不使用事务的风险</h2>
<ul>
<li>部分数据库操作成功，部分失败。</li>
<li>导致<strong>数据不一致</strong>问题。</li>
</ul>
<h2> 🧪 最佳实践</h2>
<ul>
<li>建议在<strong>Service 层的公共方法</strong>上使用。</li>
<li><strong>避免在 Controller 层或 DAO 层</strong>使用 <code>@Transactional</code>。</li>
<li><strong>注意代理失效问题</strong>：不要在同一个类内部调用 <code>@Transactional</code> 方法。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 4.0.0 预览版新特性详解：深入解读 Spring Framework 7.0.0</title>
      <link>https://spring.didispace.com/article/20250610-spring-boot-4-0-0-preview.html</link>
      <guid>https://spring.didispace.com/article/20250610-spring-boot-4-0-0-preview.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 4.0.0 预览版新特性详解：深入解读 Spring Framework 7.0.0</source>
      <description>Spring Boot 4.0.0 预览版新特性详解：深入解读 Spring Framework 7.0.0 你是否注意到创建新 Spring Boot 项目时出现的最新选项？Spring Boot 4.0.0 预览版现已发布，基于最新的 Spring Framework 7.0.0 🌱。这个版本引入了众多激动人心的新特性，不仅提升了开发效率，改善了空值安全性，还简化了 Web 应用程序的开发流程。本文将深入探讨这些重要变化，并提供完整的代码示例和单元测试，帮助你快速上手这个预览版本。让我们一起来探索吧！🔍 注意：作为预览版本，Spring Boot 4.0.0 尚未达到生产就绪状态。在关键业务应用中使用前，请务必进行充分测试。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 10 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 4.0.0 预览版新特性详解：深入解读 Spring Framework 7.0.0</h1>
<p>你是否注意到创建新 Spring Boot 项目时出现的最新选项？<em>Spring Boot 4.0.0 预览版现已发布</em>，基于最新的 Spring Framework 7.0.0 🌱。这个版本引入了众多激动人心的新特性，不仅提升了开发效率，改善了空值安全性，还简化了 Web 应用程序的开发流程。本文将深入探讨这些重要变化，并提供完整的代码示例和单元测试，帮助你快速上手这个预览版本。让我们一起来探索吧！🔍</p>
<p><em>注意：作为预览版本，Spring Boot 4.0.0 尚未达到生产就绪状态。在关键业务应用中使用前，请务必进行充分测试。</em></p>
<h2> 1. 优雅的API版本控制 📚</h2>
<p>Spring Framework 7.0.0 引入了强大的 API 版本控制支持，开发者可以通过 <code>@RequestMapping</code> 注解中的 <code>version</code> 参数来管理同一端点的多个版本。这一特性大大简化了 REST API 向后兼容性的维护工作。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>工作原理</strong>：</p>
<ul>
<li>对 <code>/api/user</code> 的请求如果带有版本头（如 <code>Accept: application/vnd.api.v1+json</code>）会路由到 <code>getUserV1</code> 方法</li>
<li>带有版本 <code>2</code> 的请求会路由到 <code>getUserV2</code> 方法</li>
<li>这种方式保持了代码库的整洁性，避免了为不同版本重复创建端点</li>
</ul>
<p><strong>优势</strong>：简化了 API 演进过程，让开发者能够在不影响现有客户端的前提下轻松引入新功能 🌟。</p>
<h2> 2. 使用 BeanRegistrar 实现便捷的 Bean 注入 🛠️</h2>
<p>Spring Framework 7.0.0 新增的 <code>BeanRegistrar</code> 接口支持灵活的编程式 Bean 注册，可以根据活动配置文件等条件动态注册多个 Bean。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>单元测试</strong>：<br>
为了验证 Bean 是否正确注册，这里使用 Spring Boot 的测试框架编写一个单元测试：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>工作原理</strong>：</p>
<ul>
<li><code>MyBeansRegistrar</code> 无条件注册一个 <code>User</code> Bean，仅在 <code>dev</code> 配置文件激活时注册 <code>Order</code> Bean</li>
<li>单元测试验证了当 <code>dev</code> 配置文件激活时，这两个 Bean 在应用程序上下文中都可正常使用</li>
</ul>
<p><strong>优势</strong>：简化了动态 Bean 注册流程，特别适用于具有特定配置文件的复杂应用场景 📦。</p>
<h2> 3. 使用 JSpecify 注解提升空值安全性 🛡️</h2>
<p>Spring Framework 7.0.0 通过采用 JSpecify 注解（<code>@Nullable</code> 和 <code>@NonNull</code>）来增强空值安全性，替代了已弃用的 JSR 305 注解。这些注解可以帮助 IntelliJ IDEA 2024 等 IDE 在开发阶段捕获潜在的空指针异常问题。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>工作原理</strong>：</p>
<ul>
<li><code>@NonNull</code> 确保 <code>setName</code> 中的 <code>name</code> 参数不能为 null，如果传递 null 值会触发 IDE 警告</li>
<li><code>@Nullable</code> 表示 <code>getName</code> 方法可能返回 null，提醒开发者进行适当的空值处理</li>
<li>IntelliJ IDEA 2024 会针对空值安全违规显示警告或错误，从而提高代码可靠性</li>
</ul>
<p><strong>优势</strong>：通过在开发阶段提前发现问题，有效减少运行时 <code>NullPointerException</code> 异常，增强代码健壮性 🔐。</p>
<h2> 4. 使用 @ImportHttpServices 轻松创建 HTTP 代理 🌐</h2>
<p>新增的 <code>@ImportHttpServices</code> 注解简化了为 HTTP 接口创建代理的过程，支持以声明式方式对 HTTP 服务进行分组和配置。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>工作原理</strong>：</p>
<ul>
<li><code>@ImportHttpServices</code> 为指定的接口注册 HTTP 代理，分别归属于 <code>weather</code> 和 <code>user</code> 组</li>
<li><code>RestClientHttpServiceGroupConfigurer</code> 为组内所有服务应用通用配置，如请求头信息</li>
</ul>
<p><strong>优势</strong>：简化了与外部 HTTP 服务的集成流程，有效减少了样板代码 📡。</p>
<h2> 5. 其他值得关注的新特性 🎉</h2>
<p><em>Spring Boot 4.0.0 和 Spring Framework 7.0.0 还包含了以下几个重要的增强功能：</em></p>
<p>a. SpEL 表达式升级 📝</p>
<p>SpEL 现在支持空值安全和 Elvis 操作符（<code>?:</code>），进一步简化了属性注入的过程。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>这行代码会注入 <code>pop3.port</code> 系统属性，如果该属性未定义则使用默认值 <code>25</code>。</p>
<p>b. GraalVM 原生应用支持 ⚡</p>
<p>Spring AOT 技术可以将应用程序编译为原生镜像，显著减少启动时间和内存占用，特别适合云原生环境部署。</p>
<p>c. Jackson 3.x 支持 📊</p>
<p>对 Jackson 2.x 的支持已被弃用，Jackson 3.x（使用 <code>tools.jackson</code> 包）成为新的默认选择，提供了更优的性能和更丰富的功能。</p>
<p>d. Servlet 和 WebSocket 升级 🌐</p>
<p>Spring Framework 7.0.0 采用了 Jakarta Servlet 6.1 和 WebSocket 2.2 规范，需要使用现代容器，如 Tomcat 11+ 或 Jetty 12.1+。</p>
<p>e. HttpHeaders API 优化 🔍</p>
<p><code>HttpHeaders</code> API 已完成现代化改造，新增了 <code>firstValue</code> 和 <code>forEach</code> 等方法，使请求头操作更加便捷。</p>
<p><strong>示例</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>f. 已移除的功能 🗑️</p>
<ul>
<li>Spring MVC 的 XML 配置已被弃用，推荐使用基于 Java 的配置方式</li>
<li>Spring TestContext Framework 中对 JUnit 4 的支持已被弃用</li>
<li>Spring JCL 已被移除，由 Apache Commons Logging 1.3.0 替代</li>
</ul>
<p>g. 最低版本要求 📋</p>
<ul>
<li>Jakarta EE 11 (Tomcat 11+)</li>
<li>Kotlin 2.x</li>
<li>JSONassert 2.0</li>
<li>GraalVM 23</li>
</ul>
<p>完整的变更列表请参考 <a href="https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes" target="_blank" rel="noopener noreferrer">Spring Framework 7.0 发布说明</a>。</p>
<h2> 实际示例：在 Spring Boot 4.0.0 中构建版本化 API 🌟</h2>
<p>下面我们通过一个完整的 Spring Boot 4.0.0 应用程序示例，来展示如何使用 API 版本控制和空值安全 Bean。</p>
<p><strong>控制器</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>配置</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>测试</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个示例完整演示了 API 版本控制和 Bean 注册的功能，并通过单元测试验证了其正确性。</p>
<h2> 总结 📚</h2>
<p>基于 Spring Framework 7.0.0 的 Spring Boot 4.0.0 预览版带来了众多革命性的新特性：</p>
<ul>
<li><strong>API 版本控制</strong>：通过 <code>@RequestMapping</code> 实现优雅的端点版本管理</li>
<li><strong>BeanRegistrar</strong>：支持灵活的编程式 Bean 注册</li>
<li><strong>空值安全</strong>：采用 JSpecify 注解构建更健壮的代码</li>
<li><strong>HTTP 代理</strong>：使用 <code>@ImportHttpServices</code> 简化服务集成</li>
<li><strong>技术栈现代化</strong>：全面支持 Jackson 3.x、Servlet 6.1、WebSocket 2.2 和 GraalVM 原生镜像</li>
</ul>
<blockquote>
<p>更多升级信息，请参考官方文档：</p>
<p><a href="https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes" target="_blank" rel="noopener noreferrer">https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-7.0-Release-Notes</a></p>
<p>最近我们翻译了Spring Boot和Spring AI的中文文档，有需要的小伙伴可以收藏，后续Spring Boot 4.0发布我们也会第一时间进行翻译</p>
<ul>
<li><a href="https://doc.spring4all.com/spring-boot/3.4.6/" target="_blank" rel="noopener noreferrer">Spring Boot 3.4中文文档</a></li>
<li><a href="https://doc.spring4all.com/spring-boot/3.5.0/" target="_blank" rel="noopener noreferrer">Spring Boot 3.5中文文档</a></li>
<li><a href="https://doc.spring4all.com/spring-ai/reference/" target="_blank" rel="noopener noreferrer">Spring AI 1.0.0中文文档</a></li>
</ul>
</blockquote>
<p>这些变化让 Spring Boot 4.0.0 成为构建现代云原生应用的强大选择。建议在你的项目中尝试这个预览版本，但在生产环境中请务必谨慎使用。欢迎在评论区分享你的使用体验，如果这篇文章对你有帮助，请不要忘记点赞支持 👏！</p>
<p>感谢你耐心阅读这篇文章！如果内容对你有所帮助，请点赞 👏、收藏 ⭐ 并分享给需要的朋友。</p>
]]></content:encoded>
    </item>
    <item>
      <title>告别 @MockBean！在 Spring Boot 3.2+ 中使用 @MockitoBean 进行单元测试</title>
      <link>https://spring.didispace.com/article/20250608-spring-boot-avoid-mockbean-use-mockitobean.html</link>
      <guid>https://spring.didispace.com/article/20250608-spring-boot-avoid-mockbean-use-mockitobean.html</guid>
      <source url="https://spring.didispace.com/rss.xml">告别 @MockBean！在 Spring Boot 3.2+ 中使用 @MockitoBean 进行单元测试</source>
      <description>告别 @MockBean！在 Spring Boot 3.2+ 中使用 @MockitoBean 进行单元测试 多年来，@MockBean 一直被广泛用于 Spring Boot 单元测试中来模拟依赖项。 然而，在 Spring Boot 3.2 中，@MockBean 由于性能和可维护性方面的问题已被标记为废弃。 🔴 @MockBean 存在的问题</description>
      <category>Spring Boot</category>
      <pubDate>Sun, 08 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 告别 @MockBean！在 Spring Boot 3.2+ 中使用 @MockitoBean 进行单元测试</h1>
<p>多年来，<code>@MockBean</code> 一直被广泛用于 Spring Boot <strong>单元测试</strong>中来模拟依赖项。</p>
<p>然而，在 <strong>Spring Boot 3.2</strong> 中，<code>@MockBean</code> 由于性能和可维护性方面的问题已被<strong>标记为废弃</strong>。</p>
<h2> 🔴 <code>@MockBean</code> 存在的问题</h2>
<p>✔ <strong>创建不必要的 Spring 上下文代理</strong> → 拖慢测试执行速度
✔ <strong>全局模拟 Bean</strong> → 可能在多个测试中产生副作用
✔ <strong>未针对 Spring Boot 3.2 的测试改进进行优化</strong></p>
<h2> ✅ 解决方案：使用 <code>@MockitoBean</code></h2>
<p>Spring Boot 3.2 引入了 <code>@MockitoBean</code>，它提供了：
✔ <strong>更快的测试执行速度</strong> - 直接集成 Mockito
✔ <strong>更好的测试组件隔离</strong>
✔ <strong>更可靠的单元测试体验</strong></p>
<h2> 1️⃣ 什么是 <code>@MockitoBean</code>？</h2>
<p><code>@MockitoBean</code> 是 <strong>Spring Boot 3.2</strong> 中的一个新注解，它是 <code>@MockBean</code> 的<strong>直接替代品</strong>。</p>
<p>✔ 它<strong>为依赖项创建模拟实例</strong>
✔ 它<strong>与 JUnit 5 和 Mockito 有更好的集成</strong>
✔ 它<strong>不需要完整的 Spring 上下文重新加载</strong></p>
<h2> 2️⃣ 将 <code>@MockBean</code> 替换为 <code>@MockitoBean</code></h2>
<p>让我们通过一个<strong>在服务测试中模拟存储库</strong>的示例来演示。</p>
<h3> 📝 示例 1：使用 <code>@MockBean</code> 的旧方法（已废弃）</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>❌ <strong>这种方法中 <code>@MockBean</code> 的问题：</strong></p>
<ul>
<li>需要<strong>初始化 Spring 上下文</strong>，使测试变慢</li>
<li>由于<strong>全局模拟</strong>会影响其他测试</li>
</ul>
<h3> ✅ 示例 2：使用 <code>@MockitoBean</code> 的新方法（Spring Boot 3.2+）</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>✅ <strong>使用 <code>@MockitoBean</code> 的优势：</strong><br>
✔ 执行更快 – <strong>不会重新加载 Spring 上下文</strong><br>
✔ <strong>作用域模拟</strong> – 仅在测试类中进行模拟<br>
✔ <strong>更稳定</strong>的单元测试体验</p>
<h2> 3️⃣ 使用 <code>@MockitoBean</code> 和 <code>MockMvc</code> 进行 REST 控制器单元测试</h2>
<p>Spring Boot 还允许<strong>测试 REST 控制器</strong>而无需启动完整的应用程序。</p>
<h3> ✅ 示例：测试 <code>UserController</code></h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>✅ <strong>使用 <code>MockMvc</code> 高效测试 API 接口</strong>
✅ <strong>模拟 <code>UserService</code> 而无需加载完整应用程序</strong></p>
<h2> 4️⃣ 核心差异：<code>@MockBean</code> vs. <code>@MockitoBean</code></h2>
<p><img src="https://static.didispace.com/images3/bc1f4e7cc848d4bcc0dba2b2b88a2106.png" alt=""></p>
<p><code>@MockBean</code> vs. <code>@MockitoBean</code> 对比</p>
<p>🚀 <strong>在 Spring Boot 3.2+ 的单元测试中请始终使用 <code>@MockitoBean</code>！</strong></p>
<h2> 5️⃣ Spring Boot 3.2+ 单元测试最佳实践</h2>
<p>✔ <strong>使用 <code>@MockitoBean</code> 而不是 <code>@MockBean</code> 以获得更快的测试速度</strong>
✔ <strong>使用 <code>@WebMvcTest</code> 进行控制器测试</strong>
✔ <strong>仅在集成测试中使用 <code>@SpringBootTest</code></strong>
✔ <strong>模拟依赖项而不是使用真实的数据库连接</strong>
✔ <strong>使用 <code>Mockito.when()</code> 和 <code>verify()</code> 来验证方法调用</strong></p>
<h2> 🎯 总结：为什么要切换到 <code>@MockitoBean</code>？</h2>
<p>通过切换到 <code>@MockitoBean</code>，你将获得：<br>
✅ <strong>更快的测试执行（无需重新加载 Spring 上下文）</strong>
✅ <strong>更好的测试隔离（按测试的模拟作用域）</strong>
✅ <strong>更高效的 Mockito 集成</strong></p>
<p>🚀 <strong>你在 Spring Boot 3.2+ 项目中使用 <code>@MockitoBean</code> 了吗？欢迎在评论区分享你的经验！</strong></p>
<p>🔗 <strong>将这个指南分享给开发者朋友们，帮助他们从 <code>@MockBean</code> 迁移到 <code>@MockitoBean</code>！</strong> 🚀</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/bc1f4e7cc848d4bcc0dba2b2b88a2106.png" type="image/png"/>
    </item>
    <item>
      <title>如何在Spring Boot中使用注解动态切换实现</title>
      <link>https://spring.didispace.com/article/20250607-spring-boot-using-annotations-no-if-else.html</link>
      <guid>https://spring.didispace.com/article/20250607-spring-boot-using-annotations-no-if-else.html</guid>
      <source url="https://spring.didispace.com/rss.xml">如何在Spring Boot中使用注解动态切换实现</source>
      <description>如何在Spring Boot中使用注解动态切换实现 还在用冗长的if-else或switch语句管理多个服务实现？ 相信不少Spring Boot开发者都遇到过这样的场景：需要根据不同条件动态选择不同的服务实现。 如果告诉你可以完全摆脱条件判断，让Spring自动选择合适的实现——只需要一个注解，你是否感兴趣？ 本文将详细介绍这种优雅的实现方式。 💡 实际开发中的痛点 假设你在开发一个支付系统，需要支持多种支付方式：</description>
      <category>Spring Boot</category>
      <pubDate>Sat, 07 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 如何在Spring Boot中使用注解动态切换实现</h1>
<p><strong>还在用冗长的if-else或switch语句管理多个服务实现？</strong></p>
<p>相信不少Spring Boot开发者都遇到过这样的场景：需要根据不同条件动态选择不同的服务实现。</p>
<p>如果告诉你可以<strong>完全摆脱条件判断</strong>，让Spring自动选择合适的实现——<strong>只需要一个注解</strong>，你是否感兴趣？</p>
<p>本文将详细介绍这种优雅的实现方式。</p>
<h2> 💡 实际开发中的痛点</h2>
<p>假设你在开发一个支付系统，需要支持多种支付方式：</p>
<ul>
<li><code>PaypalPaymentService</code></li>
<li><code>StripePaymentService</code></li>
<li><code>RazorpayPaymentService</code></li>
</ul>
<p>传统做法是根据用户选择的支付方式进行条件判断：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>❌ 这种写法存在明显问题：代码冗余、难以维护、扩展性差。</p>
<p>接下来看看如何优化。</p>
<h2> 优雅的解决方案：注解 + Map容器</h2>
<p>实现思路：</p>
<ul>
<li>定义<strong>自定义注解</strong>标识不同的实现类</li>
<li>利用Spring的依赖注入机制，通过<strong>Map容器</strong>动态获取对应的实现</li>
</ul>
<h2> 步骤1：定义通用接口</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 步骤2：创建自定义注解</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 步骤3：标注具体实现类</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 步骤4：构建服务工厂类</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 步骤5：在控制器中使用</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 🎯 方案优势</h2>
<ul>
<li><strong>告别条件判断地狱</strong>：无需编写冗长的if-else语句</li>
<li><strong>扩展性强</strong>：新增实现只需添加注解即可</li>
<li><strong>符合开闭原则</strong>：对扩展开放，对修改封闭</li>
<li><strong>代码简洁</strong>：逻辑清晰，便于维护和测试</li>
</ul>
<h2> 进阶技巧：结合Spring Profile</h2>
<p>如果需要根据不同环境自动切换实现（如测试环境和生产环境），可以这样做：</p>
<p>将<code>@PaymentType</code>与<code>@Profile("prod")</code>结合使用，Spring会根据当前激活的配置文件自动选择对应的实现。</p>
<h2> 总结</h2>
<p>这种基于注解的动态服务选择模式，能够显著<strong>提升代码质量和可维护性</strong>，特别适用于大型项目中复杂的服务发现场景。</p>
<p>告别意大利面条式的代码，拥抱注解驱动的<strong>动态、优雅的服务注入</strong>方式。</p>
]]></content:encoded>
    </item>
    <item>
      <title>感谢AI，再也不用自己给 Spring Boot 写单元测试了</title>
      <link>https://spring.didispace.com/article/20250605-spring-boot-ai-junit.html</link>
      <guid>https://spring.didispace.com/article/20250605-spring-boot-ai-junit.html</guid>
      <source url="https://spring.didispace.com/rss.xml">感谢AI，再也不用自己给 Spring Boot 写单元测试了</source>
      <description>感谢AI，再也不用自己给 Spring Boot 写单元测试了 设想这样一个未来：编写单元测试不再是开发者的负担，AI 能理解你的代码，自动生成全面的测试用例，并在问题演变为 bug 之前就将其发现。这个未来并非遥不可及，它已然成为现实。 在 Spring Boot 开发中，JUnit 测试长期以来都是测试驱动开发（TDD）和质量保障的核心支柱。然而，手动编写这些测试往往重复、枯燥且易于出错。如今，AI 驱动的测试生成正带来一场颠覆性的变革，人工智能模型能够分析你的 Java 代码，并自动生成单元测试。</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 05 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 感谢AI，再也不用自己给 Spring Boot 写单元测试了</h1>
<p>设想这样一个未来：编写单元测试不再是开发者的负担，AI 能理解你的代码，自动生成全面的测试用例，并在问题演变为 bug 之前就将其发现。这个未来并非遥不可及，它已然成为现实。</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1200/1*5tKhwnmZxbLcrUa3bYt_lw.jpeg" alt=""></p>
<p>在 Spring Boot 开发中，<strong>JUnit 测试</strong>长期以来都是测试驱动开发（TDD）和质量保障的核心支柱。然而，手动编写这些测试往往重复、枯燥且易于出错。如今，<strong>AI 驱动的测试生成</strong>正带来一场颠覆性的变革，人工智能模型能够分析你的 Java 代码，并自动生成单元测试。</p>
<p>本文将深入探讨 AI 如何重塑 Spring Boot 应用的测试格局。我们将详细解析其工作原理、主流工具、优势与局限，并指导你如何在自己的项目中快速应用 AI 生成的 JUnit 测试。</p>
<h2> 为什么要自动化 JUnit 测试生成？</h2>
<p>在深入探讨 AI 解决方案之前，理解自动化测试生成的内在动因至关重要：</p>
<ul>
<li><strong>提升开发效率</strong>：开发者可以将更多精力投入到核心业务逻辑的实现上，而测试则由 AI 自动生成。</li>
<li><strong>扩大测试覆盖</strong>：AI 模型能够更全面地分析源代码，发现开发者可能忽略的边缘案例。</li>
<li><strong>确保测试一致性</strong>：AI 生成的测试遵循统一的标准模式，从而减少测试代码结构上的不一致性。</li>
<li><strong>降低人为错误率</strong>：有效避免了在手动编写重复性测试用例时可能引入的疏忽和错误。</li>
</ul>
<h1> AI 如何助力测试生成</h1>
<p>AI 模型，特别是像 OpenAI 的 GPT、Google 的 Codey 这类<strong>大型语言模型（LLM）</strong>，具备理解源代码并生成相应测试用例的能力。其工作流程如下：</p>
<ol>
<li><strong>代码解析</strong>：AI 读取 Java 类或方法，并理解其输入、输出及行为逻辑。</li>
<li><strong>行为分析</strong>：基于代码逻辑，模型预测其预期行为和潜在的测试场景。</li>
<li><strong>测试结构生成</strong>：AI 按照 JUnit 框架的规范，格式化测试代码，包括方法注解、模拟（mock）设置和断言。</li>
<li><strong>依赖感知</strong>：针对 Spring Boot 应用，AI 会自动利用 Mockito 或 MockMvc 等库来模拟依赖项。</li>
</ol>
<h2> Spring Boot 测试生成的关键环节</h2>
<p>AI 在生成 Spring Boot 测试时，通常会覆盖以下几个核心层面：</p>
<h3> 1. Service 层测试</h3>
<p>这类测试专注于业务逻辑，不涉及 Web 层。AI 生成的 Service 层测试通常包括：</p>
<ul>
<li>使用 <code>@Mock</code> 和 <code>@InjectMocks</code> 进行依赖模拟。</li>
<li>测试各种边界情况。</li>
<li>验证返回值和异常处理场景。</li>
</ul>
<p><strong>示例概念</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 2. Controller 层测试</h3>
<p>AI 生成的 Controller 层测试会利用 <code>MockMvc</code> 模拟 HTTP 请求，并验证响应状态码和 JSON 内容。</p>
<p><strong>示例概念</strong>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 3. Repository 层测试</h3>
<p>对于数据库交互测试，AI 通常会生成集成测试，并利用 H2 等内存数据库以及 <code>@DataJpaTest</code> 注解。</p>
<h2> 支持 AI 测试生成的工具</h2>
<p>目前已涌现出多款利用 AI 和 LLM 自动生成测试的工具：</p>
<ul>
<li><strong>GitHub Copilot</strong>：虽然并非专为测试设计，但它能在编码过程中智能地提供测试代码片段建议。</li>
<li><strong>Diffblue Cover</strong>：一款专为 Java 设计的商业工具，能够利用 AI 自动编写单元测试。</li>
<li><strong>ChatGPT</strong>：开发者可以将代码粘贴到 ChatGPT 中，并提示其生成 JUnit 测试。</li>
<li><strong>CodiumAI</strong>：提供 IDE 扩展，直接在开发环境中智能生成测试。</li>
</ul>
<h2> 使用 AI 生成 JUnit 测试的优势</h2>
<ul>
<li><strong>显著节省时间</strong>：大幅减少编写样板测试代码所需的工作量。</li>
<li><strong>提升测试覆盖率</strong>：AI 模型往往能生成开发者容易忽视的边缘测试用例。</li>
<li><strong>支持代码重构</strong>：当代码发生变更时，利用 AI 重新生成测试比手动更新更为高效。</li>
<li><strong>加速新成员上手</strong>：新团队成员可以通过阅读 AI 生成的测试，快速理解代码的行为和功能。</li>
</ul>
<h2> 局限与挑战</h2>
<p>尽管 AI 模型功能强大，但也存在一些局限性：</p>
<ul>
<li><strong>代码理解并非完美</strong>：对于复杂或高度抽象的逻辑，AI 可能存在误解意图的情况。</li>
<li><strong>测试维护成本</strong>：自动生成的测试在代码变更后，可能仍需要手动进行调整和维护。</li>
<li><strong>数据准备复杂性</strong>：AI 未必总能正确地设置数据库或外部系统的初始化和清理逻辑。</li>
<li><strong>盲目信任的风险</strong>：开发者必须始终审慎审查和验证生成的测试，以确保其准确性和相关性。</li>
</ul>
<h2> 最佳实践</h2>
<p>为有效利用 AI 生成 Spring Boot JUnit 测试，建议遵循以下实践：</p>
<ol>
<li><strong>保持代码模块化</strong>：小巧、职责单一的方法更容易被 AI 理解并生成测试。</li>
<li><strong>采用描述性命名</strong>：AI 模型依赖于方法和变量的名称来推断其行为。</li>
<li><strong>循序渐进地集成</strong>：建议从 Service 层测试开始，逐步扩展到 Controller 和 Repository 层。</li>
<li><strong>严格审查所有生成测试</strong>：切勿盲目接受，务必仔细审查和验证生成的代码。</li>
<li><strong>将 AI 视为辅助工具而非替代品</strong>：让 AI 增强你的测试流程，但不要取代批判性思维和人工判断。</li>
</ol>
<h2> ChatGPT 测试生成示例流程</h2>
<ol>
<li>
<p><strong>复制你的 Service 或 Controller 类</strong>。</p>
</li>
<li>
<p><strong>粘贴到 ChatGPT</strong>，并附上类似如下的提示：</p>
<blockquote>
<p>请为以下 Spring Boot Service 类使用 Mockito 生成 JUnit 5 测试用例。</p>
</blockquote>
</li>
<li>
<p><strong>审查生成的代码</strong>，根据需要调整输入、模拟设置和断言。</p>
</li>
<li>
<p><strong>将代码复制到你的测试套件中</strong>，并通过 Maven 或 Gradle 运行。</p>
</li>
</ol>
<h2> 总结</h2>
<p>AI 模型为软件开发带来了全新的生产力工具，其中<strong>自动化 JUnit 测试生成</strong>无疑是最具实用价值的应用之一。在 Spring Boot 应用中，由于其分层架构和复杂的依赖管理，手动编写测试可能变得繁琐。此时，AI 工具能成为强大的助力，显著提升测试的效率和准确性。</p>
<p>尽管这些工具并非尽善尽美，但它们在测试速度、覆盖率和标准化方面带来了显著的提升。只要进行审慎的集成并辅以人工监督，AI 生成的 JUnit 测试必将成为任何 Spring Boot 项目测试体系中的宝贵资产。</p>
]]></content:encoded>
      <enclosure url="https://miro.medium.com/v2/resize:fit:1200/1*5tKhwnmZxbLcrUa3bYt_lw.jpeg" type="image/jpeg"/>
    </item>
    <item>
      <title>Spring Boot + 虚拟线程实现的二维码生成器</title>
      <link>https://spring.didispace.com/article/20250604-spring-boot-qrcode.html</link>
      <guid>https://spring.didispace.com/article/20250604-spring-boot-qrcode.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot + 虚拟线程实现的二维码生成器</source>
      <description>Spring Boot + 虚拟线程实现的二维码生成器 随着 Java 21 的发布，虚拟线程（Virtual Threads）成为了正式特性，彻底改变了 Java 的并发编程方式。它们为传统线程提供了更轻量、更易扩展的替代方案，让开发者能够编写出更加简洁高效的并发代码。 虚拟线程 虚拟线程为异步 Java 开发带来了革命性的变化。它们让 JVM 拥有了类似 Go 的并发能力，使高吞吐量应用的代码结构更加简洁、易于维护。如果你正在开发 Web 服务、文件处理器或任何 I/O 密集型应用，虚拟线程会成为你的得力助手。</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 04 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot + 虚拟线程实现的二维码生成器</h1>
<p>随着 <strong>Java 21</strong> 的发布，虚拟线程（Virtual Threads）成为了正式特性，彻底改变了 Java 的并发编程方式。它们为传统线程提供了更轻量、更易扩展的替代方案，让开发者能够编写出更加简洁高效的并发代码。</p>
<h2> 虚拟线程</h2>
<p>虚拟线程为异步 Java 开发带来了革命性的变化。它们让 JVM 拥有了类似 Go 的并发能力，使高吞吐量应用的代码结构更加简洁、易于维护。如果你正在开发 Web 服务、文件处理器或任何 I/O 密集型应用，虚拟线程会成为你的得力助手。</p>
<p>虚拟线程是由 Java 虚拟机（JVM）直接管理的轻量级线程，而不是由操作系统管理。与平台线程（也称为内核线程或本地线程）相比，平台线程的创建和管理成本较高，而虚拟线程则极为廉价，可以轻松支持成千上万甚至上百万的并发线程。</p>
<p>虚拟线程依然基于熟悉的 <code>java.lang.Thread</code> API，这意味着你无需学习新的并发模型或库。</p>
<p>传统线程与操作系统线程是一一对应的：</p>
<ul>
<li>每个 Java 线程对应一个操作系统线程</li>
<li>阻塞线程（如 I/O 操作）会占用操作系统资源</li>
</ul>
<p>而虚拟线程采用多对一模型，允许大量虚拟线程共享少量操作系统线程。当虚拟线程遇到阻塞操作（如 <code>sleep()</code> 或 <code>read()</code>）时，JVM 会自动挂起该线程，让底层的承载线程去执行其他任务。</p>
<h1> 虚拟线程项目示例</h1>
<p>下面的代码演示了如何配置 Tomcat（Spring Boot 默认的 Web 服务器）使用虚拟线程来处理请求任务，替代传统的平台线程。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建生成二维码的接口：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建交互界面：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>运行效果：</p>
<p><img src="https://static.didispace.com/images3/c10e4eadefbf96c0e2348282fe453029.png" alt=""></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/c10e4eadefbf96c0e2348282fe453029.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 用一个接口搞定所有查询</title>
      <link>https://spring.didispace.com/article/20250603-spring-boot-search-api-one-endpoint-to-rule-them-all.html</link>
      <guid>https://spring.didispace.com/article/20250603-spring-boot-search-api-one-endpoint-to-rule-them-all.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 用一个接口搞定所有查询</source>
      <description>Spring Boot 用一个接口搞定所有查询 如果你用过 Spring Boot，一定很熟悉“铁三角”：@Controller、@Service 和 @Repository。每加一个实体，通常就会有对应的 Controller 来处理 CRUD 和业务逻辑——至少对于同步操作来说如此。 创建 POST、PUT、DELETE 这些接口很简单：校验、业务逻辑一接，接口就能用了。 但 GET 呢？</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 03 Jun 2025 06:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 用一个接口搞定所有查询</h1>
<p>如果你用过 Spring Boot，一定很熟悉“铁三角”：<code>@Controller</code>、<code>@Service</code> 和 <code>@Repository</code>。每加一个实体，通常就会有对应的 Controller 来处理 CRUD 和业务逻辑——至少对于同步操作来说如此。</p>
<p>创建 <code>POST</code>、<code>PUT</code>、<code>DELETE</code> 这些接口很简单：校验、业务逻辑一接，接口就能用了。</p>
<p>但 <code>GET</code> 呢？</p>
<p>来看一个简单的 <code>Employee</code> 实体：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>现在我们要查所有指定状态的员工，很简单：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>清爽利落，对吧？</p>
<p>我们继续下一个需求：</p>
<blockquote>
<p>“API 能不能也按角色过滤？”</p>
</blockquote>
<p>当然可以，改一下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>又一次上线、又一次版本号递增。</p>
<p>然后又有同事说：</p>
<blockquote>
<p>“我们想按 status 或 role 查询，还能不能加个 department 过滤？”</p>
</blockquote>
<p>糟了，问题来了：组合太多，维护太累。</p>
<h2> Spring Data Specification 登场</h2>
<p>Spring Specification 提供了一种动态、可复用的方式，用 JPA Criteria API 构建查询。</p>
<p>可以把 <code>Specification&lt;T&gt;</code> 理解为运行时构建 <code>Predicate</code> 的工具，让你对查询条件有极高的灵活性。</p>
<p>我们来用 <code>Specification&lt;T&gt;</code> 搭一个灵活的查询解析器。</p>
<h2> 动态搜索构建器</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h1> 最终 API 接口</h1>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 查询示例</h2>
<p>现在，这一个接口就能支持所有这些用例：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 为什么值得这样做</h2>
<ul>
<li>不用为每种过滤组合新建接口</li>
<li>不用为每个新字段扩展 repository 接口</li>
<li>简单过滤逻辑无需重新部署</li>
<li>对业务友好，易扩展</li>
</ul>
<p>这种模式可以轻松适配任何实体，让你的代码库保持一致性和可维护性。</p>
<p>一个接口，无限查询。如果没有复杂业务逻辑，这就是最终答案。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 3.x 中的 RestClient 使用案例</title>
      <link>https://spring.didispace.com/article/20250530-spring-boot-3-restclient.html</link>
      <guid>https://spring.didispace.com/article/20250530-spring-boot-3-restclient.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 3.x 中的 RestClient 使用案例</source>
      <description>Spring Boot 3.x 中的 RestClient 使用案例 RestClient 是 Spring Framework 6 引入的一个现代化、流式、类型安全的 HTTP 客户端，在 Spring Boot 3.2+ 中可用。它旨在替代较旧的 RestTemplate，更好地适应现代 HTTP 使用模式，并提供更简洁、直观的 API。 🔍 什么是 RestClient？</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 30 May 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 3.x 中的 RestClient 使用案例</h1>
<p><code>RestClient</code> 是 <strong>Spring Framework 6</strong> 引入的一个<strong>现代化、流式、类型安全的 HTTP 客户端</strong>，在 <strong>Spring Boot 3.2+</strong> 中可用。它旨在替代较旧的 <code>RestTemplate</code>，更好地适应现代 HTTP 使用模式，并提供更简洁、直观的 API。</p>
<h2> 🔍 什么是 <code>RestClient</code>？</h2>
<p><code>RestClient</code> 是构建在 <strong>Spring 的</strong> <code>WebClient</code> 之上的新抽象，但它提供了<strong>简化的流式 API</strong>——非常适合阻塞式、同步的 HTTP 调用。可以将其视为现代化的 <code>RestTemplate</code>。</p>
<h2> RestClient 的主要优势</h2>
<p>✅ 流式 API 更清晰、更易读的代码</p>
<p>✅ 内置 JSON（反）序列化 无需手动使用 <code>ObjectMapper</code></p>
<p>✅ 异常处理 与 <code>RestClientException</code> 集成</p>
<p>✅ 类型安全的响应 直接将响应映射到 DTO</p>
<p>✅ 复用底层 WebClient 共享连接池和资源</p>
<p>✅ 更容易测试 与 <code>MockRestServiceServer</code> 配合良好</p>
<h1> 🔧 基本设置</h1>
<p>确保你使用的是 <strong>Spring Boot 3.2+</strong>。</p>
<h3> Bean 定义示例（可选）</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 基本用法</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 🎯 使用场景</h2>
<h3> 1. GET 请求</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 2. POST 请求</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 3. PUT 请求</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 4. DELETE 请求</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 5. 使用 ResponseEntity 和 Headers 进行交换</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 🔐 认证和请求头</h3>
<p>添加请求头</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>添加 Bearer Token</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 📦 处理 JSON 数组和泛型类型</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> ⚠️ 错误处理</h3>
<p>你可以使用 <code>.onStatus(...)</code> 处理 HTTP 错误</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 🧪 单元测试</h3>
<p>使用 <code>MockRestServiceServer</code> 进行测试：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>支持流式期望和验证。</p>
<h2> 🔁 重试和超时（Spring Retry + WebClient 自定义）</h2>
<p>如果需要重试或超时：</p>
<ul>
<li>配置 RestClient 使用的底层 WebClient。</li>
<li>可以通过注解或编程方式集成 Spring Retry。</li>
</ul>
<h2> 🏁 何时使用 RestClient</h2>
<p>✅ 在以下情况使用：</p>
<ul>
<li>需要简洁的同步 HTTP 调用</li>
<li>不想处理响应式编程</li>
<li>正在构建现代化的 Spring Boot 3.2+ 应用</li>
<li>需要可测试性和可读性</li>
</ul>
<p>❌ 在以下情况避免使用：</p>
<ul>
<li>需要高并发或非阻塞 I/O</li>
<li>已经深入使用响应式流（继续使用 WebClient）</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>升级到 Spring Boot 3.5，我们的云成本减少了 45%</title>
      <link>https://spring.didispace.com/article/20250529-spring-boot-3-5-config.html</link>
      <guid>https://spring.didispace.com/article/20250529-spring-boot-3-5-config.html</guid>
      <source url="https://spring.didispace.com/rss.xml">升级到 Spring Boot 3.5，我们的云成本减少了 45%</source>
      <description>升级到 Spring Boot 3.5，我们的云成本减少了 45% 上个季度，我带着越来越焦虑的心情盯着我们公司的 AWS 账单。尽管服务的客户数量基本持平，但我们的云成本在过去一年里持续攀升。作为负责后端基础设施的技术负责人，我必须在下一个预算评审前找到解决方案。 我没想到的是，一次看似例行的 Spring Boot 升级，加上一些有针对性的配置调整，竟然让我们的 AWS 开支几乎减半。以下是我们如何发现问题、实施变更，并彻底提升应用资源效率的全过程。</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 29 May 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 升级到 Spring Boot 3.5，我们的云成本减少了 45%</h1>
<p>上个季度，我带着越来越焦虑的心情盯着我们公司的 AWS 账单。尽管服务的客户数量基本持平，但我们的云成本在过去一年里持续攀升。作为负责后端基础设施的技术负责人，我必须在下一个预算评审前找到解决方案。</p>
<p><img src="https://static.didispace.com/images3/b5055ecd75270da24431ee3d0fe82bd2.png" alt=""></p>
<p>我没想到的是，一次看似例行的 Spring Boot 升级，加上一些有针对性的配置调整，竟然让我们的 AWS 开支几乎减半。以下是我们如何发现问题、实施变更，并彻底提升应用资源效率的全过程。</p>
<h2> 云成本加剧的危机</h2>
<p>我们公司运营着一个 SaaS 平台，帮助中型企业管理多渠道的库存。我们不是巨头（大约 5,000 个客户，每天处理 120,000 个订单—），但 AWS 账单已经涨到了令人头疼的每月 27,000 美元。_</p>
<p>CFO 在季度会议上说："我们要么削减成本，要么涨价。"但在当前市场，涨价根本不现实。"</p>
<p>挑战很明确：在不影响应用性能和可靠性的前提下，找到显著的基础设施节省空间。但从哪里下手？</p>
<h2> 找出资源消耗大户</h2>
<p>我们的应用架构对现代 Java 项目来说很常见：</p>
<ul>
<li>Spring Boot 2.7 后端服务</li>
<li>PostgreSQL RDS 实例做持久化存储</li>
<li>Redis 做缓存</li>
<li>EC2 实例组成自动伸缩组，挂在负载均衡器后面</li>
</ul>
<p>第一步是搞清楚钱都花在哪了。我设置了详细的成本分配标签，并用 AWS Cost Explorer 分析了我们的支出模式。</p>
<p>结果令人惊讶：</p>
<ul>
<li><strong>EC2 实例</strong>：占 58%</li>
<li><strong>RDS PostgreSQL</strong>：占 25%</li>
<li><strong>数据传输</strong>：占 12%</li>
<li><strong>其他服务</strong>（Redis、S3 等）：占 5%</li>
</ul>
<p>EC2 成本成了首要目标，深入分析后发现更有意思的现象：我们运行的实例数量远超实际流量需求。自动伸缩经常被触发，启动的新实例大多处于低利用率。</p>
<h2> 资源利用率之谜</h2>
<p>监控数据显示出一种奇怪的模式。每台 EC2 实例启动时各项指标都很健康，但随后会逐步出现：</p>
<ol>
<li>CPU 利用率逐步升高（最终达到 70–80%）</li>
<li>JVM 堆内存持续增长</li>
<li>响应时间变慢</li>
<li>吞吐量下降</li>
</ol>
<p>大约 12 小时后，指标恶化到自动伸缩被触发，启动新实例。但这些新实例并没有处理更多流量，只是在弥补已有实例性能下降的问题。</p>
<p>"看起来像是某种资源泄漏。"我对团队说，"但不是典型的内存泄漏，而是应用效率在逐步下降。"</p>
<h2> 数据库连接的真相</h2>
<p>开启详细性能监控和日志分析后，我们发现了惊人的问题：应用创建了过多的数据库连接，且很多连接没有被正确关闭。</p>
<p>典型的 API 请求流程应该是：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>但实际连接使用却是：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这些泄漏的连接会不断积累，直到连接池耗尽，导致性能下降，最终触发自动伸缩。</p>
<p>罪魁祸首？我们的应用用的是 Spring Data JPA，并有一些自定义的 repository 实现，没有正确管理事务边界和连接生命周期。</p>
<h2> Spring Boot 3.5 的启示</h2>
<p>就在这时，Spring Boot 3.5 发布了，带来了数据库连接管理和 ORM 性能的多项改进。发布说明中提到"资源利用率显著提升"，这引起了我的注意。</p>
<p>进一步研究后，我发现 Spring Boot 3.5 包含：</p>
<ol>
<li>更强的连接池集成</li>
<li>改进的事务管理</li>
<li>更智能的资源清理</li>
<li>更好地处理懒加载场景</li>
</ol>
<p>升级能解决我们的问题吗？值得一试。</p>
<h2> 关键配置变更带来的转机</h2>
<p>我们决定升级到 Spring Boot 3.5，并针对数据库连接管理做了几项关键配置调整。以下是最有影响力的具体变更：</p>
<h3> 1. 连接池优化</h3>
<p>我们将默认的 HikariCP 配置调整为更适合我们负载的参数：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>关键新增项是 <code>leak-detection-threshold</code>，帮助我们识别和记录潜在的连接泄漏。降低 <code>minimum-idle</code> 也避免了在低峰期保留过多空闲连接。</p>
<h3> 2. 事务管理改进</h3>
<p>我们优化了事务管理配置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION</code> 这个设置堪称"神器"，它确保数据库连接在真正需要时才获取，并在事务结束后立即释放。</p>
<h3> 3. JPA 查询优化</h3>
<p>我们做了多项 JPA 和 Hibernate 优化：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Spring Boot 3.5 的新查询优化器，显著减少了常用操作所需的数据库查询次数。</p>
<h3> 4. 语句缓存</h3>
<p>我们开启了预编译语句缓存，对数据库性能提升明显：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这些设置确保常用 SQL 语句被缓存，减少了语句准备的开销。</p>
<h1> 5. 针对场景的连接管理</h1>
<p>对于最消耗资源的接口，我们实现了针对性的事务和连接设置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样可以针对不同操作设置不同的事务和抓取行为，而不是一刀切。</p>
<h2> 实施与即时成效</h2>
<p>这些变更需要仔细测试，因为数据库连接问题往往隐蔽且依赖环境。我们的做法：</p>
<ol>
<li>搭建与生产一致的预发环境</li>
<li>升级到 Spring Boot 3.5 并应用新配置</li>
<li>进行大量压力测试验证变更</li>
<li>对比前后连接使用模式</li>
</ol>
<p>初步结果令人振奋：</p>
<ul>
<li>单实例平均数据库连接数从 7.8 降到 3.2</li>
<li>连接获取时间降低 68%</li>
<li>72 小时压力测试期间未检测到连接泄漏</li>
</ul>
<p>但真正的考验在生产环境。</p>
<h2> 上线与 AWS 成本影响</h2>
<p>我们在维护窗口将变更上线，并立即开始监控。实际效果比预发还要明显：</p>
<ul>
<li>EC2 集群平均 CPU 利用率从 62% 降到 28%</li>
<li>JVM 垃圾回收暂停减少 76%</li>
<li>单实例吞吐量提升 120%</li>
<li>平均响应时间从 187ms 降到 74ms</li>
</ul>
<p>最重要的是，自动伸缩事件几乎消失。原本高峰期需要 20–24 台 EC2，现在只需 9–10 台即可稳定运行。</p>
<p>AWS 成本立竿见影：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>节省明细：</p>
<ul>
<li>EC2 成本降低 58%（实例数减少，CPU 利用率下降）</li>
<li>RDS 成本降低 32%（连接数减少，查询效率提升）</li>
<li>数据传输成本降低 15%（API 响应更高效）</li>
</ul>
<h2> 技术细节解析：为何如此有效</h2>
<p>要真正理解这些变更为何如此有效，必须了解 Spring Boot 3.5 的技术改进，以及我们的配置如何充分利用了这些特性。</p>
<h2> 连接生命周期的改进</h2>
<p>Spring Boot 3.5 从根本上改变了数据库连接的管理方式。旧版本往往过早获取连接且持有时间过长。新设置 <code>DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION</code> 保证：</p>
<ol>
<li>只有在即将执行 SQL 时才获取连接</li>
<li>事务结束后立即释放连接</li>
</ol>
<p>这样大大缩短了连接持有时间，让更小的连接池也能应对同样的负载。</p>
<h2> 查询优化引擎</h2>
<p>Spring Boot 3.5 的新查询优化器解决了多种常见低效问题：</p>
<ol>
<li><strong>N+1 查询预防</strong>：检测潜在 N+1 查询模式并自动转为高效批量查询</li>
<li><strong>连接优化</strong>：分析实体关系，选择更优的连接策略</li>
<li><strong>抓取大小调优</strong>：根据结果集大小自动调整 JDBC fetch size</li>
</ol>
<p>我们的配置已启用并调优了该优化器：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h2> 语句缓存</h2>
<p>数据库语句准备开销不容小觑。开启语句缓存后，常用查询可跳过准备阶段：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>对于我们这种查询模式较为固定的应用，显著降低了数据库 CPU 占用并提升响应速度。</p>
<h2> 泄漏检测与预防</h2>
<p>泄漏检测配置对定位剩余问题至关重要：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>该设置会在连接持有超时后记录详细堆栈，帮助我们定位和修复代码中的连接管理问题。</p>
<h2> 代码优化</h2>
<p>虽然配置变更带来了主要提升，我们也做了几项代码优化作为补充：</p>
<h3> 1. 简化 Repository 方法</h3>
<p>我们重构了复杂的 repository 方法，充分利用 Spring Data JPA 的查询派生能力：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在 Spring Boot 3.5 的新查询优化器下，简化后的方法性能更好，框架能做出更优的抓取决策。</p>
<h3> 2. 显式事务边界</h3>
<p>我们让事务边界更明确，尤其是只读操作：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>readOnly = true</code> 提示 Spring 和数据库进一步优化查询执行。</p>
<h3> 3. 批量操作异步处理</h3>
<p>对于资源密集型操作，我们采用异步处理并控制资源消耗：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样保证批量操作不会占用过多数据库连接或 CPU。</p>
<h2> 总结与最佳实践</h2>
<p>在这个过程中，我们总结出几条对其他团队也有参考价值的最佳实践：</p>
<h3> 1. 连接池大小要与数据库连接数匹配</h3>
<p>最大连接池大小应按如下公式计算：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>比如 RDS 最大连接 100，5 个应用实例：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>我们取整到 20，留有余地。</p>
<h3> 2. 监控并记录连接使用</h3>
<p>开启详细连接监控：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>这样能清晰看到连接使用模式，便于排查问题。</p>
<h3> 3. 区分环境配置</h3>
<p>不同环境需求不同。我们为不同环境设置了专属 profile：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样开发环境不会占用多余资源。</p>
<h3> 4. 定期审查查询性能</h3>
<p>我们实现了 SQL 性能监控，记录慢查询及其执行计划：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>帮助我们及时发现和优化最耗资源的查询。</p>
<h2> 意外收获：超越成本的好处</h2>
<p>虽然降低 AWS 成本是首要目标，但我们还发现了不少额外好处：</p>
<h3> 1. 开发体验提升</h3>
<p>连接管理更好，错误信息更清晰，开发过程中遇到的超时和连接问题大幅减少。</p>
<h3> 2. 压测更准确</h3>
<p>我们的压力测试结果更可预测、更贴近生产，有助于容量规划。</p>
<h3> 3. 运维事件减少</h3>
<p>变更后六个月内，我们得到了这样的改变：</p>
<ul>
<li>连接相关告警减少 87%</li>
<li>自动伸缩事件减少 92%</li>
<li>数据库连接相关的生产事故为零</li>
</ul>
<h3> 4. 环保效益</h3>
<p>服务器数量从 24 台降到 10 台，不仅省钱，也大幅减少了能耗和碳排放。</p>
<h2> 结语：把配置当作一等优化手段</h2>
<p>作为软件工程师，面对性能挑战时我们常常关注代码优化、算法改进和架构调整。我们的经验表明，配置（尤其是数据库连接相关配置）同样值得作为一等优化手段。</p>
<p>Spring Boot 3.5 的改进是基础，而我们精细的配置调优则释放了这些提升的全部潜力。最终收获的不只是成本节省，还有更可靠、高效、环保的应用。</p>
<p>对于面临类似挑战的团队，我的建议是：</p>
<ol>
<li>先彻底分析当前资源使用模式</li>
<li>理解框架的数据库连接行为</li>
<li>针对性地调整配置</li>
<li>持续监控并迭代优化</li>
</ol>
<p>我们实现 45% AWS 账单削减，并不是靠大刀阔斧重构或彻底换架构，而是通过理解和优化已有系统——有时，最有效的提升就藏在你应用的配置文件里。</p>
<blockquote>
<p>作者：DevDecoded，原文：https://medium.com/@vermatanisha666/the-spring-boot-3-5-configuration-that-cut-our-aws-bill-by-45-e32118706ea7</p>
</blockquote>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/b5055ecd75270da24431ee3d0fe82bd2.png" type="image/png"/>
    </item>
    <item>
      <title>重磅！Spring AI 1.0 正式发布，Java 开发者的 AI神器！</title>
      <link>https://spring.didispace.com/article/20250521-spring-ai-1-release.html</link>
      <guid>https://spring.didispace.com/article/20250521-spring-ai-1-release.html</guid>
      <source url="https://spring.didispace.com/rss.xml">重磅！Spring AI 1.0 正式发布，Java 开发者的 AI神器！</source>
      <description>重磅！Spring AI 1.0 正式发布，Java 开发者的 AI神器！ Spring AI 1.0 GA版本终于正式发布！第一个正式版本，超多内容，每个Java开发者都必须了解一下！ 核心功能包括支持20个AI模型的ChatClient接口、适配20个向量数据库的检索模块、支持滑动窗口和向量搜索的对话记忆功能、基于@Tool注解的工具调用机制，以及模型评估、可观测性和Model Context Protocol（MCP）支持。此外，新增RAG流水线、ETL框架、工作流驱动和自主代理功能，并提供与微软Azure、AWS、Google Cloud等云服务商的集成案例。</description>
      <category>Spring AI</category>
      <pubDate>Wed, 21 May 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 重磅！Spring AI 1.0 正式发布，Java 开发者的 AI神器！</h1>
<p>Spring AI 1.0 GA版本终于正式发布！第一个正式版本，超多内容，每个Java开发者都必须了解一下！</p>
<p><img src="https://static.didispace.com/images3/46e70f8dc7e89800d6ac965d62657416.png" alt="Spring AI全新Logo"></p>
<p>核心功能包括支持20个AI模型的<code>ChatClient</code>接口、适配20个向量数据库的检索模块、支持滑动窗口和向量搜索的对话记忆功能、基于<code>@Tool</code>注解的工具调用机制，以及模型评估、可观测性和Model Context Protocol（MCP）支持。此外，新增RAG流水线、ETL框架、工作流驱动和自主代理功能，并提供与微软Azure、AWS、Google Cloud等云服务商的集成案例。</p>
<h2> 1. ChatClient核心接口</h2>
<ul>
<li>作为与AI模型交互的主要接口，支持<strong>20个AI模型</strong>（如Anthropic、ZhiPu、DeepSeek、MiniMax等），涵盖多模态输入输出（若模型支持）和JSON格式结构化响应。</li>
<li>各支持模型的横向特性对比如下：</li>
</ul>
<table>
<thead>
<tr>
<th>供应商</th>
<th>多模态支持</th>
<th>工具/功能支持</th>
<th>流式传输支持</th>
<th>重试支持</th>
<th>可观测性支持</th>
<th>内置JSON支持</th>
<th>本地化支持</th>
<th>兼容OpenAI API</th>
</tr>
</thead>
<tbody>
<tr>
<td>Anthropic Claude</td>
<td>文本、PDF、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Azure OpenAI</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>DeepSeek (OpenAI代理)</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>Google VertexAI Gemini</td>
<td>文本、PDF、图像、音频、视频</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>Groq (OpenAI代理)</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>HuggingFace</td>
<td>文本</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Mistral AI</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>MiniMax</td>
<td>文本</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>（未明确）</td>
</tr>
<tr>
<td>Moonshot AI</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>（未明确）</td>
</tr>
<tr>
<td>NVIDIA (OpenAI代理)</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>OCI GenAI/Cohere</td>
<td>文本</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Ollama</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>OpenAI</td>
<td>输入：文本、图像、音频<br>输出：文本、音频</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>Perplexity (OpenAI代理)</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>QianFan</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>ZhiPu AI</td>
<td>文本</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Amazon Bedrock Converse</td>
<td>文本、图像、视频、文档（PDF、HTML、MD、DOCX等）</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
</tbody>
</table>
<h2> 2. 检索增强生成（RAG）与向量数据库</h2>
<ul>
<li><strong>向量存储抽象</strong>：适配<strong>20个向量数据库</strong>（如Azure Cosmos DB、Weaviate），支持SQL-like过滤语言及原生查询回退。</li>
<li><strong>ETL框架</strong>：通过可插拔<code>DocumentReader</code>支持本地文件、网页、GitHub、云存储（AWS S3、Azure Blob等）及数据库输入，内置分块、元数据 enrichment 和嵌入生成。</li>
<li><strong>RAG流水线</strong>：基础<code>QuestionAnswerAdvisor</code>和模块化<code>RetrievalAugmentationAdvisor</code>，参考案例<a href="https://spring.io/blog/2025/05/20/your-first-spring-ai-1#retrieval-augmented-generation-rag-with-vector-stores" target="_blank" rel="noopener noreferrer">Retrieval Augmented Generation (R.A.G.) with Vector Stores</a></li>
</ul>
<h2> 3. 对话记忆（ChatMemory）</h2>
<ul>
<li><strong>基础实现</strong>：<code>MessageWindowChatMemory</code>通过滑动窗口存储最近N条消息，支持JDBC、Cassandra、Neo4j等持久化存储。</li>
<li><strong>高级功能</strong>：<code>VectorStoreChatMemoryAdvisor</code>基于向量搜索检索语义相似历史消息，参考案例<a href="https://spring.io/blog/2025/05/20/your-first-spring-ai-1#chat-memory" target="_blank" rel="noopener noreferrer">《Guide to chat memory implementation》</a>。</li>
</ul>
<h2> 4. 工具调用（Function Calling）</h2>
<ul>
<li>通过<code>@Tool</code>注解声明工具方法，支持动态注册Bean或编程式创建，可调用天气查询、数据库操作等外部功能，配套<a href="https://spring.io/blog/2025/05/20/your-first-spring-ai-1#local-tool-calling" target="_blank" rel="noopener noreferrer">《Guide to local tool calling》</a>。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 5. 模型评估与可观测性</h2>
<ul>
<li><strong>评估组件</strong>：<code>RelevancyEvaluator</code>验证响应相关性，<code>FactCheckingEvaluator</code>基于上下文校验事实准确性，引用Hugging Face专家指出“LLM作为裁判”的局限性（如模式崩溃、冗长偏见）。</li>
<li><strong>可观测性</strong>：集成Micrometer追踪模型延迟、Token使用、工具调用等指标，支持日志记录和Micrometer Tracing。</li>
</ul>
<h2> 6. 模型上下文协议（MCP）</h2>
<ul>
<li><strong>客户端</strong>：通过<code>spring-ai-starter-mcp-client</code>快速连接MCP服务器，支持stdio和HTTP-SSE端点，示例连接Brave搜索引擎。</li>
<li><strong>服务器</strong>：使用<code>spring-ai-starter-mcp-server</code>和<code>@Tool</code>注解构建MCP服务器，集成Spring Batch/Cloud Config提供企业级工具，支持OAuth安全认证。</li>
</ul>
<h2> 7. 代理支持</h2>
<ul>
<li><strong>工作流驱动代理</strong>：包含评估优化（自评估响应）、路由（智能分配请求）、编排（动态任务分解）、链式（分步处理）、并行化（批量调用聚合结果）等模式。</li>
<li><strong>自主代理</strong>：通过MCP动态发现工具，维护执行记忆，支持递归策略优化，孵化项目Spring MCP Agent演示相关能力。</li>
</ul>
<h2> 小结</h2>
<p>这次 Spring AI 的正式发布，对于 Java 开发者来说，是一次重大的升级。它不仅提供了强大的 AI 能力，还提供了丰富的工具和框架，帮助开发者更轻松地构建 AI 应用。DD也一直有在用Spring AI，最近也会第一时间把项目升级到正式版本，如果遇到问题，再继续分享。</p>
<p>最后，做个小调研，你现在用什么框架来构建AI应用呢？留言区聊一聊吧～</p>
<p>感谢阅读！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/46e70f8dc7e89800d6ac965d62657416.png" type="image/png"/>
    </item>
    <item>
      <title>Spring AI 支持模型的横向对比</title>
      <link>https://spring.didispace.com/article/20250521-spring-ai-model-comparison.html</link>
      <guid>https://spring.didispace.com/article/20250521-spring-ai-model-comparison.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring AI 支持模型的横向对比</source>
      <description>Spring AI 支持模型的横向对比 Spring AI支持的模型横向对比： 供应商 多模态支持 工具/功能支持 流式传输支持 重试支持 可观测性支持 内置JSON支持 本地化支持 兼容OpenAI API Anthropic Claude 文本、PDF、图像 ✅ ✅ ✅ ✅ ❌ ❌ ❌ Azure OpenAI 文本、图像 ✅ ✅ ✅ ✅ ✅ ❌ ✅ DeepSeek (OpenAI代理) 文本 ❌ ✅ ✅ ✅ ✅ ✅ ✅ Google VertexAI Gemini 文本、PDF、图像、音频、视频 ✅ ✅ ✅ ✅ ✅ ❌ ✅ Groq (OpenAI代理) 文本、图像 ✅ ✅ ✅ ✅ ❌ ❌ ✅ HuggingFace 文本 ❌ ❌ ❌ ❌ ❌ ❌ ❌ Mistral AI 文本、图像 ✅ ✅ ✅ ✅ ✅ ❌ ✅ MiniMax 文本 ✅ ✅ ✅ ✅ ❌ ❌ （未明确） Moonshot AI 文本 ❌ ✅ ✅ ✅ ❌ ❌ （未明确） NVIDIA (OpenAI代理) 文本、图像 ✅ ✅ ✅ ✅ ❌ ❌ ✅ OCI GenAI/Cohere 文本 ❌ ❌ ❌ ✅ ❌ ❌ ❌ Ollama 文本、图像 ✅ ✅ ✅ ✅ ✅ ✅ ✅ OpenAI 输入：文本、图像、音频输出：文本、音频 ✅ ✅ ✅ ✅ ✅ ❌ ✅ Perplexity (OpenAI代理) 文本 ❌ ✅ ✅ ✅ ❌ ❌ ✅ QianFan 文本 ❌ ✅ ✅ ✅ ❌ ❌ ❌ ZhiPu AI 文本 ✅ ✅ ✅ ✅ ❌ ❌ ❌ Amazon Bedrock Converse 文本、图像、视频、文档（PDF、HTML、MD、DOCX等） ✅ ✅ ✅ ✅ ❌ ❌ ❌</description>
      <category>Spring AI</category>
      <pubDate>Wed, 21 May 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring AI 支持模型的横向对比</h1>
<p>Spring AI支持的模型横向对比：</p>
<table>
<thead>
<tr>
<th>供应商</th>
<th>多模态支持</th>
<th>工具/功能支持</th>
<th>流式传输支持</th>
<th>重试支持</th>
<th>可观测性支持</th>
<th>内置JSON支持</th>
<th>本地化支持</th>
<th>兼容OpenAI API</th>
</tr>
</thead>
<tbody>
<tr>
<td>Anthropic Claude</td>
<td>文本、PDF、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Azure OpenAI</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>DeepSeek (OpenAI代理)</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>Google VertexAI Gemini</td>
<td>文本、PDF、图像、音频、视频</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>Groq (OpenAI代理)</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>HuggingFace</td>
<td>文本</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Mistral AI</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>MiniMax</td>
<td>文本</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>（未明确）</td>
</tr>
<tr>
<td>Moonshot AI</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>（未明确）</td>
</tr>
<tr>
<td>NVIDIA (OpenAI代理)</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>OCI GenAI/Cohere</td>
<td>文本</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Ollama</td>
<td>文本、图像</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>OpenAI</td>
<td>输入：文本、图像、音频<br>输出：文本、音频</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>Perplexity (OpenAI代理)</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>QianFan</td>
<td>文本</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>ZhiPu AI</td>
<td>文本</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>Amazon Bedrock Converse</td>
<td>文本、图像、视频、文档（PDF、HTML、MD、DOCX等）</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
</tbody>
</table>
<p>感谢阅读！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot的Docker Layer优化：缩小镜像体积并提升启动速度</title>
      <link>https://spring.didispace.com/article/20250517-spring-boot-docker-layer-optimizations.html</link>
      <guid>https://spring.didispace.com/article/20250517-spring-boot-docker-layer-optimizations.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot的Docker Layer优化：缩小镜像体积并提升启动速度</source>
      <description>Spring Boot的Docker Layer优化：缩小镜像体积并提升启动速度 容器化Spring Boot应用很简单，但低效的Docker镜像会导致部署臃肿、启动缓慢和安全风险。 本文我们将使用多阶段构建、Spring Boot的Layer工具和类数据共享(CDS)来优化Docker镜像，将体积缩小达60%，启动时间减少30%。 为什么要优化Docker Layer？ 更小的镜像：加速CI/CD流程并降低云存储成本 更快的启动：对无服务和自动扩展环境至关重要 安全性：通过排除构建时依赖来最小化攻击面 高效缓存：通过隔离频繁变更的代码来优化重建</description>
      <category>Spring Boot</category>
      <pubDate>Sat, 17 May 2025 01:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot的Docker Layer优化：缩小镜像体积并提升启动速度</h1>
<p>容器化Spring Boot应用很简单，但低效的Docker镜像会导致部署臃肿、启动缓慢和安全风险。</p>
<p>本文我们将使用<strong>多阶段构建</strong>、Spring Boot的Layer工具和类数据共享(CDS)来优化Docker镜像，将体积缩小达<strong>60%</strong>，启动时间减少<strong>30%</strong>。</p>
<h2> 为什么要优化Docker Layer？</h2>
<ul>
<li><strong>更小的镜像</strong>：加速CI/CD流程并降低云存储成本</li>
<li><strong>更快的启动</strong>：对无服务和自动扩展环境至关重要</li>
<li><strong>安全性</strong>：通过排除构建时依赖来最小化攻击面</li>
<li><strong>高效缓存</strong>：通过隔离频繁变更的代码来优化重建</li>
</ul>
<h2> 1. 多阶段构建：分离构建与运行时</h2>
<p>多阶段构建会从最终镜像中丢弃不必要的构建工具(如Maven、JDK)。</p>
<p><img src="https://static.didispace.com/images3/07bbb5520caf878bae65293121ecc313.png" alt=""></p>
<h3> 未优化的Dockerfile</h3>
<div class="language-docker line-numbers-mode" data-ext="docker"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><img src="https://static.didispace.com/images3/7ebc414df63c2a93fd2ec8b512fa3099.png" alt="镜像大小"></p>
<h3> 使用多阶段优化后的Dockerfile</h3>
<div class="language-docker line-numbers-mode" data-ext="docker"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><img src="https://static.didispace.com/images3/e50ebcf623d7ca5d8275b6060bd9600a.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/3683e08a250429e8ee9a77ab33d566ec.png" alt=""></p>
<p><strong>关键优势</strong>:</p>
<ul>
<li>从最终镜像中移除了构建工具(Maven、JDK)</li>
<li>使用更小的JRE基础镜像而非JDK</li>
</ul>
<h1> 2. Spring Boot Layer工具：分离依赖</h1>
<p>虽然多阶段构建减小了镜像体积，但Spring Boot的Layer工具通过将依赖与应用程序代码分离进一步优化了缓存，最小化开发过程中的重建。<code>layertools</code>模式将JAR分为：</p>
<ol>
<li><strong>dependencies</strong>：已发布的库（<em>很少变化</em>）</li>
<li><strong>spring-boot-loader</strong>：Spring内部组件</li>
<li><strong>snapshot-dependencies</strong>：SNAPSHOT库（<em>偶尔变化</em>）</li>
<li><strong>application</strong>：您的代码（<em>频繁变化</em>）</li>
</ol>
<h3> 步骤1：提取 layer</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 步骤2：分层优化的 Dockerfile</h3>
<div class="language-docker line-numbers-mode" data-ext="docker"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>优势</strong>:</p>
<ul>
<li>对<code>application</code>层的更改不会使缓存的依赖失效</li>
<li>最终镜像：<strong>~300MB</strong>（额外减少33%）</li>
</ul>
<h2> 3. 类数据共享(CDS)：加速启动</h2>
<p>类数据共享(CDS)通过将核心Java类预加载到共享存档中，减少JVM启动时间，避免JVM每次运行时重新解析这些类。</p>
<h3> 步骤1：生成CDS存档</h3>
<p>运行应用一次以创建类列表和存档：</p>
<div class="language-docker line-numbers-mode" data-ext="docker"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>**注意：**需要在受控环境中生成存档，因为这需要运行应用程序。</p>
<p><img src="https://static.didispace.com/images3/09fe8f1a3f71e7653561e324ad84a0fc.png" alt=""></p>
<h3> 步骤2：上传 Dockerfile</h3>
<div class="language-docker line-numbers-mode" data-ext="docker"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>结果</strong>：启动时间从<strong>2.1秒降至1.05秒</strong>(提速40%)</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/07bbb5520caf878bae65293121ecc313.png" type="image/png"/>
    </item>
    <item>
      <title>使用Prometheus和Grafana监控Spring Boot应用</title>
      <link>https://spring.didispace.com/article/20250516-spring-boot-prometheus-grafana.html</link>
      <guid>https://spring.didispace.com/article/20250516-spring-boot-prometheus-grafana.html</guid>
      <source url="https://spring.didispace.com/rss.xml">使用Prometheus和Grafana监控Spring Boot应用</source>
      <description>使用Prometheus和Grafana监控Spring Boot应用 在现代云原生应用中，指标数据是系统可观测性的命脉。它们能准确反映应用的健康状态是运行良好还是濒临崩溃。Spring Boot 结合 Prometheus 和 Grafana，构建了一套强大的指标采集、存储与可视化解决方案。 本文将指导您将Spring Boot应用打造成指标生成引擎，并构建令运维团队惊艳的监控仪表盘。 第一步：使用Micrometer和Prometheus导出指标 添加相关依赖 首先在项目中添加micrometer-registry-prometheus依赖。</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 16 May 2025 10:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 使用Prometheus和Grafana监控Spring Boot应用</h1>
<p>在现代云原生应用中，指标数据是系统可观测性的命脉。它们能准确反映应用的健康状态是运行良好还是濒临崩溃。Spring Boot 结合 Prometheus 和 Grafana，构建了一套强大的指标采集、存储与可视化解决方案。</p>
<p>本文将指导您将Spring Boot应用打造成指标生成引擎，并构建令运维团队惊艳的监控仪表盘。</p>
<h1> 第一步：使用Micrometer和Prometheus导出指标</h1>
<h2> 添加相关依赖</h2>
<p>首先在项目中添加<code>micrometer-registry-prometheus</code>依赖。</p>
<p>该依赖使Spring Boot Actuator能够以Prometheus可抓取的格式暴露指标。</p>
<p><strong>Maven</strong>:</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Gradle</strong>:</p>
<div class="language-gradle line-numbers-mode" data-ext="gradle"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 暴露Prometheus端点</h2>
<p>在<code>application.properties</code>中启用端点：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>/actuator/prometheus</code> 端点现在以 Prometheus 格式提供指标数据。</li>
<li><code>management.metrics.tags.application</code> 为所有指标添加自定义标签（在多服务架构中很有用）。</li>
</ul>
<p><strong>验证端点</strong>：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>您将看到原始指标数据，如 <code>jvm_memory_used_bytes</code>（JVM内存使用字节数）和 <code>http_server_requests_seconds_count</code>（HTTP请求计数）。</p>
<h1> 第二步：配置Prometheus抓取指标</h1>
<p>创建一个<code>prometheus.yml</code>配置文件来配置抓取您的Spring Boot应用指标：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过Docker运行Prometheus:</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>访问<code>http://localhost:9090/targets</code>确认运行成功。</p>
<h1> 第三步：构建Grafana仪表盘</h1>
<h2> 使用Grafana连接Prometheus</h2>
<p>安装运行Grafana:</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>访问 <code>http://localhost:3000</code> 登录Grafana (默认账密: admin/admin).</p>
<p>增加Prometheus数据源：</p>
<ul>
<li>URL地址：<code>http://host.docker.internal:9090</code>（Docker环境使用）或 <code>http://localhost:9090</code>（非Docker环境）</li>
</ul>
<h2> 导入预置仪表盘</h2>
<p>Grafana 社区为 Spring Boot 提供了丰富的仪表盘模板。以下是一些推荐：</p>
<p><strong>JVM 指标</strong>：使用仪表盘 ID <code>4701</code>。</p>
<ul>
<li>跟踪内存、线程、GC和CPU使用情况。</li>
</ul>
<p><strong>HTTP 指标</strong>：使用仪表盘 ID <code>6756</code>。</p>
<ul>
<li>监控请求率、延迟和错误百分比。</li>
</ul>
<p><strong>导入方式</strong>：</p>
<ul>
<li>点击 "+" → "Import" → 输入仪表盘 ID。</li>
</ul>
<h1> 第四步：创建自定义指标</h1>
<p>让我们来跟踪一个业务相关的指标：<strong>登录尝试次数</strong>。</p>
<h2> 使用MeterRegistry定义自定义指标</h2>
<p>注入MeterRegistry并创建计数器：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 在服务中使用指标</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这将生成三个时间序列：</p>
<ul>
<li><code>login_attempts_total{type="auth"}</code> — 跟踪登录尝试总次数</li>
<li><code>login_attempts_total{type="auth", result="success"}</code> — 跟踪成功登录尝试次数</li>
<li><code>login_attempts_total{type="auth", result="failure"}</code> — 跟踪失败登录尝试次数</li>
</ul>
<h1> 第五步：在Grafana中可视化自定义指标</h1>
<p>创建跟踪登录尝试的仪表盘：</p>
<p><strong>新增一个新的面板</strong> → “Add panel”.</p>
<p><strong>Query</strong>:</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><ul>
<li>使用 <code>rate()</code> 函数计算5分钟内的每秒平均请求率</li>
</ul>
<p><strong>可视化配置</strong>：</p>
<ul>
<li>选择"Graph"图表类型以查看趋势。</li>
<li>将"result"标签分成独立的线条显示。</li>
</ul>
<p><strong>专业建议</strong>：添加统计面板展示总尝试次数：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h1> 生产环境最佳实践</h1>
<ol>
<li><strong>避免高基数</strong>：不要使用无界标签（如用户ID）</li>
<li><strong>使用层级化指标</strong>：按<code>http.requests.total</code>格式组织指标名称</li>
<li><strong>端点安全</strong>：使用Spring Security保护<code>/actuator/prometheus</code>端点</li>
<li><strong>设置告警</strong>：针对错误率激增或JVM内存溢出配置Grafana告警</li>
</ol>
<h1> 小结</h1>
<p>通过Prometheus和Grafana，您不仅是在监控应用——更是在深入理解系统运行。指标数据将转化为可读性极强的运维叙事，比如：</p>
<ul>
<li>"为什么昨天的登录尝试次数下降了？</li>
<li>"新版本发布对GC暂停有什么影响？"</li>
</ul>
<p>通过添加自定义指标，您可以将原始数据转化为可执行的洞察。现在就开始全面埋点监控——当你在深夜调试系统故障时，未来的你会感谢现在的决定！</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Hibernate中@Formula注解的最佳实践</title>
      <link>https://spring.didispace.com/article/hibernate-formula.html</link>
      <guid>https://spring.didispace.com/article/hibernate-formula.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Hibernate中@Formula注解的最佳实践</source>
      <description>Hibernate中@Formula注解的最佳实践 1. @Formula注解是什么？ 1.1 动态计算字段值 Hibernate中的**@Formula注解允许您在实体中映射计算字段。不同于直接将数据库列映射到Java字段，@Formula**可定义SQL表达式用于字段值的动态计算。该特性特别适用于依赖其他列或表数据的只读字段。 例如，在Employee实体中需要根据first_name和last_name列生成全名，使用**@Formula**即可实现无需数据库存储全名：</description>
      <category>Spring Data</category>
      <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Hibernate中@Formula注解的最佳实践</h1>
<h1> 1. @Formula注解是什么？</h1>
<h2> 1.1 动态计算字段值</h2>
<p>Hibernate中的**@Formula<strong>注解允许您在实体中映射计算字段。不同于直接将数据库列映射到Java字段，</strong>@Formula**可定义SQL表达式用于字段值的动态计算。该特性特别适用于依赖其他列或表数据的只读字段。</p>
<p>例如，在Employee实体中需要根据first_name和last_name列生成全名，使用**@Formula**即可实现无需数据库存储全名：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 1.2 实现原理</h2>
<p>Hibernate在查询实体时会将**@Formula**定义的SQL表达式直接注入SELECT语句。每次实体加载时都会重新计算表达式值，非常适合基于其他列或关联表数据的动态字段。</p>
<p>上例中，每次加载Employee时Hibernate会执行SQL：SELECT concat(first_name, ' ', last_name) AS full_name FROM employee 来计算fullName字段。</p>
<h2> 1.3 只读特性</h2>
<p><strong>@Formula</strong>的核心特性是只读性。虽然可以映射计算字段到实体，但无法通过修改该字段值来更新数据库。这种特性有利于性能优化，因为字段值在每次查询时都会重新计算。</p>
<h1> 2. @Formula实战应用</h1>
<h2> 2.1 聚合值计算</h2>
<p>复杂场景示例：需要从多个订单明细计算订单总价。使用**@Formula**创建计算字段自动汇总明细价格：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>该案例中，<strong>@Formula</strong>通过子查询计算关联OrderLine实体的总价，避免数据库冗余存储，每次订单查询时自动重新计算。</p>
<h2> 2.2 多表数据整合</h2>
<p><strong>@Formula</strong>可整合多表数据。例如根据PerformanceReview和Training相关实体计算员工综合评分：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 2.3 最佳实践</h2>
<p><strong>SQL表达式优化</strong>：由于**@Formula**在每次实体加载时执行，需确保SQL表达式高效。复杂子查询可能影响性能，应谨慎使用。</p>
<p><strong>只读场景专用</strong>：适用于派生字段计算，写入场景建议直接映射列或使用@Transient进行Java端计算。</p>
<p><strong>结合延迟加载</strong>：当公式依赖复杂查询时，建议使用延迟加载策略避免不必要的性能消耗。</p>
<p><strong>排序限制</strong>：使用**@Formula**字段排序可能导致性能问题，因排序需依赖数据库层SQL表达式计算。</p>
<h2> 2.4 效果演示</h2>
<p>实际订单查询SQL示例：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>查询结果示例：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可见totalPrice字段根据OrderLine表数据动态计算生成。</p>
<h1> 3. 总结</h1>
<p>本文详细解析了Hibernate**@Formula<strong>注解，展示了从简单字段拼接至复杂SQL表达式应用场景。作为增强Hibernate映射灵活性的利器，需注意性能优化与适用场景。遵循本文最佳实践，可有效简化实体映射并保持数据库结构整洁。如有关于</strong>@Formula**或Hibernate的疑问，欢迎留言讨论！</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>我用这些 JVM 参数将 Spring Boot 应用性能提升了 300%</title>
      <link>https://spring.didispace.com/article/spring-boot-jvm-flags.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-jvm-flags.html</guid>
      <source url="https://spring.didispace.com/rss.xml">我用这些 JVM 参数将 Spring Boot 应用性能提升了 300%</source>
      <description>我用这些 JVM 参数将 Spring Boot 应用性能提升了 300% 当你的 Spring Boot 应用响应迟缓，且已采用缓存、数据库索引和异步处理优化后，下一个优化方向在哪里？我的答案是 JVM 本身。 经过性能分析和深入研究，我发现合理配置 JVM 参数可以带来显著的性能提升——在负载压力下实现高达 300% 的响应速度提升，且无需修改任何应用代码。 本文将详解： 实际采用的 JVM 参数配置 这些参数生效的原理 优化前后的基准测试对比 安全调优的实施方法</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 14 May 2025 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 我用这些 JVM 参数将 Spring Boot 应用性能提升了 300%</h1>
<p>当你的 Spring Boot 应用响应迟缓，且已采用缓存、数据库索引和异步处理优化后，下一个优化方向在哪里？我的答案是 JVM 本身。</p>
<p>经过性能分析和深入研究，我发现合理配置 JVM 参数可以带来显著的性能提升——在负载压力下实现高达 300% 的响应速度提升，且无需修改任何应用代码。</p>
<p>本文将详解：</p>
<ul>
<li>实际采用的 JVM 参数配置</li>
<li>这些参数生效的原理</li>
<li>优化前后的基准测试对比</li>
<li>安全调优的实施方法</li>
</ul>
<h1> 基准测试：性能瓶颈在哪？</h1>
<p>我的应用是一个典型的 Spring Boot REST API，使用 PostgreSQL 数据库和 Hibernate ORM。主要处理 CPU 密集的 JSON 转换和中等强度的数据库查询。</p>
<p>使用 <a href="https://github.com/wg/wrk" target="_blank" rel="noopener noreferrer">wrk</a> 进行压力测试：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h1> 优化前性能指标</h1>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>常规负载下表现尚可，但在生产级流量下延迟峰值难以接受。</p>
<h1> 第一步：启用 G1 垃圾回收器</h1>
<p>Spring Boot 应用通常内存消耗较大，特别是在处理 JSON 数据和 Hibernate 会话时。<strong>G1 垃圾回收器</strong>专为需要大堆内存和低暂停时间的应用设计。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><blockquote>
<p><em>G1 将堆内存划分为多个区域并行回收，有效减少「全局停顿」时间。</em></p>
</blockquote>
<h1> 进阶调优：降低延迟</h1>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>这些参数要求 JVM 将 GC 停顿控制在 100ms 以内，并通过并行引用处理降低长尾延迟。</p>
<h1> 第二步：压缩类与对象元数据</h1>
<p>减少内存占用可为应用数据腾出更多空间，同时降低 GC 频率。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>多数 64 位 JVM 默认启用这些参数，显式声明可确保跨环境一致性。</p>
<h1> 第三步：调整线程栈大小</h1>
<p>每个线程默认占用 1MB 栈内存。当应用创建数百个线程（如数据库连接池、异步工作线程）时，内存消耗将显著增加。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>将线程栈调整为 256KB（经负载测试验证安全），释放堆内存同时避免栈溢出。</p>
<h1> 第四步：启动时预分配内存</h1>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>强制 JVM 在启动时完成内存分配，避免运行时因内存分页导致的性能波动。</p>
<h1> 第五步：预热 JIT 编译器</h1>
<p>JVM 性能会随着 <strong>JIT 编译器</strong>对热点代码的优化逐步提升。以下参数可加速优化过程：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>字符串去重功能可减少处理大型 JSON 数据时的内存重复占用。</p>
<h1> 完整 JVM 参数清单</h1>
<p>最终采用的优化配置：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可在 <code>application.properties</code> 或 Dockerfile 中配置：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h1> 优化后性能表现</h1>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h1> 实施建议</h1>
<ol>
<li><strong>环境适配测试</strong> - JVM 行为因工作负载和硬件差异可能不同</li>
<li><strong>启用 GC 日志</strong>：</li>
</ol>
<ul>
<li><code>-Xlog:gc*:file=gc.log:time,uptime,level,tags</code></li>
</ul>
<ol start="3">
<li>根据容器内存限制或实例规格调整堆大小 (<code>-Xms</code>, <code>-Xmx</code>)</li>
</ol>
<h1> 结论</h1>
<p>对于生产环境的 Spring Boot 应用，合理配置 JVM 参数能释放巨大性能潜力。通过本文的调优方案，我实现了 3 倍性能提升——完全无需修改 Java 代码。</p>
<p>建议从 <code>-XX:+UseG1GC</code> 和 <code>-XX:MaxGCPauseMillis=100</code> 开始，再根据具体性能表现逐步添加其他参数。</p>
<p>请谨记：JVM 绝非黑盒。通过恰当调优，这台强大的机器定能展翅高飞。</p>
<blockquote>
<p>本文翻译自：https://medium.com/@kanishks772/i-boosted-my-spring-boot-apps-performance-by-300-with-these-jvm-flags-4935cb442e9b</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>没有Spring AOP的话，Java代码很难保持简洁</title>
      <link>https://spring.didispace.com/article/spring-aop-java-code-clean.html</link>
      <guid>https://spring.didispace.com/article/spring-aop-java-code-clean.html</guid>
      <source url="https://spring.didispace.com/rss.xml">没有Spring AOP的话，Java代码很难保持简洁</source>
      <description>没有Spring AOP的话，Java代码很难保持简洁 如果你曾经在Java企业级代码库中工作过，你很可能遇到过一个Controller或Service做了太多事情 — 业务逻辑、日志记录、验证、认证、指标统计、重试等。所有这些都在一个方法中。 结果如何？代码难以阅读，更难测试，几乎不可能干净地扩展。 所以，我要做出一个大胆的声明： 在企业级应用中，如果没有面向切面编程（AOP），编写干净、可维护的Java代码是不可能的。 让我来解释原因，以及如何使用AOP为最混乱的Spring后端带来清晰和秩序。</description>
      <category>Spring</category>
      <pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 没有Spring AOP的话，Java代码很难保持简洁</h1>
<p>如果你曾经在Java企业级代码库中工作过，你很可能遇到过一个Controller或Service做了太多事情 — 业务逻辑、日志记录、验证、认证、指标统计、重试等。所有这些都在一个方法中。</p>
<p>结果如何？代码难以阅读，更难测试，几乎不可能干净地扩展。</p>
<p>所以，我要做出一个大胆的声明：
<strong>在企业级应用中，如果没有面向切面编程（AOP），编写干净、可维护的Java代码是不可能的。</strong></p>
<p>让我来解释原因，以及如何使用AOP为最混乱的Spring后端带来清晰和秩序。</p>
<h2> 什么是AOP？</h2>
<p>**面向切面编程（AOP）**是一种编程范式，它允许你将横切关注点（日志记录、安全、事务等）与业务逻辑分开模块化。</p>
<p><em>在Java中，AOP通常使用注解和代理来实现 — 特别是通过Spring AOP或AspectJ。</em></p>
<h2> 问题：横切关注点</h2>
<p>让我们看一个典型的Spring服务：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>现在想象这个方法在20个服务中。每一个都混合了：</p>
<ul>
<li>日志记录</li>
<li>验证</li>
<li>监控</li>
<li>错误处理</li>
<li>重试逻辑</li>
</ul>
<p>每个关注点都被重复和纠缠在一起，违反了<strong>单一职责</strong>原则，使得关注点的清晰分离变得不可能。</p>
<h2> 解决方案：AOP来拯救</h2>
<p>通过Spring AOP，你可以将这些横切关注点提取到可组合、可重用的<strong>切面</strong>中。</p>
<h2> 日志切面</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 验证切面</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 指标切面</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 干净的服务代码</h2>
<p>现在你的服务只关注业务逻辑：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这才是干净代码应该有的样子 — <strong>简短、专注且可测试</strong>。</p>
<h2> 真实基准测试：使用和不使用AOP</h2>
<h3> 测试设置</h3>
<ul>
<li>100,000次服务方法调用</li>
<li>关注点：日志记录、验证、指标</li>
<li>使用和不使用AOP的Spring Boot应用</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p><em>AOP每个方法增加了约300ns的开销。但可维护性、可重用性和可测试性得到了显著改善。</em></p>
</blockquote>
<h2> 对AOP的常见反对意见</h2>
<h3> "它难以调试。"</h3>
<p>只有在过度使用时才会如此。保持切面小而专注，并始终为它们编写测试。Spring的AOP日志功能（<code>@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)</code>）使调试变得可预测。</p>
<h3> "它太魔幻了。"</h3>
<p>当有文档记录且保持一致时，魔幻就变得可维护。相比之下，在每个方法中硬编码每个关注点是你将来必须偿还的技术债务。</p>
<h3> 什么时候不该使用AOP</h3>
<ul>
<li>你需要<strong>底层</strong>控制（例如，微优化）</li>
<li>你的团队不熟悉AOP且缺乏约定</li>
<li>你在Spring生态系统之外（例如，普通Java SE应用）</li>
</ul>
<p>在这些情况下，显式装饰器或函数组合可能更好。</p>
<h2> 最后的思考</h2>
<p>AOP不是为了抽象而抽象。它是关于<strong>干净地分离关注点的代码编写</strong>。</p>
<p>当正确使用时：</p>
<ul>
<li>你的服务变得可读</li>
<li>你的关注点变得可重用</li>
<li>你的代码库变得可测试</li>
</ul>
<p>如果你在2025年还在编写企业级Java代码，仍然在每个方法中手动复制日志记录、验证和指标，你写的不是干净的代码 — 你写的是杂乱的代码。</p>
<blockquote>
<p>本文翻译自：https://medium.com/@kanishksinghpujari/why-writing-clean-java-code-is-impossible-without-aop-change-my-mind-535e89489909</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>如何为Spring AI MCP Server提供OAuth2认证</title>
      <link>https://spring.didispace.com/article/spring-ai-mcp-server-oauth2.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-mcp-server-oauth2.html</guid>
      <source url="https://spring.didispace.com/rss.xml">如何为Spring AI MCP Server提供OAuth2认证</source>
      <description>如何为Spring AI MCP Server提供OAuth2认证 Spring AI 提供了对模型上下文协议（简称 MCP）的支持，该协议允许人工智能模型以结构化的方式与外部工具和资源进行交互并访问它们。 借助 Spring AI，开发人员只需几行代码就可以创建自己的 MCP 服务器，并向人工智能模型公开功能。</description>
      <category>Spring AI</category>
      <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 如何为Spring AI MCP Server提供OAuth2认证</h1>
<p>Spring AI 提供了对<a href="https://docs.spring.io/spring-ai/reference/1.0/api/mcp/mcp-overview.html" target="_blank" rel="noopener noreferrer">模型上下文协议</a>（简称 MCP）的支持，该协议允许人工智能模型以结构化的方式与外部工具和资源进行交互并访问它们。</p>
<p>借助 Spring AI，开发人员只需几行代码就可以创建自己的 MCP 服务器，并向人工智能模型公开功能。</p>
<h2> MCP 中的授权与安全</h2>
<p>MCP 服务器可以使用标准输入输出（STDIO）传输方式在本地运行。要将 MCP 服务器暴露到外部，它必须公开一些标准的 HTTP 端点。虽然私人使用的 MCP 服务器可能不需要严格的身份验证，但企业部署需要对暴露的端点进行完备的安全和权限管理。上周发布的<a href="https://spec.modelcontextprotocol.io/specification/2025-03-26/" target="_blank" rel="noopener noreferrer">最新版本的 MCP 规范（2025 年 3 月 26 日）</a>解决了这一挑战。它利用广泛使用的<a href="https://oauth.net/2/" target="_blank" rel="noopener noreferrer">OAuth2 框架</a>，为确保客户端与服务器之间的通信安全奠定了基础。</p>
<p>虽然我们不会在这篇博客文章中对 OAuth2 进行全面回顾，但快速复习一下可能会很有用。在该规范草案中，MCP 服务器既是资源服务器，也是授权服务器。</p>
<p>作为资源服务器，它通过检查 “Authorization” 标头对传入的请求进行授权检查。该标头必须包含一个 OAuth2 “access_token”（访问令牌），这是一个表示客户端 “权限” 的字符串。该令牌可以是 JSON Web Token（JWT），也可以是一个本身不携带信息的不透明字符串。如果令牌缺失或无效（格式错误、过期、接收方错误等），资源服务器将拒绝该请求。使用这些令牌，一个典型的请求可能如下所示：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>作为授权服务器，MCP 服务器还必须能够以安全的方式为客户端颁发 “access_token”（访问令牌）。在颁发令牌之前，服务器将验证客户端的凭据，在某些情况下，还会验证试图访问服务器的用户身份。授权服务器将决定令牌的属性：有效期、作用域、目标受众等。</p>
<p>使用 Spring Security 和 Spring Authorization Server，我们可以轻松地将这两种功能添加到现有的 Spring MCP 服务器中。</p>
<p><img src="https://static.didispace.com/images3/cf419b4f95528a4895055b029bc3e2b7.png" alt="包含 MCP 客户端和 MCP 服务器的 OAuth2 示意图"></p>
<h2> 为 MCP Server 添加 OAuth2 支持</h2>
<p>在这个示例中，我们将为一个示例 MCP 服务器添加 OAuth 2 支持，该服务器是我们 Spring AI 示例仓库中的<a href="https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webmvc-server" target="_blank" rel="noopener noreferrer">“天气” MCP 工具</a>。我们不会探讨交互的客户端方面，只确保我们的服务器能够颁发令牌并验证它们。</p>
<p>首先，我们在 <code>pom.xml</code> 中导入所需的 Spring Boot 启动器：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后，我们在 <code>application.properties</code> 中配置一个 OAuth2 客户端，以便我们可以请求访问令牌：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这是最简单的客户端。我们可以通过发送 POST 请求直接与授权服务器交互，无需浏览器，并使用硬编码的凭据 <code>mcp-client</code> / <code>secret</code>。</p>
<p>最后一步是启用授权服务器和资源服务器功能。我们通过为安全功能创建一个配置类（例如 <code>SecurityConfiguration</code>）来实现这一点，在该类中我们公开一个 <code>SecurityFilterChain</code>  bean：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个过滤器链将执行以下多项操作：</p>
<ul>
<li>确保每个请求都经过身份验证。有了这一点，我们的 MCP 服务器将只允许带有 “access_token”（访问令牌）的请求。</li>
<li>启用 Spring Authorization Server 和 Spring Resource Server。</li>
<li>关闭跨站请求伪造（CSRF）防护。MCP 服务器不是为基于浏览器的交互而设计的，不需要 CSRF 防护。</li>
<li>启用跨域资源共享（CORS）支持，这样我们就可以使用 MCP 检查器对服务器进行演示。</li>
</ul>
<p>有了这些设置，我们的应用程序就得到了保护，只会接受带有访问令牌的请求。否则，请求将被拒绝，并返回 <code>HTTP 401 Unauthorized</code>（未经授权）错误。例如：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要使用我们的 MCP 服务器，我们首先需要获取一个访问令牌。我们使用 <code>client_credentials</code> 这种 OAuth2 授权类型，它用于 “机器对机器” 或 “服务账户” 场景：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>复制 <code>access_token</code> 的值。它以字母 “ey” 开头。现在我们可以使用这个访问令牌发出请求，并且这些请求应该会成功。例如，使用 <code>curl</code>，你可以将 <code>YOUR_ACCESS_TOKEN</code> 替换为你上面复制的值：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>自 <code>0.6.0</code> 版本起，也可以直接在 <a href="https://modelcontextprotocol.io/docs/tools/inspector" target="_blank" rel="noopener noreferrer">MCP 检查器</a>中使用访问令牌。只需启动检查器，然后将访问令牌粘贴到左侧菜单的 “Authentication &gt; Bearer”（身份验证 &gt; 承载令牌）字段中。然后点击 “Connect”（连接）：你应该能够进行 MCP 调用。</p>
<p><img src="https://static.didispace.com/images3/c97ba420f56df030b91f72bcd472a691.png" alt="MCP检查器"></p>
<p>如果你想自己运行这个示例，可以查看 <code>spring-ai-examples</code> 仓库中的<a href="https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webmvc-oauth2-server" target="_blank" rel="noopener noreferrer">示例代码</a>。</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://spring.didispace.com/" target="_blank" rel="noopener noreferrer">Spring专题技术分享点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/cf419b4f95528a4895055b029bc3e2b7.png" type="image/png"/>
    </item>
    <item>
      <title>Spring AI的提示工程技术详解</title>
      <link>https://spring.didispace.com/article/spring-ai-prompt-engineering.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-prompt-engineering.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring AI的提示工程技术详解</source>
      <description>Spring AI的提示工程技术详解 本文中的示例和模式基于全面的提示工程指南，该指南涵盖了有效的提示工程的理论、原则和模式。本文展示了如何使用Spring AI流畅的ChatClient API将这些概念转化为可运行的Java代码。 为方便起见，示例的结构遵循原始指南中概述的相同模式和技术。本文中使用的演示源代码可在以下地址获取： https://github.com/spring-projects/spring-ai-examples/tree/main/prompt-engineering/prompt-engineering-patterns</description>
      <category>Spring AI</category>
      <pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring AI的提示工程技术详解</h1>
<p>本文中的示例和模式基于全面的提示工程指南，该指南涵盖了有效的提示工程的理论、原则和模式。本文展示了如何使用Spring AI流畅的ChatClient API将这些概念转化为可运行的Java代码。</p>
<p>为方便起见，示例的结构遵循原始指南中概述的相同模式和技术。本文中使用的演示源代码可在以下地址获取：</p>
<ul>
<li>https://github.com/spring-projects/spring-ai-examples/tree/main/prompt-engineering/prompt-engineering-patterns</li>
</ul>
<h2> 1. 配置</h2>
<p>配置部分概述了如何使用Spring AI设置和调整大型语言模型（LLM）。它涵盖了为你的用例选择合适的LLM提供商，以及配置控制模型输出质量、风格和格式的重要生成参数。</p>
<h3> LLM提供商选择</h3>
<p>对于提示工程，你首先要选择一个模型。Spring AI支持多个LLM提供商（如OpenAI、Anthropic、Google Vertex AI、AWS Bedrock、Ollama等），这使你无需更改应用程序代码即可切换提供商，只需更新配置。只需添加所选的启动器依赖项<code>spring-ai-starter-model-&lt;MODEL-PROVIDER-NAME&gt;</code>。例如，以下是启用Anthropic Claude API的方法：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>同时还需要一些连接属性：<code>spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}</code></p>
<p>你可以这样指定特定的LLM模型名称：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在参考文档中可以找到启用和配置首选AI模型的详细信息。</p>
<h3> LLM输出配置</h3>
<p>在深入探讨提示工程技术之前，了解如何配置大型语言模型（LLM）的输出行为至关重要。Spring AI 提供了多个配置选项，通过 ChatOptions 构建器可以控制生成过程的各个方面。</p>
<p>所有配置都可以通过编程方式应用，如下面的示例所示，也可以在启动时通过 Spring 应用程序属性进行配置。</p>
<h4> 温度</h4>
<p>温度控制模型响应的随机性或 “创造性”。</p>
<ul>
<li>较低的值（0.0 - 0.3）：响应更具确定性和针对性。适用于事实性问题、分类或对一致性要求较高的任务。</li>
<li>中等的值（0.4 - 0.7）：在确定性和创造性之间取得平衡。适用于一般用例。</li>
<li>较高的值（0.8 - 1.0）：响应更具创造性、多样性，可能会带来惊喜。适用于创意写作、头脑风暴或生成多样化的选项。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>理解温度对于提示工程至关重要，因为不同的技术受益于不同的温度设置。</p>
<h4> 输出长度（MaxTokens）</h4>
<p><code>maxTokens</code>参数限制模型在响应中可以生成的令牌（词元）数量。</p>
<ul>
<li>较低的值（5 - 25）：适用于单个单词、短短语或分类标签。</li>
<li>中等的值（50 - 500）：适用于段落或简短解释。</li>
<li>较高的值（1000+）：适用于长篇内容、故事或复杂解释。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>设置合适的输出长度对于确保获得完整的响应且不过于冗长非常重要。</p>
<h4> 采样控制（Top-K和Top-P）</h4>
<p>这些参数使你能够在生成过程中对令牌选择过程进行精细控制。</p>
<ul>
<li>Top-K：将令牌选择限制为接下来最可能的K个令牌。较高的值（例如40 - 50）会引入更多的多样性。</li>
<li>Top-P（核采样）：动态地从累积概率超过P的最小令牌集中进行选择。常见的值如0.8 - 0.95。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这些采样控制与温度一起作用，塑造响应的特征。</p>
<h4> 结构化响应格式</h4>
<p>除了纯文本响应（使用<code>.content()</code>）之外，Spring AI还通过<code>.entity()</code>方法轻松地将LLM响应直接映射到Java对象。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当与指示模型返回结构化数据的系统提示结合使用时，此功能特别强大。</p>
<h4> 特定模型选项</h4>
<p>虽然可移植<code>ChatOptions</code>为不同的LLM提供商提供了一致的接口，但Spring AI还提供特定模型的选项类，这些类公开了提供商特定的功能和配置。这些特定模型的选项使你能够利用每个LLM提供商的独特功能。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>每个模型提供商都有自己的聊天选项实现（例如<code>OpenAiChatOptions</code>、<code>AnthropicChatOptions</code>、<code>MistralAiChatOptions</code>），这些实现公开了提供商特定的参数，同时仍然实现了通用接口。这种方法使你可以灵活地使用可移植选项以实现跨提供商的兼容性，或者在需要访问特定提供商的独特功能时使用特定模型的选项。
请注意，当使用特定模型的选项时，你的代码会与该特定提供商绑定，降低了可移植性。这是在访问高级提供商特定功能与保持应用程序中提供商独立性之间的权衡。</p>
<h2> 2. 提示工程技术</h2>
<p>以下每个部分都实现了指南中的一种特定提示工程技术。通过同时遵循 “提示工程” 指南和这些实现，你将不仅深入了解可用的提示工程技术，还将了解如何在生产Java应用程序中有效地实现它们。</p>
<h3> 2.1 零样本提示</h3>
<p>零样本提示是指在不提供任何示例的情况下要求AI执行任务。这种方法测试模型从零开始理解和执行指令的能力。大型语言模型在大量文本语料库上进行训练，使它们能够理解 “翻译”、“总结” 或 “分类” 等任务的含义，而无需明确的演示。
零样本提示适用于简单直接的任务，在这些任务中模型可能在训练期间见过类似的示例，并且当你希望最小化提示长度时也适用。然而，性能可能会因任务的复杂性以及指令的制定方式而有所不同。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此示例展示了如何在不提供示例的情况下对电影评论的情感进行分类。注意使用较低的温度（0.1）以获得更具确定性的结果，以及直接使用<code>.entity(Sentiment.class)</code>映射到Java枚举。</p>
<blockquote>
<p>参考文献：Brown, T. B., 等人 (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165. https://arxiv.org/abs/2005.14165</p>
</blockquote>
<h3> 2.2 一样本和少样本提示</h3>
<p>少样本提示为模型提供一个或多个示例，以帮助指导其响应，这对于需要特定输出格式的任务特别有用。通过向模型展示所需的输入 - 输出对示例，它可以学习模式并将其应用于新的输入，而无需进行显式的参数更新。</p>
<p>一样本提供单个示例，当示例成本高昂或模式相对简单时很有用。少样本使用多个示例（通常为3 - 5个），以帮助模型更好地理解更复杂任务中的模式，或说明正确输出的不同变体。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>少样本提示对于需要特定格式设置、处理边缘情况的任务特别有效，或者当任务定义在没有示例的情况下可能不明确时也很有用。示例的质量和多样性会显著影响性能。</p>
<blockquote>
<p>参考文献：Brown, T. B., 等人 (2020). "Language Models are Few-Shot Learners." arXiv:2005.14165. https://arxiv.org/abs/2005.14165</p>
</blockquote>
<h3> 2.3 系统提示、上下文提示和角色提示</h3>
<h4> 系统提示</h4>
<p>系统提示为语言模型设置了整体上下文和目的，定义了模型应该执行的 “大局”。它为模型的响应建立了行为框架、约束和高级目标，与特定的用户查询分开。</p>
<p>系统提示在整个对话中充当持久的 “使命声明”，使你能够设置全局参数，如输出格式、语气、道德边界或角色定义。与专注于特定任务的用户提示不同，系统提示框架了所有用户提示应如何被解释。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当系统提示与Spring AI的实体映射功能结合使用时特别强大：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>系统提示对于多轮对话特别有价值，可确保在多个查询中行为一致，并用于建立适用于所有响应的格式约束，如JSON输出。</p>
<blockquote>
<p>参考文献：OpenAI. (2022). "System Message." https://platform.openai.com/docs/guides/chat/introduction</p>
</blockquote>
<h4> 角色提示</h4>
<p>角色提示指示模型采用特定的角色或身份，这会影响它生成内容的方式。通过为模型分配特定的身份、专业知识或视角，你可以影响其响应的风格、语气、深度和框架。</p>
<p>角色提示利用了模型模拟不同专业领域和通信风格的能力。常见的角色包括专家（例如，“你是一位经验丰富的数据科学家”）、专业人士（例如，“扮演一名导游”）或风格化角色（例如，“像莎士比亚一样解释”）。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>角色提示可以通过风格指令进行增强：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种技术对于专业领域知识、在响应中实现一致的语气以及与用户创建更引人入胜、个性化的交互特别有效。</p>
<blockquote>
<p>参考文献：Shanahan, M., 等人 (2023). "Role-Play with Large Language Models." arXiv:2305.16367. https://arxiv.org/abs/</p>
</blockquote>
<h3> 2.4 退后提示（Step-Back Prompting）</h3>
<p>退后提示通过首先获取背景知识，将复杂请求分解为更简单的步骤。该技术鼓励模型在回答具体问题之前，先“退后一步”，考虑更广泛的上下文、基本原理或与问题相关的一般知识。</p>
<p>通过将复杂问题分解为更易管理的部分并首先建立基础知识，模型能够对困难问题提供更准确的回答。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>退后提示在复杂推理任务、需要专业领域知识的问题以及希望获得更全面和深思熟虑的回答而非即时答案
时尤其有效。</p>
<blockquote>
<p>参考文献： Zheng, Z., 等 (2023)。《退后一步：通过抽象激发大型语言模型的推理》。arXiv:2310.06117。 https://arxiv.org/abs/2310.06117</p>
</blockquote>
<h3> 2.5 思维链（Chain of Thought, CoT）</h3>
<p>思维链提示鼓励模型通过逐步推理来解决问题，从而提高复杂推理任务的准确性。通过明确要求模型展示其工作过程或以逻辑步骤思考问题，可以显著提升需要多步推理的任务表现。
CoT 通过鼓励模型在生成最终答案之前生成中间推理步骤，类似于人类解决复杂问题的方式。这使得模型的思考过程显式化，并帮助其得出更准确的结论。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>关键短语“让我们一步步思考”触发模型展示其推理过程。CoT 对于数学问题、逻辑推理任务以及任何需要多步推理的问题尤其有价值。它通过显式化中间推理步骤来减少错误。</p>
<blockquote>
<p>参考文献： Wei, J., 等 (2022)。《思维链提示激发大型语言模型的推理》。arXiv:2201.11903。 https://arxiv.org/abs/2201.11903</p>
</blockquote>
<h3> 2.6 自我一致性（Self-Consistency）</h3>
<p>自我一致性涉及多次运行模型并聚合同一问题的结果，以获得更可靠的答案。该技术通过为同一问题采样不同的推理路径并通过多数投票选择最一致的答案，解决了大型语言模型输出的变异性。
通过使用不同的温度或采样设置生成多个推理路径，然后聚合同一问题的最终答案，自我一致性提高了复杂推理任务的准确性。这本质上是一种针对语言模型输出的集成方法。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>自我一致性在高风险决策、复杂推理任务以及需要比单一响应更自信的答案时尤其有价值。代价是由于多次 API 调用而增加的计算成本和延迟。</p>
<blockquote>
<p>参考文献： Wang, X., 等 (2022)。《自我一致性改进语言模型中的思维链推理》。arXiv:2203.11171。 https://arxiv.org/abs/2203.11171</p>
</blockquote>
<h3> 2.7 思维树（Tree of Thoughts, ToT）</h3>
<p>思维树（ToT）是一种高级推理框架，通过同时探索多个推理路径扩展了思维链。它将问题解决视为搜索过程，模型生成不同的中间步骤，评估其潜力，并探索最有前景的路径。</p>
<p>该技术对于具有多种可能方法或需要探索各种替代方案以找到最优路径的复杂问题尤其强大。</p>
<p>注意：原始的“提示工程”指南未提供 ToT 的实现示例，可能是因为其复杂性。以下是一个简化的示例，展示其核心概念。
游戏解决 ToT 示例：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>参考文献： Yao, S., 等 (2023)。《思维树：使用大型语言模型进行深思熟虑的问题解决》。arXiv:2305.10601。 https://arxiv.org/abs/2305.10601</p>
</blockquote>
<h3> 2.8 自动提示工程（Automatic Prompt Engineering）</h3>
<p>自动提示工程利用 AI 生成和评估替代提示。这种元技术利用语言模型本身来创建、优化和基准测试不同的提示变体，以找到特定任务的最优表述。
通过系统地生成和评估提示变体，自动提示工程可以找到比手动工程更有效的提示，特别是在复杂任务中。这是一种利用 AI 提升自身表现的方法。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>自动提示工程对于优化生产系统中的提示、解决手动提示工程已达极限的挑战性任务以及系统性地大规模提升提示质量尤其有价值。</p>
<blockquote>
<p>参考文献： Zhou, Y., 等 (2022)。《大型语言模型是人类级别的提示工程师》。arXiv:2211.01910。 https://arxiv.org/abs/2211.01910</p>
</blockquote>
<h3> 2.9 代码提示（Code Prompting）</h3>
<p>代码提示是指针对代码相关任务的专门技术。这些技术利用大型语言模型理解和生成编程语言的能力，使其能够编写新代码、解释现有代码、调试问题以及在不同语言之间翻译。</p>
<p>有效的代码提示通常涉及明确的规格、适当的上下文（库、框架、风格指南），有时还包括类似代码的示例。温度设置通常较低（0.1-0.3），以获得更确定的输出。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>代码提示对于自动化代码文档、原型设计、学习编程概念以及在编程语言之间翻译尤其有价值。结合少样本提示或思维链等技术可以进一步增强其效果。</p>
<blockquote>
<p>参考文献： Chen, M., 等 (2021)。《评估在代码上训练的大型语言模型》。arXiv:2107.03374。 https://arxiv.org/abs/2107.03374</p>
</blockquote>
<h2> 结论</h2>
<p>Spring AI 提供了一个优雅的 Java API，用于实现所有主要的提示工程技术。通过将这些技术与 Spring 强大的实体映射和流畅的 API 结合，开发者可以构建复杂且易于维护的 AI 驱动应用程序。
最有效的方法通常是将多种技术结合使用，例如使用系统提示结合少样本示例，或将思维链与角色提示结合。Spring AI 的灵活 API 使这些组合易于实现。</p>
<p>对于生产应用程序，请记住：</p>
<ul>
<li>使用不同参数（温度、top-k、top-p）测试提示</li>
<li>考虑在关键决策中使用自我一致性</li>
<li>利用 Spring AI 的实体映射实现类型安全的响应</li>
<li>使用上下文提示提供特定于应用程序的知识</li>
</ul>
<p>凭借这些技术和 Spring AI 强大的抽象，开发者可以创建提供一致、高质量结果的强大 AI 驱动应用程序。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://spring.didispace.com/" target="_blank" rel="noopener noreferrer">Spring专题技术分享点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>如何用Spring AI构建MCP Client-Server架构</title>
      <link>https://spring.didispace.com/article/spring-ai-mcp.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-mcp.html</guid>
      <source url="https://spring.didispace.com/rss.xml">如何用Spring AI构建MCP Client-Server架构</source>
      <description>如何用Spring AI构建MCP Client-Server架构 现代 Web 应用正加速与大语言模型（LLMs）深度融合，构建超越传统问答场景的智能解决方案。为突破模型知识边界，增强上下文理解能力，开发者普遍采用多源数据集成策略，将 LLM 与搜索引擎、数据库、文件系统等外部资源互联。然而，异构数据源的协议差异与格式壁垒，往往导致集成复杂度激增，成为制约 AI 应用规模化落地的关键瓶颈。因此，Anthropic公司推出了模型上下文协议（Model Context Protocol, MCP），通过标准化接口为 AI 应用与外部数据源建立统一交互通道。这一协议体系不仅实现了数据获取与操作的规范化，更构建起可扩展的智能体开发框架，使开发者能够基于原生 LLM 能力快速构建复杂工作流。</description>
      <category>Spring AI</category>
      <pubDate>Tue, 25 Mar 2025 13:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 如何用Spring AI构建MCP Client-Server架构</h1>
<p>现代 Web 应用正加速与大语言模型（LLMs）深度融合，构建超越传统问答场景的智能解决方案。为突破模型知识边界，增强上下文理解能力，开发者普遍采用多源数据集成策略，将 LLM 与搜索引擎、数据库、文件系统等外部资源互联。然而，异构数据源的协议差异与格式壁垒，往往导致集成复杂度激增，成为制约 AI 应用规模化落地的关键瓶颈。因此，Anthropic公司推出了模型上下文协议（Model Context Protocol, MCP），通过标准化接口为 AI 应用与外部数据源建立统一交互通道。这一协议体系不仅实现了数据获取与操作的规范化，更构建起可扩展的智能体开发框架，使开发者能够基于原生 LLM 能力快速构建复杂工作流。</p>
<p>本文我们就来尝试通过 Spring AI 框架来构建MCP 客户端 - 服务器架构的实现方法。</p>
<h2> 什么是MCP</h2>
<p>关于MCP的架构，这里可以看看ByteByteGo的这张架构图：</p>
<p><img src="https://static.didispace.com/images3/bd867885949a05434f7132ff3ddca178.png" alt=""></p>
<p>MCP遵循客户端 - 服务器架构，围绕几个关键组件：</p>
<ul>
<li><strong>MCP Host</strong>：用户使用的应用程序，比如：Claude客户端、Cursor这样的AI应用程序，它与大语言模型集成，提供 AI 交互环境以访问不同工具和数据源。</li>
<li><strong>MCP Client</strong>：与MCP Server建立并维护一对一连接的组件。它属于AI应用程序的内部组件，使其能够与 MCP Server通信。例如，若需要 PostgreSQL 数据，MCP 客户端会将请求格式化为结构化消息发送给 MCP 服务器。</li>
<li><strong>MCP Server</strong>：外部数据源集成并公开与之交互功能的组件。作为中间件连接 AI 模型与外部系统（如 PostgreSQL、Google Drive 或 API）。例如，当 Claude 分析 PostgreSQL 中的销售数据时，PostgreSQL 的 MCP 服务器会充当 Claude 与数据库之间的连接器。</li>
</ul>
<p>下面我们就参考<a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html" target="_blank" rel="noopener noreferrer">Spring AI的官方文档</a>，来尝试应用简单的MCP，想要了解更多，读者可以点击链接查看官方文档。</p>
<h2> 创建MCP Host</h2>
<p>下面我们将使用Anthropic的Claude模型构建一个聊天机器人，该模型将充当我们的MCP Host。</p>
<h3> 引入相关依赖</h3>
<p>首先，将必要的依赖项添加到项目的<code>pom.xml</code>文件中：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>下面的案例使用Anthropic的Claude模型，所以使用<code>spring-ai-anthropic-spring-boot-starter</code>，如果你使用其他模型，也可以使用其他启动器依赖项，比如：使用deepseek的话也可以参考之前的<a href="/spring-ai-ollama-deepseek">《Spring AI + Ollama 实现 deepseek的API服务和调用》</a>去引入相关链接和调用实现。</li>
<li><code>spring-ai-mcp-client-spring-boot-starter</code>是引入MCP的重点，用来实现将Spring Boot应用程序与MCP服务器保持一对一连接的客户端。</li>
<li>由于Spring AI M6是一个里程碑版本，所以增加了相关<code>repository</code>的配置</li>
</ul>
<p>接下来，在application.yaml文件中配置调用大模型的密钥和模型名称：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>使用<code>${}</code>属性占位符从环境变量中加载API密钥的值。</li>
<li>模型使用<code>claude-3-7-sonnet-20250219</code>, 你也可以根据需要使用其他模型。</li>
</ul>
<p>配置上述属性后，Spring AI会自动创建一个ChatModel类型的bean，使我们能够与指定的模型进行交互。</p>
<h3> 为Brave Search和文件系统服务器配置MCP Client</h3>
<p>现在，让我们为两个预构建的MCP服务器实现（Brave Search和文件系统）配置MCP客户端。这些服务器将使我们的聊天机器人能够执行网络搜索和文件系统操作。</p>
<p>首先，在<code>application.yaml</code>文件中为Brave Search MCP Server注册一个MCP Client：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在这里，我们配置了一个使用<code>stdio</code>传输的客户端，使用npx命令来下载并运行<code>@modelcontextprotocol/server-brave-search</code>，并使用<code>-y</code>标志自动确认所有安装提示。</p>
<p>接下来，为文件系统MCP Server配置一个MCP Client：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>与之前的配置类似，我们通过命令行参数启动文件系统MCP服务器。该配置允许机器人在指定目录执行文件创建、读写操作（默认当前目录，可通过参数扩展多目录）。Spring AI启动时会自动扫描配置，创建MCP客户端连接服务器，并生成包含所有可用工具的<code>SyncMcpToolCallbackProvider</code> Bean。</p>
<h3> 构建简单的聊天机器人</h3>
<p>配置好人工智能模型和MCP Client后，让我们构建一个简单的聊天机器人：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们首先使用<code>ChatModel</code>和<code>SyncMcpToolCallbackProvider</code>创建一个<code>ChatClient</code>，它将作为我们与模型交互的主要入口点。</p>
<p>接下来，创建一个新的<code>ChatbotService</code>类，<code>chat()</code>方法将用户的问题传递给聊天客户端bean，并简单返回人工智能模型的响应。：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再创建一个Controller，暴露一个REST API来实现聊天的交互：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 构建 MCP Server</h2>
<p>除了使用预构建的MCP服务器外，我们还可以创建自己的MCP服务器，用我们的业务逻辑扩展聊天机器人的功能。下面我们创建一个新的Spring Boot应用程序来尝试构建一个简单的MCP Server</p>
<h3> 引入相关依赖</h3>
<p>首先，在pom.xml文件中包含必要的依赖项：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 定义并暴露自定义工具</h3>
<p>接下来，定义一些我们的MCP服务器将暴露的自定义工具。</p>
<p>我们创建一个<code>AuthorRepository</code>类，该类提供获取作者详细信息的方法：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里使用<code>@Tool</code>注解对两个方法进行注解，并为每个方法提供简要描述。该描述有助于人工智能模型根据用户输入决定是否调用以及何时调用这些工具，并将结果纳入其响应中。</p>
<blockquote>
<p>为了演示，这里直接硬编码返回信息，在实际应用程序中，这些工具通常会与数据库或外部API进行交互。</p>
</blockquote>
<p>接下来，向MCP Server注册我们的作者工具：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们使用<code>MethodToolCallbackProvider从AuthorRepository</code>类中定义的工具创建一个<code>ToolCallbackProvider</code>。应用程序启动时，用<code>@Tool</code>注解的方法将作为MCP工具暴露出来。</p>
<h3> 构建 MCP Client</h3>
<p>最后，为了在聊天机器人应用程序中使用我们的自定义MCP服务器，我们需要针对它配置一个MCP客户端：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在<code>application.yaml</code>文件中，我们针对自定义MCP服务器配置了一个新客户端。请注意，这里我们使用的是sse传输类型。</p>
<p>此配置假设MCP服务器在<code>http://localhost:8081</code>上运行，如果它在不同的主机或端口上运行，请确保更新url。</p>
<p>通过此配置，我们的MCP客户端现在除了可以调用Brave Search和文件系统MCP服务器提供的工具外，还可以调用我们的自定义服务器暴露的工具。</p>
<h2> 测试聊天效果</h2>
<p>现在我们已经构建了聊天机器人并将其与各种MCP Server集成，让我们与它进行交互并进行测试。</p>
<p>我们将使用HTTPie CLI调用聊天机器人的API端点：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>在这里，我们向聊天机器人发送一个关于大语言模型知识截止日期之后发生的事件的简单问题。让我们看看得到的响应是什么：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>正如我们所见，聊天机器人能够使用配置的Brave Search MCP服务器执行网络搜索，并提供准确的答案以及来源。</p>
<p>接下来，让我们验证聊天机器人是否可以使用文件系统MCP服务器执行文件系统操作：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>我们指示聊天机器人创建一个名为<code>mcp-demo.txt</code>的文件，并包含特定内容。让我们看看它是否能够完成请求：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>聊天机器人给出了成功的响应。我们可以验证文件是否在我们在<code>application.yaml</code>文件中指定的目录中创建。</p>
<p>最后，让我们验证聊天机器人是否可以调用我们的自定义MCP服务器暴露的工具之一。我们将通过提及文章标题来询问作者详细信息：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>让我们调用API，看看聊天机器人的响应是否包含硬编码的作者详细信息：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上述响应验证了聊天机器人使用我们的自定义MCP服务器暴露的<code>getAuthorByArticleTitle()</code>工具获取了作者详细信息。</p>
<h2> 结论</h2>
<p>在本文中，我们探索了模型上下文协议，并使用Spring AI实现了MCP的Client-Server架构，就从Client-Server这一侧其实也挺简单的吧，核心难的其实还是能力的提供方，也就是Server段能调用到多少能力，往往一些专业的能力是由专业软件提供的，这就需要对方有API的支持，然后通过MCP协议来调用。</p>
<p>最后，灵魂一问，你目前有在AI应用中使用MCP Server吗？把你觉得好用的MCP Server在评论推荐一波吧！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/bd867885949a05434f7132ff3ddca178.png" type="image/png"/>
    </item>
    <item>
      <title>Spring AI + Ollama 实现 deepseek-r1 的API服务和调用</title>
      <link>https://spring.didispace.com/article/spring-ai-ollama-deepseek.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-ollama-deepseek.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring AI + Ollama 实现 deepseek-r1 的API服务和调用</source>
      <description>Spring AI + Ollama 实现 deepseek-r1 的API服务和调用 最近DeepSeek开源了对openai-o1的第一代开源推理大模型：deepseek-r1，因其极低的成本和与openai-o1相当的性能引发了国内外的激烈讨论。DD在做独立产品的时候也一直都有用DeepSeek的API来实现一些功能，比如：TransDuck中的字幕翻译、视频翻译，效果也是非常不错的。但是，最近因为收到一些私有化的需求，所以对于API的调用就不可行了，不得不转向本地部署大模型，然后提供API的方式来实现。本文就针对这样的情况，尝试了一下使用 Ollama 在本地运行 DeepSeek-R1 并提供 API 服务，然用再使用Spring Boot + Spring AI 实现对 DeepSeek-R1 的调用，有类似需求或者感兴趣的小伙伴也可以根据下面的内容来实践。</description>
      <category>Spring AI</category>
      <pubDate>Mon, 27 Jan 2025 15:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring AI + Ollama 实现 deepseek-r1 的API服务和调用</h1>
<p>最近DeepSeek开源了对<code>openai-o1</code>的第一代开源推理大模型：<strong>deepseek-r1</strong>，因其极低的成本和与<code>openai-o1</code>相当的性能引发了国内外的激烈讨论。DD在做独立产品的时候也一直都有用DeepSeek的API来实现一些功能，比如：<a href="https://transduck.com/" target="_blank" rel="noopener noreferrer">TransDuck</a>中的字幕翻译、视频翻译，效果也是非常不错的。但是，最近因为收到一些私有化的需求，所以对于API的调用就不可行了，不得不转向本地部署大模型，然后提供API的方式来实现。本文就针对这样的情况，尝试了一下使用 Ollama 在本地运行 DeepSeek-R1 并提供 API 服务，然用再使用Spring Boot + Spring AI 实现对 DeepSeek-R1 的调用，有类似需求或者感兴趣的小伙伴也可以根据下面的内容来实践。</p>
<h2> 使用 Ollama 运行 deepseek-r1</h2>
<p>通过 Ollama 来运行 <strong>deepseek-r1</strong> 非常简单，在Linux服务器上的话，只需要两步：</p>
<ol>
<li>安装 Ollama</li>
</ol>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>如果本地MacOS或者Windows开发环境使用的话，也可以从<a href="https://ollama.com/download/mac" target="_blank" rel="noopener noreferrer">前往官网</a>下载客户端版本：</p>
<p><img src="https://static.didispace.com/images3/a64da2ab69d130ffefadf28ce66b88b0.png" alt=""></p>
<ol start="2">
<li>运行 deepseek-r1</li>
</ol>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>如果你的环境没有足够的资源运行<code>671b</code>模型，那么也可以根据你的算力资源情况选择其他几个小参数版本，命令如下：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>更多关于信息可查看：https://ollama.com/library/deepseek-r1</p>
<h2> 使用Spring Boot + Spring AI</h2>
<p>在使用Ollama把deepseek-r1跑起来之后，我们就可以开始使用Spring Boot + Spring AI来调用了。</p>
<ol>
<li>使用 <code>https://start.spring.io/</code> 构建一个Spring Boot项目。点击<code>ADD DEPENDENCIES</code>，搜索<code>Ollama</code>添加依赖，这是Spring AI对Ollama的实现支持。</li>
</ol>
<p><img src="https://static.didispace.com/images3/ceffc426b5e5dd9a5f3f1dab432523c4.png" alt=""></p>
<ol start="2">
<li>打开生成的项目，查看<code>pom.xml</code>，可以看到核心依赖：</li>
</ol>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果你要在现有项目中集成的话，就可以直接添加这个依赖即可。</p>
<ol start="3">
<li>配置Ollama的相关信息：</li>
</ol>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>spring.ai.ollama.base-url</code>: Ollama的API服务地址，如果部署在非本机，就需要做对应的修改</li>
<li><code>spring.ai.ollama.chat.model</code>: 要调用的模型名称，对应上一节<code>ollama run</code>命令运行的模型名称</li>
</ul>
<ol start="4">
<li>写个单元测试，尝试调用Ollama中的deepseek-r1模型，这里尝试实现一个翻译的功能。</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>运行单元测试，结果如下：</li>
</ol>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到结果响应分成两部分，先是<code>&lt;think&gt;</code>标签包含的内容，这是模型根据提供的提示，生成了一个思考的过程，最后才输出了翻译后的结果。</p>
<p>最后，如果需要本文案例工程的话，可以关注公众号“程序猿DD”，发送关键词：<code>spring-ai-ollama-deepseek-r1</code>获取。</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/a64da2ab69d130ffefadf28ce66b88b0.png" type="image/png"/>
    </item>
    <item>
      <title>用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？</title>
      <link>https://spring.didispace.com/article/how-fast-spring-boot-3-2-virtual-thread.html</link>
      <guid>https://spring.didispace.com/article/how-fast-spring-boot-3-2-virtual-thread.html</guid>
      <source url="https://spring.didispace.com/rss.xml">用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？</source>
      <description>用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？ Spring Boot 3.2 于 2023 年 11 月大张旗鼓地发布，标志着 Java 开发领域的一个关键时刻。这一突破性的版本引入了一系列革命性的功能，包括： 虚拟线程：利用 Project Loom 的虚拟线程释放可扩展性，从而减少资源消耗并增强并发性。 Native Image支持：通过Native Image编译制作速度极快的应用程序，减少启动时间并优化资源利用率。 JVM 检查点：利用 CRaC 项目的 JVM 检查点机制实现应用程序的快速重启，无需冗长的重新初始化。 RestClient：采用新的 RestClient 接口的功能方法，简化 HTTP 交互并简化代码。 Spring for Apache Pulsar：利用 Apache Pulsar 的强大功能实现强大的消息传递功能，无缝集成到您的 Spring Boot 应用程序中。</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 05 Jan 2024 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？</h1>
<p>Spring Boot 3.2 于 2023 年 11 月大张旗鼓地发布，标志着 Java 开发领域的一个关键时刻。这一突破性的版本引入了一系列革命性的功能，包括：</p>
<ul>
<li>虚拟线程：利用 Project Loom 的虚拟线程释放可扩展性，从而减少资源消耗并增强并发性。</li>
<li>Native Image支持：通过Native Image编译制作速度极快的应用程序，减少启动时间并优化资源利用率。</li>
<li>JVM 检查点：利用 CRaC 项目的 JVM 检查点机制实现应用程序的快速重启，无需冗长的重新初始化。</li>
<li>RestClient：采用新的 RestClient 接口的功能方法，简化 HTTP 交互并简化代码。</li>
<li>Spring for Apache Pulsar：利用 Apache Pulsar 的强大功能实现强大的消息传递功能，无缝集成到您的 Spring Boot 应用程序中。</li>
</ul>
<p>其中，虚拟线程是最近 Java 版本中引入的最具变革性的特性之一。正如官方文件所述：虚拟线程是轻量级线程，可减少编写、维护和调试高吞吐量并发应用程序的工作量。线程是可以调度的最小处理单元。它与其他此类单位同时运行，并且在很大程度上独立于其他此类单元运行。它是 java.lang.Thread 的一个实例。有两种线程：平台线程和虚拟线程。平台线程是作为操作系统 （OS） 线程的瘦包装器实现的。平台线程在其底层操作系统线程上运行 Java 代码，平台线程在平台线程的整个生命周期内捕获其操作系统线程。因此，可用平台线程数限制为操作系统线程数。与平台线程一样，虚拟线程也是 java.lang.Thread 的实例。但是，虚拟线程不绑定到特定的操作系统线程。虚拟线程仍在操作系统线程上运行代码。但是，当在虚拟线程中运行的代码调用阻塞 I/O 操作时，Java 运行时会挂起虚拟线程，直到它可以恢复为止。与挂起的虚拟线程关联的操作系统线程现在可以自由地对其他虚拟线程执行操作。虚拟线程适用于运行大部分时间被阻塞的任务，通常等待 I/O 操作完成。但是，它们不适用于长时间运行的 CPU 密集型操作。</p>
<p>虽然人们普遍认为虚拟线程在 I/O 密集型方案中表现出色，但它们在 CPU 密集型任务中的性能仍然是一个问号。本系列文章深入探讨了虚拟线程在各种用例中的潜在优势，从基本的“hello world”到静态文件服务（I/O 密集型）、QR 码生成（CPU 密集型）和多部分/表单数据处理（混合工作负载）等实际应用。</p>
<p>在本系列的开头文章中，我们已经了解了虚拟线程与物理线程相比在最简单（且不切实际）的 hello world 情况下的性能。物理线程和虚拟线程之间几乎没有任何性能或资源使用差异。在本文中，我们将更加“实用”，并针对静态文件服务器情况进行比较。这绝对是一个常见且“真实世界”的案例。让我们看看这次我们发现了什么。</p>
<h2> 测试环境</h2>
<p>所有测试均在配备 16G RAM、8 个物理内核和 4 个效率内核的 MacBook Pro M2 上执行。测试工具是 Bombardier，它是更快的 HTTP 负载测试器之一（用 Go 编写）。</p>
<p>软件版本为：</p>
<ul>
<li>Java v21.0.1</li>
<li>Spring Boot 3.2.1</li>
</ul>
<h3> 程序配置</h3>
<p>除了主 Java 类之外，不需要编写任何 Java 文件，静态文件服务器只能通过配置就能发挥作用。</p>
<p><code>application.properties</code>文件如下：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>使用虚拟线程时，我们将通过添加以下行来启用它们：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><code>pom.xml</code>内容：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 测试数据</h3>
<p>大小完全相同但数据不同的 100K 文件被放置在静态资源目录中。每个文件大小正好是 102400 字节。</p>
<p>文件的命名范围为 1 到 100000。</p>
<p>使用 Bombardier 的修改版本，为每次运行生成随机请求 URL: <code>http://localhost:3000/static/&lt;file-name&gt;</code></p>
<h3> 应用场景</h3>
<p>为了确保结果一致，每个测试在开始数据收集之前都会经历 5K 请求预热阶段。</p>
<p>然后，在不同范围的并发连接级别（50、100 和 300）中仔细记录测量结果，每个级别都承受 500 万个请求工作负载。</p>
<h3> 结果评估</h3>
<p>除了简单地跟踪原始速度之外，我们还将采用详细的指标框架来捕获延迟分布（最小值、百分位数、最大值）和吞吐量（每秒请求数）。</p>
<p>CPU 和内存的资源使用情况监控将补充此分析，从而提供不同工作负载下系统性能的全面了解。</p>
<h2> 测试结果</h2>
<p>结果以图表形式呈现如下：</p>
<p><img src="https://static.didispace.com/images3/032f55aa3190738afdd3d471fe94f462.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/f20ce190ee51e19be8f9688e401df7e0.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/0cbac43928d340b4cdd2b1840d303c23.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/56f5bf628923568bbeb0d7eabb75171d.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/ec8e368658be22e17e0009d37e0e54e3.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/9741278d2cdb9547bcfe924bbe16c00f.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/058e66dfc540aa9266bc4d17d18f456c.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/1d44d3c0600755e5516fe61e4f1a586c.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/56408adbe62ff4ff2dca17bf7ec39233.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/0612011771670b1d839df2bfc7aa3c52.png" alt=""></p>
<h2> 总结</h2>
<p>对静态文件服务的分析表明，物理线程在性能和资源效率方面略胜一筹（与我们的预期相反）。</p>
<p>不过，这种受 I/O 限制的场景可能并不是充分发挥虚拟线程潜力的理想场所。涉及数据库交互的任务可能会显示出更多令人信服的优势。也许负载不足以让虚拟线程发挥出最大的作用。为了找出答案，我们将在接下来的文章中介绍 URL短链（数据库驱动）、二维码生成（CPU受限）和混合工作负载场景（如表单数据处理），旨在揭示虚拟线程真正出类拔萃的案例。</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/032f55aa3190738afdd3d471fe94f462.png" type="image/png"/>
    </item>
    <item>
      <title>Java微服务框架选择：Micronaut vs Spring Boot</title>
      <link>https://spring.didispace.com/article/micronaut-vs-spring-boot.html</link>
      <guid>https://spring.didispace.com/article/micronaut-vs-spring-boot.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Java微服务框架选择：Micronaut vs Spring Boot</source>
      <description>Java微服务框架选择：Micronaut vs Spring Boot 深入研究微服务世界的 Java 开发人员经常发现自己面临着在两个著名框架之间做出选择：Micronaut 和 Spring Boot。 这两个框架都为构建微服务提供了强大的解决方案，但每个框架都有自己的优点和缺点。 在本文中，我们将探讨与 Spring Boot 相比使用 Micronaut 的一些潜在缺点，强调根据项目的特定需求调整您的选择的重要性。 生态系统的成熟度与规模 Spring Boot 在生态系统的成熟度与规模方面远远领先于 Micronaut。</description>
      <category>Java</category>
      <pubDate>Thu, 28 Dec 2023 11:10:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Java微服务框架选择：Micronaut vs Spring Boot</h1>
<p>深入研究微服务世界的 Java 开发人员经常发现自己面临着在两个著名框架之间做出选择：Micronaut 和 Spring Boot。</p>
<p>这两个框架都为构建微服务提供了强大的解决方案，但每个框架都有自己的优点和缺点。</p>
<p>在本文中，我们将探讨与 Spring Boot 相比使用 Micronaut 的一些潜在缺点，强调根据项目的特定需求调整您的选择的重要性。</p>
<h2> 生态系统的成熟度与规模</h2>
<p>Spring Boot 在生态系统的成熟度与规模方面远远领先于 Micronaut。</p>
<p>Spring Boot 拥有完善的社区、丰富的库和广泛的工具支持。</p>
<p>另一方面，Micronaut 虽然受到关注，但可能无法提供相同水平的成熟度，并且可能缺乏 Spring Boot 生态系统中可用的第三方集成的广度。</p>
<h2> 学习曲线</h2>
<p>Micronaut 引入了编译时依赖注入等创新概念，这可以为习惯更传统 Spring Boot 方法的开发人员提供更陡峭的学习曲线。</p>
<p>Spring Boot 庞大的社区和全面的文档有助于提供更易于访问的学习路径，使开发人员更容易找到资源和支持。</p>
<h2> Annotation处理</h2>
<p>Micronaut 在编译时使用Annotation处理可以提高性能，但可能会导致构建时间更长。</p>
<p>相比之下，Spring Boot 依赖运行时反射来进行依赖注入，这可能会导致更快的构建时间，但会牺牲一些运行时性能。</p>
<p>在评估这些框架时，构建时间和运行时效率之间的权衡是一个至关重要的考虑因素。</p>
<h2> 社区规模与成熟度</h2>
<p>Spring Boot 拥有规模更大、更成熟的社区，为开发人员提供了丰富的资源、教程和第三方插件。</p>
<p>虽然 Micronaut 社区不断发展，但它可能无法提供同等水平的资源和支持，这可能会给寻求帮助或寻找最佳实践的开发人员带来挑战。</p>
<h2> 集成测试</h2>
<p>Micronaut 的编译时依赖注入可能会在编写集成测试时带来挑战，而 Spring Boot 的运行时依赖注入使某些类型的测试更加简单。</p>
<p>尽管 Micronaut 提供了测试框架，但与 Spring Boot 中熟悉的方法相比，开发人员可能需要调整其测试策略。</p>
<h2> 配套工具支持</h2>
<p>Spring Boot 受益于广泛的工具和全面的 IDE 支持，有助于实现无缝的开发体验。</p>
<p>Micronaut 虽然正在积极开发，但可能无法提供与各种开发工具相同级别的集成，这可能会给开发人员带来不便。</p>
<h2> 总结</h2>
<p>总之，在 Micronaut 和 Spring Boot 之间进行选择应该以对项目需求、团队的学习偏好以及每个框架生态系统的当前状态进行全面评估为指导。</p>
<p>选择正确的框架是一项战略决策，可以显着影响微服务架构的成功。</p>
<blockquote>
<p>本文翻译自：https://medium.com/@bubu.tripathy/micronaut-vs-spring-boot-c700bf721141</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Spring AI：在你的Spring应用中使用生成式AI</title>
      <link>https://spring.didispace.com/article/new-spring-ai.html</link>
      <guid>https://spring.didispace.com/article/new-spring-ai.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring AI：在你的Spring应用中使用生成式AI</source>
      <description>Spring AI：在你的Spring应用中使用生成式AI 过去一年里，ChatGPT 和 Google Bard 这样的东西出现，为大众带来了生成式人工智能，似乎每个人都在梦想和计划如何在他们的项目甚至日常生活中利用人工智能。 如果您是 Spring 开发人员，您可能想知道如何在 Spring 应用程序中实现生成式 AI。如果是这样，那么接下来这个视频一定适合您。</description>
      <category>Spring AI</category>
      <pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring AI：在你的Spring应用中使用生成式AI</h1>
<p>过去一年里，ChatGPT 和 Google Bard 这样的东西出现，为大众带来了生成式人工智能，似乎每个人都在梦想和计划如何在他们的项目甚至日常生活中利用人工智能。</p>
<p>如果您是 Spring 开发人员，您可能想知道如何在 Spring 应用程序中实现生成式 AI。如果是这样，那么接下来这个视频一定适合您。</p>
]]></content:encoded>
    </item>
    <item>
      <title>如何用 Spring AI + Ollama 构建生成式 AI 应用</title>
      <link>https://spring.didispace.com/article/spring-ai-ollama-ai-app.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-ollama-ai-app.html</guid>
      <source url="https://spring.didispace.com/rss.xml">如何用 Spring AI + Ollama 构建生成式 AI 应用</source>
      <description>如何用 Spring AI + Ollama 构建生成式 AI 应用 为了构建生成式AI应用，需要完成两个部分： AI大模型服务：有两种方式实现，可以使用大厂的API，也可以自己部署，本文将采用ollama来构建 应用构建：调用AI大模型的能力实现业务逻辑，本文将采用Spring Boot + Spring AI来实现 Ollama安装与使用</description>
      <category>Spring AI</category>
      <pubDate>Wed, 23 Oct 2024 09:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 如何用 Spring AI + Ollama 构建生成式 AI 应用</h1>
<p>为了构建生成式AI应用，需要完成两个部分：</p>
<ul>
<li>AI大模型服务：有两种方式实现，可以使用大厂的API，也可以自己部署，本文将采用ollama来构建</li>
<li>应用构建：调用AI大模型的能力实现业务逻辑，本文将采用Spring Boot + Spring AI来实现</li>
</ul>
<h2> Ollama安装与使用</h2>
<p><img src="https://static.didispace.com/images3/d888726bdabc2f682d2c0019e54b2289.png" alt=""></p>
<p>进入官网：https://ollama.com/ ，下载、安装、启动 ollama</p>
<p>具体步骤可以参考我之前的这篇文章：<a href="/article/how_to_use_llama3_1.html" target="blank">手把手教你本地运行Meta最新大模型：Llama3.1</a></p>
<h2> 构建 Spring 应用</h2>
<ol>
<li>
<p>通过<a href="https://start.spring.io/" target="_blank" rel="noopener noreferrer">spring initializr</a>创建Spring Boot应用</p>
</li>
<li>
<p>注意右侧选择Spring Web和Spring AI对Ollama的支持依赖</p>
</li>
</ol>
<p><img src="https://static.didispace.com/images3/72e62dbb3e952d2fe56299d2cf441cf2.png" alt=""></p>
<ol start="3">
<li>
<p>点击“generate”按钮获取工程</p>
</li>
<li>
<p>使用IDEA或者任何你喜欢的工具打开该工程，工程结构如下；</p>
</li>
</ol>
<p><img src="https://static.didispace.com/images3/09bb98a0568febe3851e50ea14dee103.png" alt=""></p>
<ol start="5">
<li>写个单元测试，尝试在Spring Boot应用里调用本地的ollama服务</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>运行得到如下输出：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>上述样例工程打包放公众号了，如果需要的话，关注"程序猿DD"，发送关键词<code>spring+ollama</code>获得下载链接。</p>
</blockquote>
<h2> 小结</h2>
<p>通过本文的介绍，我们就已经完成了Spring Boot应用与Ollama运行的AI模型之间的对接。剩下的就是与业务逻辑的结合实现，这里读者根据自己的需要去实现即可。</p>
<p><strong>可能存在的一些疑问</strong></p>
<ol>
<li>如何使用其他AI模型</li>
</ol>
<p>通过ollama的 <a href="https://ollama.com/library" target="_blank" rel="noopener noreferrer">Models</a> 页面，可以找到各种其他模型：</p>
<p><img src="https://static.didispace.com/images3/d36e6e37d302d7de3578d143f24af8d9.png" alt=""></p>
<p>选择你要使用的模型来启动即可。</p>
<ol start="2">
<li>如何植入现有应用？</li>
</ol>
<p>打开上面工程的<code>pom.xml</code>，可以看到主要就下面两个依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>所以，如果要在现有工程引入的话只要引入<code>spring-ai-ollama-spring-boot-starter</code>依赖就可以了。</p>
<p>好了，今天的分享就到这里。最近较忙，分享较少，感谢持续的关注与支持 ^_^</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/d888726bdabc2f682d2c0019e54b2289.png" type="image/png"/>
    </item>
    <item>
      <title>Spring AI更新：支持OpenAI的结构化输出，增强JSON响应可靠性</title>
      <link>https://spring.didispace.com/article/spring-ai-openai-json.html</link>
      <guid>https://spring.didispace.com/article/spring-ai-openai-json.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring AI更新：支持OpenAI的结构化输出，增强JSON响应可靠性</source>
      <description>Spring AI更新：支持OpenAI的结构化输出，增强JSON响应可靠性 就在昨晚，Spring AI发了个比较重要的更新。由于最近OpenAI推出了结构化输出的功能，可确保 AI 生成的响应严格遵守预定义的 JSON 模式。此功能显着提高了人工智能生成内容在现实应用中的可靠性和可用性。Spring AI 紧随其后，现在也可以对OpenAI的结构化输出完美支持了。 下图展示了本次扩展的实现结构，如果对于当前实现还不够满意，需要扩展的可以根据此图来着手理解分析进行下一步扩展工作。</description>
      <category>Spring AI</category>
      <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring AI更新：支持OpenAI的结构化输出，增强JSON响应可靠性</h1>
<p>就在昨晚，Spring AI发了个比较重要的更新。由于最近OpenAI推出了结构化输出的功能，可确保 AI 生成的响应严格遵守预定义的 JSON 模式。此功能显着提高了人工智能生成内容在现实应用中的可靠性和可用性。Spring AI 紧随其后，现在也可以对OpenAI的结构化输出完美支持了。</p>
<p>下图展示了本次扩展的实现结构，如果对于当前实现还不够满意，需要扩展的可以根据此图来着手理解分析进行下一步扩展工作。</p>
<p><img src="https://static.didispace.com/images3/ecaed1d8010ff5daf7f5bdd5df27502f.png" alt=""></p>
<h2> 使用样例</h2>
<p>通过Spring AI，开发者可以很方便的来构建针对 OpenAI 结构化输出的请求和解析：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过 <code>OpenAiChatOptions</code>中指定<code>ResponseFormat</code>来让OpenAI返回JSON格式。</p>
<p>Spring AI还提供了<code>BeanOutputConverter</code>来实现将JSON出转换成Java Bean，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果你整合了Spring AI针对OpenAI的Spring Boot Starter模块，那么也可以通过下面的方式来自动配置默认的JSON返回格式：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>今天的分享就到这里，感谢阅读！码字不易，点赞、关注、收藏支持一下！随便转载，标注下出处链接即可。</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/ecaed1d8010ff5daf7f5bdd5df27502f.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 3.0.0 M3、2.7.0发布，2.5.x将停止维护</title>
      <link>https://spring.didispace.com/article/spring-boot-2.7-release.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-2.7-release.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 3.0.0 M3、2.7.0发布，2.5.x将停止维护</source>
      <description>Spring Boot 3.0.0 M3、2.7.0发布，2.5.x将停止维护 昨晚（5月19日），Spring Boot官方发布了一系列Spring Boot的版本更新，其中包括： Spring Boot 3.0.0-M3 Spring Boot 2.7.0 Spring Boot 2.6.8 Spring Boot 2.5.14 Spring Boot 3.0.0-M3 此版本包括74个bug修复、文档改进和依赖项升级，其中值得注意的新功能包括：</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 20 May 2022 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 3.0.0 M3、2.7.0发布，2.5.x将停止维护</h1>
<p>昨晚（5月19日），Spring Boot官方发布了一系列Spring Boot的版本更新，其中包括：</p>
<ul>
<li>Spring Boot 3.0.0-M3</li>
<li>Spring Boot 2.7.0</li>
<li>Spring Boot 2.6.8</li>
<li>Spring Boot 2.5.14</li>
</ul>
<h2> Spring Boot 3.0.0-M3</h2>
<p>此版本包括74个bug修复、文档改进和依赖项升级，其中值得注意的新功能包括：</p>
<ul>
<li>Micrometer Observation、Tracing和OtlpMeterRegistry的自动化配置支持</li>
<li>恢复对REST Assured和Pooled JMS的支持</li>
</ul>
<h2> Spring Boot 2.7.0</h2>
<p>2.7.0是2.x版本的一个重要的新特性版本，在该版本中有这些亮点可以关注一下：</p>
<ul>
<li>增加了Spring GraphQL的自动化配置和Metrics</li>
<li>增加<code>@DataCoubaseTest</code>和<code>@DataElasticsearchTest</code>支持</li>
<li>可以在使用Cloud Native Buildpacks构建镜像时使用Podman</li>
<li>支持Cache2k</li>
<li>简化Jackson Mixins的注册</li>
<li>Web服务器的SSL配置使用PEM编码证书</li>
</ul>
<p>此外，在该版本中更新了Spring家族中的依赖项目，包括：</p>
<ul>
<li>Spring Data 2021.2</li>
<li>Spring HATEOAS 1.5</li>
<li>Spring LDAP 2.4</li>
<li>Spring Security 5.7</li>
<li>Spring Session 2021.2</li>
</ul>
<p>更多该版本的更新内容可查看官方release notes：https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes</p>
<h2> Spring Boot 2.5.14、2.6.8</h2>
<p>2.5.14和2.6.8分别是2.5和2.6版本的常规问题修复版本，没有什么新鲜内容，所以这里就不具体介绍了，如果感兴趣的话，可以看看官方文档。</p>
<p>值得注意的是，2.5.14将是2.5版本的最后一个版本，后面不会再继续更新，如果您仍在使用此版本，请考虑尽早升级。</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 3.2发布：大量Java 21的支持上线，改进可观测性</title>
      <link>https://spring.didispace.com/article/spring-boot-3-2.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-3-2.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 3.2发布：大量Java 21的支持上线，改进可观测性</source>
      <description>Spring Boot 3.2发布：大量Java 21的支持上线，改进可观测性 就在今天凌晨，Spring Boot 3.2正式发布了！该版本是在Java 21正式发布之后的重要支持版本，所以在该版本中包含大量对Java 21支持的优化。 下面，我们分别通过Spring官方发布的博文和Josh Long长达80+分钟的介绍视频，一起认识一下Spring Boot 3.2最新版本所带来的全新内容。 官方博文：https://spring.io/blog/2023/11/23/spring-boot-3-2-0-available-now Josh Long的视频：https://www.youtube.com/watch?v=dMhpDdR6nHw</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 24 Nov 2023 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 3.2发布：大量Java 21的支持上线，改进可观测性</h1>
<p>就在今天凌晨，Spring Boot 3.2正式发布了！该版本是在Java 21正式发布之后的重要支持版本，所以在该版本中包含大量对Java 21支持的优化。</p>
<p>下面，我们分别通过Spring官方发布的博文和Josh Long长达80+分钟的介绍视频，一起认识一下Spring Boot 3.2最新版本所带来的全新内容。</p>
<ul>
<li>官方博文：https://spring.io/blog/2023/11/23/spring-boot-3-2-0-available-now</li>
<li>Josh Long的视频：https://www.youtube.com/watch?v=dMhpDdR6nHw</li>
</ul>
<h2> 最新特性</h2>
<p>通过官方博文的介绍，可以有个大致的最新特性了解，其中包括：</p>
<ul>
<li>支持<a href="https://www.didispace.com/java-features/java21/jep444-virtual-threads.html" target="_blank" rel="noopener noreferrer">虚拟线程</a></li>
<li>对JVM Checkpoint Restore的初步支持（CRaC项目）</li>
<li>SSL 捆绑包重新加载</li>
<li>大量可观察性改进</li>
<li>支持 <a href="https://www.didispace.com/article/oblog/spring-6-1-restclient.html" target="_blank" rel="noopener noreferrer">RestClient</a></li>
<li>支持 JdbcClient</li>
<li>支持 Jetty 12</li>
<li>Apache Pulsar 对 Spring 的支持</li>
<li>对 Kafka 和 RabbitMQ 的 SSL 捆绑支持</li>
<li>重新设计的嵌套 Jar 处理</li>
<li>Docker 镜像构建改进</li>
</ul>
<p>此外，对于依赖的更新列表，因为比较长，这里DD就不列出来了，感兴趣的可以<a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2.0-Release-Notes#dependency-upgrades" target="_blank" rel="noopener noreferrer">点击这里查看</a></p>
<h2> 视频介绍</h2>
<p>下面，可以把关注点放到John Long的介绍视频里。一如既往的干货视频！通过在线编码的方式，直观的给大家展示新功能的魅力。</p>
<p><img src="https://static.didispace.com/images3/17cdb64600918dbd309052e8b01fa092.png" alt=""></p>
<blockquote>
<p>Tips：Josh Long的视频如果您不方便访问的话，DD这边给大家下载好了，同时也做了一份翻译字幕和中文配音视频。有需要观看的视频的小伙伴可以通过关注公众号：程序猿DD，发送关键词：<strong>springboot</strong>，获取下载链接。</p>
</blockquote>
<p>下面是关于视频的总结，可以先大概看一下视频内容，感兴趣的话可以下载观看：</p>
<p><strong>[00:16]</strong> Spring Boot 3.2带来了许多新功能，包括支持虚拟线程和Project Loom、改进的可观察性支持、可重载的SSL支持等。</p>
<ul>
<li>Spring Boot 3.2带来了许多新功能，包括虚拟线程和Project Loom。</li>
<li>Java 21是Spring Boot 3.2的一个重要特性。</li>
<li>Java 21引入了一些新的语法变化，如封闭类型、模式匹配、智能开关表达式和记录。</li>
<li>Java 21被称为数据导向编程，旨在改进Java在大型单体应用中的表现。</li>
</ul>
<p><strong>[10:13]</strong> 使用字符串格式化和多行变量非常方便，还有一些新的特性，如模式匹配和解构操作符。</p>
<ul>
<li>字符串格式化和多行变量是方便的选项。</li>
<li>新特性包括记录、密封类型、智能开关表达式和模式匹配。</li>
<li>目前还没有解构操作符，但正在开发中。</li>
<li>项目Loom虚拟线程是Java 21中的重要特性。</li>
</ul>
<p><strong>[20:27]</strong> 在这个片段中，演示了创建一个跳过重复项的集合，并使用线程来记录当前线程的名称和休眠100毫秒。</p>
<ul>
<li>创建了一个跳过重复项的集合。</li>
<li>使用线程记录当前线程的名称。</li>
<li>休眠100毫秒。</li>
<li>演示了使用虚拟线程来执行Java代码。</li>
</ul>
<p><strong>[30:41]</strong> 在Spring Boot 3.2中，我们将使用Java 21、Maven和一些支持库来构建一个与SQL数据库通信的应用程序。</p>
<ul>
<li>使用了test containers API来启动Docker镜像。</li>
<li>使用了spring boot starter jdbc依赖来连接PostgreSQL数据库。</li>
<li>通过添加特定的配置来启动PostgreSQL容器。</li>
</ul>
<p><strong>[40:57]</strong> 使用Spring Boot 3.0中的声明式接口可以更简化代码，提供一个给定URL请求的猫事实。</p>
<ul>
<li>可以使用新的JDBC和REST客户端来调用端点。</li>
<li>使用Project Loom和Drava 21，可以在Spring应用程序的不同层级中获得一致的虚拟线程集成。</li>
<li>这种方法既具备了阻塞API的便利性，又不会丧失可用性。</li>
<li>可以使用声明式接口来实现给定URL请求的猫事实。</li>
</ul>
<p><strong>[51:09]</strong> Spring框架中有一个称为SmartLifeCycle的接口，可以用来表示典型Spring生命周期的方法。</p>
<ul>
<li>SmartLifeCycle是一个更智能的版本，用于让用户消费。</li>
<li>SmartLifeCycle的方法与检查点方法对应。</li>
<li>可以使用SmartLifeCycle来管理应用程序的启动和停止。</li>
<li>在这个例子中，通过实现SmartLifeCycle接口，可以实现应用程序的启动和停止功能。</li>
</ul>
<p><strong>[01:01:23]</strong> 通过一个文件来生成唯一的键值对，并且配置了一个自签名的SSL证书。</p>
<ul>
<li>在一个文件中写入数字，每次运行时递增并写入文件。</li>
<li>生成的键值对是唯一的，用于展示随时间变化。</li>
<li>配置了自签名的SSL证书，并通过指定端口号8443来使用。</li>
</ul>
<p><strong>[01:11:38]</strong> 在本地机器上，我们可以通过Zipkin来查看分布式追踪的图形</p>
<ul>
<li>使用Docker compose启动Zipkin实例</li>
<li>在应用代码中增加采样概率</li>
<li>通过aop支持在类路径上添加注解来显示跟踪ID和跨度ID</li>
<li>通过Zipkin可以查看请求日志和服务之间的跳转</li>
</ul>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/17cdb64600918dbd309052e8b01fa092.png" type="image/png"/>
    </item>
    <item>
      <title>如何创建自己的Spring Boot Starter并为其编写单元测试</title>
      <link>https://spring.didispace.com/article/spring-boot-custom-starter.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-custom-starter.html</guid>
      <source url="https://spring.didispace.com/rss.xml">如何创建自己的Spring Boot Starter并为其编写单元测试</source>
      <description>如何创建自己的Spring Boot Starter并为其编写单元测试 当我们想要封装一些自定义功能给别人使用的时候，创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话，本文将带你一起创建一个自己的Spring Boot Starter。 快速入门 创建一个新的 Maven 项目。第三方封装的命名格式是 xxx-spring-boot-starter ，例如：didispace-spring-boot-starter。 编辑pom.xml，添加spring-boot-autoconfigure和spring-boot-starter依赖</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 27 Feb 2024 11:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 如何创建自己的Spring Boot Starter并为其编写单元测试</h1>
<p>当我们想要封装一些自定义功能给别人使用的时候，创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话，本文将带你一起创建一个自己的Spring Boot Starter。</p>
<h2> 快速入门</h2>
<ol>
<li>
<p>创建一个新的 Maven 项目。第三方封装的命名格式是 <code>xxx-spring-boot-starter</code> ，例如：<code>didispace-spring-boot-starter</code>。</p>
</li>
<li>
<p>编辑<code>pom.xml</code>，添加<code>spring-boot-autoconfigure</code>和<code>spring-boot-starter</code>依赖</p>
</li>
</ol>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="3">
<li>创建一个用 <code>@Configuration</code> 注释的配置类，在这里您可以使用<code>@Bean</code>来创建使用<code>@ConditionalOnClass</code>、<code>@ConditionalOnMissingBean</code>等条件注释来控制何时应用配置。</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>在<code>src/main/resources/META-INF</code>目录下创建<code>spring.factories</code>文件，并在<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration</code>关键字下列出您的自动配置类，比如：</li>
</ol>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>该配置的作用是让Spring Boot应用在引入您自定义Starter的时候可以自动这里的配置类。</p>
<blockquote>
<p>注意：Spring Boot 2.7开始，不再推荐使用<code>spring.factories</code>，而是改用<code>/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</code>，文件内容直接放需要自动加载配置类路径即可。这个变更具体可见之前的这篇文章：<a href="https://www.didispace.com/article/spring-boot/spring-boot-factories-deprecations.html" target="_blank" rel="noopener noreferrer">《Spring Boot 2.7开始spring.factories不推荐使用了》</a></p>
</blockquote>
<h2> 验证测试</h2>
<p>在制作Spring Boot Starter的时候，一定记得使用单元测试来验证和确保自动化配置类在任何条件逻辑在启动器下能够按照正确的预期运行。</p>
<h3> 创建单元测试</h3>
<p>使用<code>@SpringBootTest</code>加载完整的应用程序上下文，并验证启动程序是否正确配置了 Bean 和属性。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 覆盖不同的配置</h3>
<p>如果有不同的配置方案，那么还需要使用<code>@TestPropertySource</code>或<code>@DynamicPropertySource</code>覆盖属性以测试不同配置下的情况。</p>
<p>或者也可以直接简单的通过<code>@SpringBootTest</code>中的属性来配置，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 覆盖<code>@Conditional</code>的不同分支</h3>
<p>如果您的启动器包含条件配置，比如：<code>@ConditionalOnProperty</code>、<code>@ConditionalOnClass</code>等注解，那么就必须编写测试来覆盖所有条件以验证是否已正确。</p>
<p>比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>为了覆盖不同的条件分支，我们通常还需要使用<code>@TestConfiguration</code>注解来有选择地启用或禁用某些自动配置。</p>
<h2> 小结</h2>
<p>本文介绍了两个Spring Boot的进阶内容：</p>
<ol>
<li>如何创建 Spring Boot Starter</li>
<li>如何为 Spring Boot Starter 提供单元测试</li>
</ol>
<p>掌握这项技能可以帮你更好的为Spring Boot提供模块划的功能封装。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<p>最后再给大家推荐一些有关Spring Boot Starter和自动化配置的扩展阅读：</p>
<ul>
<li><a href="https://www.didispace.com/article/spring-boot/spring-factories-mica-auto.html" target="_blank" rel="noopener noreferrer">Spring Boot Starter配置spring.factories的自动生成神器</a></li>
<li><a href="https://www.didispace.com/spring-boot-1/9-5-autoconfig.html" target="_blank" rel="noopener noreferrer">Spring Boot自动化配置的利弊及解决之道</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.7开始spring.factories不推荐使用了</title>
      <link>https://spring.didispace.com/article/spring-boot-factories-deprecations.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-factories-deprecations.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.7开始spring.factories不推荐使用了</source>
      <description>Spring Boot 2.7开始spring.factories不推荐使用了 如果你是Spring Boot用户的话，一定有这样的开发体验，当我们要引入某个功能的时候，只需要在maven或gradle的配置中直接引入对应的Starter，马上就可以使用了，而不需要像传统Spring应用那样写个xml或java配置类来初始化各种Bean。 如果你有探索过这些Starter的原理，那你一定知道Spring Boot并没有消灭这些原本你要配置的Bean，而是将这些Bean做成了一些默认的配置类，同时利用/META-INF/spring.factories这个文件来指定要加载的默认配置。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 24 May 2022 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.7开始spring.factories不推荐使用了</h1>
<p>如果你是Spring Boot用户的话，一定有这样的开发体验，当我们要引入某个功能的时候，只需要在maven或gradle的配置中直接引入对应的Starter，马上就可以使用了，而不需要像传统Spring应用那样写个xml或java配置类来初始化各种Bean。</p>
<p>如果你有探索过这些Starter的原理，那你一定知道Spring Boot并没有消灭这些原本你要配置的Bean，而是将这些Bean做成了一些默认的配置类，同时利用<code>/META-INF/spring.factories</code>这个文件来指定要加载的默认配置。</p>
<p>这样当Spring Boot应用启动的时候，就会根据引入的各种Starter中的<code>/META-INF/spring.factories</code>文件所指定的配置类去加载Bean。</p>
<p>而这次刚发布的Spring Boot 2.7中，有一个不推荐使用的内容就是关于这个<code>/META-INF/spring.factories</code>文件的，所以对于有自定义Starter的开发者来说，有时间要抓紧把这一变化改起来了，因为在Spring Boot 3开始将移除对<code>/META-INF/spring.factories</code>的支持。</p>
<p><img src="https://static.didispace.com/images/202205/blogs/spring-boot-factories-deprecations/2022-05-24T003000.png" alt=""></p>
<p>那么具体怎么改呢？下面以之前我们编写的一个swagger的starter为例，它的<code>/META-INF/spring.factories</code>内容是这样的：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>我们只需要创建一个新的文件：<code>/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</code>，内容的话只需要直接放配置类就可以了，比如这样：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>注意：这里多了一级spring目录。</p>
<p>本期视频：<a href="https://www.bilibili.com/video/BV16t4y1W7fF/" target="_blank" rel="noopener noreferrer">https://www.bilibili.com/video/BV16t4y1W7fF/</a></p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/202205/blogs/spring-boot-factories-deprecations/2022-05-24T003000.png" type="image/png"/>
    </item>
    <item>
      <title>对比Spring Boot中的JdbcClient与JdbcTemplate</title>
      <link>https://spring.didispace.com/article/spring-boot-jdbcclient-jdbctemplate.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-jdbcclient-jdbctemplate.html</guid>
      <source url="https://spring.didispace.com/rss.xml">对比Spring Boot中的JdbcClient与JdbcTemplate</source>
      <description>对比Spring Boot中的JdbcClient与JdbcTemplate 本文我们一起看看Spring Boot中 JdbcClient 和 JdbcTemplate 之间的差异。 以下内容使用的Java和Spring Boot版本为： Java 21 Spring Boot 3.2.1 假设我们有一个ICustomerService接口： public interface ICustomerService { List&amp;lt;Customer&amp;gt; getAllCustomer(); Optional&amp;lt;Customer&amp;gt; getCustomerById(int id); void insert(Customer customer); void update(int id, Customer customer); void delete(int id); }</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 10 Jan 2024 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 对比Spring Boot中的JdbcClient与JdbcTemplate</h1>
<p>本文我们一起看看Spring Boot中 <code>JdbcClient</code> 和 <code>JdbcTemplate</code> 之间的差异。</p>
<p>以下内容使用的Java和Spring Boot版本为：</p>
<ul>
<li>Java 21</li>
<li>Spring Boot 3.2.1</li>
</ul>
<p>假设我们有一个<code>ICustomerService</code>接口：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中，涵盖了我们常见的数据CRUD操作。</p>
<p>下面就来一起看看，分别使用 <code>JDBC Client</code> 和 <code>JDBC Template</code> 的实现。</p>
<h2> 初始化对比</h2>
<p><code>JdbcTemplate</code>的初始化：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>JdbcClient</code>的初始化；</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 增删改查的实现对比</h2>
<h3> 查询的实现对比</h3>
<p><code>getAllCustomer</code>查询返回集合数据的实现对比：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>getCustomerById</code>查询返回单条数据的实现对比：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> <code>insert</code>插入数据的实现对比</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> <code>update</code>更新数据的实现对比</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> <code>delete</code>删除数据的实现对比</h3>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 总结</h2>
<p>上面我们分别演示了<code>JdbcClient</code> 和 <code>JdbcTemplate</code>从初始化到真正执行增删改查操作的代码样例。总体上来说，<code>JdbcClient</code>的实现更为简洁方便。如果不考虑其他ORM框架的情况下，在未来的Spring Boot版本中，我会更偏向于选择<code>JdbcClient</code>来操作数据库。那么您觉得怎么样呢？留言区一起聊聊～</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>使用Spring AI让你的Spring Boot应用拥有生成式AI能力</title>
      <link>https://spring.didispace.com/article/spring-boot-openai-example.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-openai-example.html</guid>
      <source url="https://spring.didispace.com/rss.xml">使用Spring AI让你的Spring Boot应用拥有生成式AI能力</source>
      <description>使用Spring AI让你的Spring Boot应用拥有生成式AI能力 之前分享了关于Spring新项目Spring AI的介绍视频。视频里演示了关于使用Spring AI将Open AI的能力整合到Spring应用中的操作，但有不少读者提到是否有博客形式的学习内容。所以，本文就将具体介绍如何使用 Spring AI 快速让您的Spring应用拥有生成式AI的强大能力。</description>
      <category>Spring Boot</category>
      <pubDate>Sun, 07 Jan 2024 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 使用Spring AI让你的Spring Boot应用拥有生成式AI能力</h1>
<p>之前分享了关于Spring新项目<a href="https://www.didispace.com/article/news/new-spring-ai.html" target="_blank" rel="noopener noreferrer"><code>Spring AI</code>的介绍视频</a>。视频里演示了关于使用Spring AI将Open AI的能力整合到Spring应用中的操作，但有不少读者提到是否有博客形式的学习内容。所以，本文就将具体介绍如何使用 Spring AI 快速让您的Spring应用拥有生成式AI的强大能力。</p>
<h2> 动手试试</h2>
<p>第一步：使用你最喜欢的IDE来生成一个基础的Spring Boot项目。如果您还不会这个，建议先前往<a href="https://www.didispace.com/spring-boot-2/1-2-quick-start.html" target="_blank" rel="noopener noreferrer">Spring Boot快速入门</a>学习。</p>
<p>第二步：<code>pom.xml</code>中引入依赖。当前分为两个，Azure OpenAI和OpenAI，选择其中一个你在用的即可。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外，因为用的是SNAPSHOT版本，记得配置：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第三步：打开<code>application.properties</code>，配置您的<code>openai api key</code></p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>第四步：创建<code>OpenAIController.java</code></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第五步：使用<code>AiClient</code>对象来根据接口输入返回内容：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这是一个最简单的例子，而实际真正应用的时候，我们还需要<code>Prompt</code>来获得更精准的结果。比如，下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过使用<code>PromptTemplate</code>创建一个模版，然后根据用户输入使用模版来创建具体的<code>Prompt</code>生成结果。</p>
<p>这里我们提到的<code>Prompt</code>类，其实是一系列<code>Message</code>对象的结构化持有者，每个对象代表完整<code>Prompt</code>的一部。每个<code>Message</code>都有着不同的内容和目的，这种设置有助于与人工智能模型进行复杂而细致的交流，因为<code>Prompt</code>由各种消息组成，每条消息在对话中都指定了特定的功能。</p>
<p>下面是一个更复杂的使用方式：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里<code>Prompt</code>使用了List类型的Message，包含了多个不同级别的Prompt模版：<code>SystemPromptTemplate</code>和<code>PromptTemplate</code>，以完成更好的生成结果。</p>
<p>完成这几个API的构建之后，您可以尝试启动它，并用API测试工具调用试试，体验一下生成式AI的强大能力。</p>
<p>好了，今天的分享就到这里，感谢阅读！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot中的 6 种API请求参数读取方式</title>
      <link>https://spring.didispace.com/article/spring-boot-rest-api-6-ways-to-pass-params.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-rest-api-6-ways-to-pass-params.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot中的 6 种API请求参数读取方式</source>
      <description>Spring Boot中的 6 种API请求参数读取方式 使用Spring Boot开发API的时候，读取请求参数是服务端编码中最基本的一项操作，Spring Boot中也提供了多种机制来满足不同的API设计要求。 接下来，就通过本文，为大家总结6种常用的请求参数读取方式。如果你发现自己知道的不到6种，那么赶紧来查漏补缺一下。如果你知道的不止6种，那么告诉大家，一起互相学习一下吧～ @RequestParam 这是最最最最最最常用的一个了吧，用来加载URL中?之后的参数。 比如：这个请求/user?name=didispace 就可以如下面这样，使用@RequestParam来加载URL中的name参数</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 22 May 2024 23:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot中的 6 种API请求参数读取方式</h1>
<p>使用Spring Boot开发API的时候，读取请求参数是服务端编码中最基本的一项操作，Spring Boot中也提供了多种机制来满足不同的API设计要求。</p>
<p>接下来，就通过本文，为大家总结6种常用的请求参数读取方式。如果你发现自己知道的不到6种，那么赶紧来查漏补缺一下。如果你知道的不止6种，那么告诉大家，一起互相学习一下吧～</p>
<h2> @RequestParam</h2>
<p>这是最最最最最最常用的一个了吧，用来加载URL中<code>?</code>之后的参数。</p>
<p>比如：这个请求<code>/user?name=didispace</code> 就可以如下面这样，使用<code>@RequestParam</code>来加载URL中的name参数</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> @PathVariable</h2>
<p>这是RESTful风格API中常用的注解，用来加载URL路径中的参数</p>
<p>比如：这个请求<code>/user/1</code> 就可以如下面这样，使用<code>@PathVariable</code>来加载URL中的id参数</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> @MatrixVariable</h2>
<p>这个我们用的并不是很多，但一些国外系统有提供这类API参数，这种API的参数通过<code>;</code>分割。</p>
<p>比如：这个请求<code>/books/reviews;isbn=1234;topN=5;</code> 就可以如下面这样，使用<code>@MatrixVariable</code>来加载URL中用<code>;</code>分割的参数</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> @RequestBody</h2>
<p>这也是最常用的一个注解，用来加载POST/PUT请求的复杂请求体（也叫：payload）。比如，客户端需要提交一个复杂数据的时候，就要将这些数据放到请求体中，然后服务端用<code>@RequestBody</code>来加载请求体中的数据</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> @RequestHeader</h2>
<p><code>@RequestHeader</code>注解用来加载请求头中的数据，一般在业务系统中不太使用，但在基础设施的建设中会比较常用，比如传递分布式系统的TraceID等。用法也很简单，比如，假设我们将鉴权数据存在http请求头中，那么就可以像下面这样用<code>@RequestHeader</code>来加载请求头中的<code>Authorization</code>参数</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> @CookieValue</h2>
<p>当我们需要与客户端保持有状态的交互时，就需要用到Cookie。此时，服务端读取Cookie数据的时候，就可以像下面这样用<code>@CookieValue</code>来读取Cookie中的<code>SessionId</code>数据</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>好了，今天的分享就到这里。这些方式你都知道吗？如果有学到新知识，欢迎点赞支持。如果你还知道其他方式，也欢迎评论分享。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>为什么不推荐在Spring Boot中使用@Value加载配置</title>
      <link>https://spring.didispace.com/article/spring-boot-stop-use-value.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-stop-use-value.html</guid>
      <source url="https://spring.didispace.com/rss.xml">为什么不推荐在Spring Boot中使用@Value加载配置</source>
      <description>为什么不推荐在Spring Boot中使用@Value加载配置 @Value注解相信很多Spring Boot的开发者都已经有接触了，通过使用该注解，我们可以快速的把配置信息加载到Spring的Bean中。 比如下面这样，就可以轻松的把配置文件中key为com.didispace.title配置信息加载到TestService中来使用 @Service public class TestService { @Value(&amp;quot;${com.didispace.title}&amp;quot;) private String title; }</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 20 May 2024 23:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 为什么不推荐在Spring Boot中使用@Value加载配置</h1>
<p><code>@Value</code>注解相信很多Spring Boot的开发者都已经有接触了，通过使用该注解，我们可以快速的把配置信息加载到Spring的Bean中。</p>
<p>比如下面这样，就可以轻松的把配置文件中key为<code>com.didispace.title</code>配置信息加载到TestService中来使用</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个注解可以说非常的好用！但是为什么不推荐大家使用它呢？<strong>核心原因是：当我们使用<code>@Value</code>来直接提取配置信息使用的时候，会产生配置信息加载的碎片化</strong>。比如，同一个配置，可能背多个Service或者Controller使用，当我们再要修改它的时候，就会存在一个遗漏的风险。我们无法方便的维护这些配置加载而导致一些问题。</p>
<p>那么，如果不使用<code>@Value</code>，我们应该用什么来替代呢？</p>
<p>我比较推荐的就是使用<code>@ConfigurationProperties</code>来分类和加载各种配置信息，比如，我要加载关于<code>com.didispace</code>的相关配置时候，就写一个这样的实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个<code>DidispaceProperties</code>就会加载<code>com.didispace</code>开头的配置。其他Service或者Controller要使用这些配置的时候，就通过注入<code>DidispaceProperties</code>就也可以了。如果要修改配置相关的逻辑，也只需要修改<code>DidispaceProperties</code>中的内容即可，而不是到处找<code>@Value</code>的配置。</p>
<p>另外，在这里，其实还可以增加对配置的校验，主要在pom.xml中引入<code>spring-boot-starter-validation</code>模块。然后修改配置类，增加<code>@Validated</code>注解和具体校验注解，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这就轻松实现配置的校验了，是不是很方便呢？</p>
<p>今天的分享就到这里。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>MySQL驱动扯后腿？Spring Boot用虚拟线程可能比用物理线程还差</title>
      <link>https://spring.didispace.com/article/spring-boot-virtual-threads-mysql.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-virtual-threads-mysql.html</guid>
      <source url="https://spring.didispace.com/rss.xml">MySQL驱动扯后腿？Spring Boot用虚拟线程可能比用物理线程还差</source>
      <description>MySQL驱动扯后腿？Spring Boot用虚拟线程可能比用物理线程还差 之前已经分享过多篇关于Spring Boot中使用Java 21新特性虚拟线程的性能测试案例： Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？ Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 18 Jan 2024 12:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> MySQL驱动扯后腿？Spring Boot用虚拟线程可能比用物理线程还差</h1>
<p>之前已经分享过多篇关于Spring Boot中使用<a href="https://www.didispace.com/java-features/java21/jep444-virtual-threads.html" target="_blank" rel="noopener noreferrer">Java 21新特性虚拟线程</a>的性能测试案例：</p>
<ul>
<li><a href="https://www.didispace.com/article/spring-boot/how-fast-spring-boot-3-2-virtual-thread.html" target="_blank" rel="noopener noreferrer">Spring Boot 3.2虚拟线程搭建静态文件服务器有多快？</a></li>
<li><a href="https://www.didispace.com/article/spring-boot/spring-boot-virtual-threads-vs-webflux.html" target="_blank" rel="noopener noreferrer">Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</a></li>
</ul>
<p>早上看到群友问到一个关于虚拟线程遇到MySQL连接不兼容导致的性能问题：</p>
<p><img src="https://static.didispace.com/images3/f0228c9843d19482ff72719fabf0f29f.png" alt=""></p>
<p>这个问题确实之前就有看到过相关的评测，顺着个这个问题，重新把相关评测找出来，给大家分享一下。</p>
<blockquote>
<p>以下内容主要参考文章：https://medium.com/deno-the-complete-reference/springboot-physical-vs-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-23d773b41ffd</p>
</blockquote>
<h2> 评测案例</h2>
<p>评测采用现实场景中的处理流程，具体如下：</p>
<ol>
<li>从HTTP授权标头（authorization header）中提取 JWT</li>
<li>验证 JWT 并从中提取用户的电子邮件</li>
<li>使用提取到的电子邮件执行 MySQL 查询用户</li>
<li>返回用户记录</li>
</ol>
<p>这个场景其实是<a href="https://www.didispace.com/article/spring-boot/spring-boot-virtual-threads-vs-webflux.html" target="_blank" rel="noopener noreferrer">Spring Boot 虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</a>测试的后续。前文主要对比了虚拟线程和WebFlux的，但没有对比虚拟线程与物理线程的区别。所以，接下来的内容就是本文关心的重点：在物理线程和虚拟线程下，MySQL驱动是否有性能优化。</p>
<h2> 测试环境</h2>
<ul>
<li>Java 20（使用预览模式，开启虚拟线程）</li>
<li>Spring Boot 3.1.3</li>
<li>依赖的第三方库：jjwt、mysql-connector-java</li>
</ul>
<p><strong>测试工具：Bombardier</strong></p>
<p>采用了开源负载测试工具：Bombardier。在测试场景中预先创建 100,000 个 JWT 列表。</p>
<p>在测试期间，Bombardier 从该池中随机选择了JWT，并将它们包含在HTTP请求的Authorization标头中。</p>
<p><strong>MySQL表结构与数据准备</strong></p>
<p>User表结构如下：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>准备大约10w条数据：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试代码：使用物理线程</h2>
<p>配置文件：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>User实体定义：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>数据访问实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>API实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>应用主类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试代码：使用虚拟线程</h2>
<p>主要调整应用主类，其他一样，具体修改如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试代码：使用WebFlux</h2>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>数据访问实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>业务逻辑实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>API实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>应用主类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试结果</h2>
<p>每次测试都包含 100 万个请求，分别评估了它们在不同并发（50、100、300）水平下的性能。下面是结果展示：</p>
<p><img src="https://static.didispace.com/images3/53bf3395b0718c39394f50124c89b270.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/b34ff77fb1c93168661c1f4f31dd9830.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/61eb78352afb34eecc3c784b893aeb91.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/2ccb3b6ea6c66b5eff45ae9eb52640cd.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/a0499fed887ed50e45f3821c9422b732.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/2228582ef2dd31c597c12b3d6ba4428b.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/96255019e85e43a7bdccf6f6a985c370.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/b9ed40b83e4b88b594a8dfe941566b53.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/28c3bdc1f0fe4d12cf55af1b567876b5.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/6b6c01737f7ce90a4b26b7bc63149b9d.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/01f176418b3db51d88926902a96fecd9.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/ff4a6b8d83f7d3fa3680bb7a68ac4aca.png" alt=""></p>
<p><img src="https://static.didispace.com/images3/e936a9f6e2507615c2dfc40a86603af4.png" alt=""></p>
<h2> 分析总结</h2>
<p>在这个测试案例中使用了MySQL驱动，虚拟线程的实现方式性能最差，WebFlux依然保持领先。所以，主要原因在于这个MySQL的驱动对虚拟线程不友好。如果涉及到数据库访问的情况下，需要寻找对虚拟线程支持最佳的驱动程序。另外，该测试使用的是Java 20和Spring Boot 3.1。对于Java 21和Spring Boot 3.2建议读者在使用的时候自行评估。</p>
<p>最后，对于MySQL驱动对虚拟线程支持好的，欢迎留言区推荐一下。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/f0228c9843d19482ff72719fabf0f29f.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</title>
      <link>https://spring.didispace.com/article/spring-boot-virtual-threads-vs-webflux.html</link>
      <guid>https://spring.didispace.com/article/spring-boot-virtual-threads-vs-webflux.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</source>
      <description>Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较 早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章，觉得还不错。内容较长，我就不翻译了，抓重点给大家介绍一下这篇文章的核心内容，方便大家快速阅读。 测试场景 作者采用了一个尽可能贴近现实操作的场景： 从授权头信息中提取JWT 验证JWT并从中提取用户的Email 使用用户的Email去MySQL里执行查询 返回用户记录</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 19 Sep 2023 12:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot虚拟线程与Webflux在JWT验证和MySQL查询上的性能比较</h1>
<p>早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章，觉得还不错。内容较长，我就不翻译了，抓重点给大家介绍一下这篇文章的核心内容，方便大家快速阅读。</p>
<h2> 测试场景</h2>
<p>作者采用了一个尽可能贴近现实操作的场景：</p>
<ol>
<li>从授权头信息中提取<a href="https://www.didispace.com/oblog/json-web-token-web-security.html" target="_blank" rel="noopener noreferrer">JWT</a></li>
<li>验证JWT并从中提取用户的Email</li>
<li>使用用户的Email去MySQL里执行查询</li>
<li>返回用户记录</li>
</ol>
<h2> 测试技术</h2>
<p>这里要对比的两个核心技术点是：</p>
<ol>
<li>带有虚拟线程的Spring Boot：这不是一个跑在传统物理线程上的Spring Boot应用，而是跑在虚拟线程上的。这些轻量级线程简化了开发、维护和调试高吞吐量并发应用程序的复杂任务。虽然虚拟线程仍然在底层操作系统线程上运行，但它们带来了显着的效率改进。当虚拟线程遇到阻塞 I/O 操作时，Java 运行时会暂时挂起它，从而释放关联的操作系统线程来为其他虚拟线程提供服务。这个优雅的解决方案优化了资源分配并增强了整体应用程序响应能力。</li>
<li>Spring Boot Webflux：Spring Boot WebFlux是Spring生态系统中的反应式编程框架，它利用Project Reactor库来实现非阻塞、事件驱动的编程。所以，它特别适合需要高并发和低延迟的应用程序。依靠反应式方法，它允许开发人员有效地处理大量并发请求，同时仍然提供与各种数据源和通信协议集成的灵活性。</li>
</ol>
<p>不论是Webflux还是虚拟线程，这两个都是为了提供程序的高并发能力而生，那么谁更胜一筹呢？下面一起看看具体的测试。</p>
<h2> 测试环境</h2>
<p><strong>运行环境与工具</strong></p>
<ul>
<li>一台16G内存的MacBook Pro M1</li>
<li>Java 20</li>
<li>Spring Boot 3.1.3</li>
<li>启用预览模式，以获得虚拟线程的强大能力</li>
<li>依赖的第三方库：jjwt、mysql-connector-java</li>
<li>测试工具：Bombardier</li>
<li>数据库：MySQL</li>
</ul>
<p><strong>数据准备</strong></p>
<ul>
<li>在Bombardier中准备100000个JWT列表，用来从中随机选取<a href="https://www.didispace.com/oblog/json-web-token-web-security.html" target="_blank" rel="noopener noreferrer">JWT</a>，并将其放入HTTP请求的授权信息中。</li>
<li>在MySQL中创建一个users表，表结构如下：</li>
</ul>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>为users表准备100000条用户数据</li>
</ul>
<h2> 测试代码</h2>
<h3> 带虚拟线程的Spring Boot程序</h3>
<p><code>application.properties</code>配置文件：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>User</code>实体类（为了让文章让简洁一些，这里<a href="https://www.didispace.com/blog.html" target="_blank" rel="noopener noreferrer">DD</a>省略了getter和setter）：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>应用主类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提供CRUD操作的<code>UserRepository</code>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提供API接口的<code>UserController</code>类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> Spring Boot Webflux程序</h3>
<p><code>application.properties</code>配置文件：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>User</code>实体（这里<a href="https://www.didispace.com/blog.html" target="_blank" rel="noopener noreferrer">DD</a>也省略了构造函数、getter和setter）：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>应用主类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提供CRUD操作的<code>UserRepository</code>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提供根据id查用户的业务类<code>UserService</code>：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>提供API接口的<code>UserController</code>类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试结果</h2>
<p>接下来是重头戏了，作者对两个技术方案都做了500w个请求的测试，评估的不同并发连接级别包含：50、100、300。</p>
<p>具体结果如下三张图：</p>
<p><img src="https://static.didispace.com/images3/a4283c7c25273c78770a5405c2ebf845.png" alt="50并发连接"></p>
<p><img src="https://static.didispace.com/images3/542059ff5d0fee82c98ede90baf34155.png" alt="100并发连接"></p>
<p><img src="https://static.didispace.com/images3/4ba5e04caadac9da58f911c160891f18.png" alt="300并发连接"></p>
<p>最后，作者得出结论：Spring Boot Webflux要更优于带虚拟线程的Spring Boot。</p>
<p><img src="https://static.didispace.com/images3/988b5993ce89387d4622663b207a5d4a.png" alt=""></p>
<p>似乎引入了虚拟线程还不如已经在用的Webflux？不知道大家是否有做过相关调研呢？如果有的话，欢迎在留言区一起聊聊～</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<p>如果您对这篇内容的原文感兴趣的话，可以通过下面的链接直达：</p>
<blockquote>
<p>原文：https://medium.com/deno-the-complete-reference/springboot-virtual-threads-vs-webflux-performance-comparison-for-jwt-verify-and-mysql-query-ff94cf251c2c</p>
</blockquote>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/a4283c7c25273c78770a5405c2ebf845.png" type="image/png"/>
    </item>
    <item>
      <title>spring-configuration-metadata.json文件是做啥的？</title>
      <link>https://spring.didispace.com/article/spring-configuration-metadata-json.html</link>
      <guid>https://spring.didispace.com/article/spring-configuration-metadata-json.html</guid>
      <source url="https://spring.didispace.com/rss.xml">spring-configuration-metadata.json文件是做啥的？</source>
      <description>spring-configuration-metadata.json文件是做啥的？ 在前几天分享的关于Spring Boot Starter中的spring.factories文件不再推荐使用的视频下，看到有网友问了这样一个问题： 这个文件也是位于/META-INF/目录下面：</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 27 May 2022 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> spring-configuration-metadata.json文件是做啥的？</h1>
<p>在前几天分享的关于Spring Boot Starter中的<code>spring.factories</code>文件不再推荐使用的视频下，看到有网友问了这样一个问题：</p>
<p><img src="https://static.didispace.com/images/202205/spring-configuration-metadata-json/1653644481556.png" alt="图 1"></p>
<p>这个文件也是位于<code>/META-INF/</code>目录下面：</p>
<p><img src="https://static.didispace.com/images/202205/spring-configuration-metadata-json/1653644568973.png" alt="图 2"></p>
<p>里面的大概是这样的：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>spring.factories</code>的作用是让Spring Boot知道要加载这个Starter的哪些配置类，而这个文件又有啥用呢？</p>
<p>其实，之前我在<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程</a>中有具体讲过，这个内容是<a href="https://blog.didispace.com/spring-boot-learning-24-1-6/" target="_blank" rel="noopener noreferrer">配置元数据</a>。它的主要作用就是，当我们在配置文件中尝试编写配置信息的时候，IDE可以根据这个配置元数据给出了相关的提示信息，比如：</p>
<p><img src="https://static.didispace.com/images/202205/spring-configuration-metadata-json/1653646525823.png" alt="图 3"></p>
<p>所以，要做一个体验良好的Starter，这个文件还是非常重要的，对于使用你封装的开发者来说，写配置的时候就会方便很多。</p>
<h2> 如何自动生成？</h2>
<p>既然<code>spring.factories</code>可以自动生成，那么<code>spring-configuration-metadata.json</code>文件可以自动生成吗？</p>
<p>答案是肯定的，不然维护这个<code>json</code>文件都会累死人！</p>
<p>你只需要在你的<code>pom.xml</code>中加入这个依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>一切就都搞定了，当你再次编译的时候，<code>spring-configuration-metadata.json</code>文件就自动出现啦！不过，这里还有个前提，你在写配置属性类的时候，有好好写注释，那么自动提示就都会带上了，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>好了，今天的分享就到这里！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<p>本期视频已发布：<a href="https://www.bilibili.com/video/BV1L54y1o7rC/" target="_blank" rel="noopener noreferrer">https://www.bilibili.com/video/BV1L54y1o7rC/</a>，欢迎关注我的B站！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/202205/spring-configuration-metadata-json/1653644481556.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot应用中如何动态指定数据库，实现不同用户不同数据库等场景</title>
      <link>https://spring.didispace.com/article/spring-data-jpa-dynamically-schema.html</link>
      <guid>https://spring.didispace.com/article/spring-data-jpa-dynamically-schema.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot应用中如何动态指定数据库，实现不同用户不同数据库等场景</source>
      <description>Spring Boot应用中如何动态指定数据库，实现不同用户不同数据库等场景 当在 Spring Boot 应用程序中使用Spring Data JPA 进行数据库操作时，配置Schema名称是一种常见的做法。然而，在某些情况下，模式名称需要是动态的，可能会在应用程序运行时发生变化。比如：需要做数据隔离的SaaS应用。 所以，这篇博文将帮助您解决了在 Spring Boot 应用程序中如何设置动态 Schema。</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 26 Apr 2024 15:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot应用中如何动态指定数据库，实现不同用户不同数据库等场景</h1>
<p>当在 Spring Boot 应用程序中使用<a href="/spring-boot-2/4-4-spring-data-jpa.html" target="blank">Spring Data JPA</a> 进行数据库操作时，配置Schema名称是一种常见的做法。然而，在某些情况下，模式名称需要是动态的，可能会在应用程序运行时发生变化。比如：需要做数据隔离的SaaS应用。</p>
<p>所以，这篇博文将帮助您解决了在 Spring Boot 应用程序中如何设置动态 Schema。</p>
<h2> 问题场景</h2>
<p>假设，您的应用程序是一个SaaS软件，需要为多个租户提供服务，每个租户都需要一个单独的数据库架构。</p>
<p>在这种情况下，在应用程序属性中对Shema名称进行硬编码是不太可能的，这样有一个用户新增，就要去写代码更新。</p>
<p>所以，为了应对这一挑战，我们将探索一种允许在运行时动态配置模式名称的解决方案。</p>
<h2> 代码案例</h2>
<p>让我们创建一个 Spring Boot 项目 首先设置一个具有必要依赖项的新 Spring Boot 项目。在项目配置中包括 Spring Web、Spring Data JPA 和关于数据库的依赖项。</p>
<p>定义Spring Data JPA的实体类，例如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建数据访问接口，以便您的实体提供 CRUD 操作：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>创建一个用来处理业务逻辑，包括与数据库交互的方法：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>实现API接口：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>重点：配置动态Schema</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>重新打包该Spring Boot应用，然后当我们要为不同用户使用完全隔离的数据库、完全隔离的应用的时候，只需要通过下面的启动命令，就能轻松实现了：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这里，通过启动命令中的<code>custom.schema.name</code>参数，就能去指定不同的数据库Schema，而应用程序端都是同一套代码，由于启动了新的Spring Boot应用，所以应用端进程也是完全隔离的。这种方法，对于使用Spring Boot构建需要一定资源隔离SaaS软件来说，是个不错的实现方案。</p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot Starter配置spring.factories的自动生成神器：mica-auto</title>
      <link>https://spring.didispace.com/article/spring-factories-mica-auto.html</link>
      <guid>https://spring.didispace.com/article/spring-factories-mica-auto.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot Starter配置spring.factories的自动生成神器：mica-auto</source>
      <description>Spring Boot Starter配置spring.factories的自动生成神器：mica-auto 昨晚我们讲了Spring Boot 2.7开始不再推荐使用spring.factories了，今天早上公众号（程序猿DD）上也推了，然后收到了不少反馈。其中有个网友说：要维护两个文件，太麻烦了。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 24 May 2022 14:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot Starter配置spring.factories的自动生成神器：mica-auto</h1>
<p>昨晚我们讲了<a href="https://blog.didispace.com/spring-factories-deprecations/" target="_blank" rel="noopener noreferrer">Spring Boot 2.7开始不再推荐使用spring.factories了</a>，今天早上公众号（程序猿DD）上也推了，然后收到了不少反馈。其中有个网友说：要维护两个文件，太麻烦了。</p>
<p><img src="https://static.didispace.com/images/202205/blogs/spring-factories-mica-auto/WX20220524-131534.png" alt=""></p>
<p>作为一名优秀的程序员，当觉得麻烦的时候，第一反应就是要想办法偷懒！</p>
<p>所以，今天就给大家讲讲怎么样轻松的维护这两个文件！</p>
<h2> 开源项目：mica-auto</h2>
<p>为了解决维护麻烦的问题，这里要给大家推荐一个开源项目：<strong>mica-auto</strong>。不要看Star不多，DD用下来感觉还是非常好的。</p>
<ul>
<li>GitHub地址：<a href="https://github.com/lets-mica/mica-auto" target="_blank" rel="noopener noreferrer">https://github.com/lets-mica/mica-auto</a></li>
</ul>
<p>该项目的实现原理与Lombok类似，利用Java 6就开始支持的Annotation Processor，在编译期扫描注解和处理注解。这里mica-auto的主要解决问题是为Spring Boot自动生成<code>spring.factories</code>、<code>spring-devtools.properties</code>配置。</p>
<p>目前最新的2.3版本也支持Spring Boot 2.7之后推荐使用的<code>org.springframework.boot.autoconfigure.AutoConfiguration.imports</code>配置文件的自动生成。</p>
<h2> 使用mica-auto</h2>
<p>下面以我自己的项目为例，看看怎么一步步整合：</p>
<p><strong>第1步</strong>：pom.xml中加入依赖（注意，如果有lombok的话，要放到lombok后面）</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第2步</strong>：替换一些老的自动化配置注解，比如，我这里用<code>@AutoConfiguration</code>替换了老的<code>@Configuration</code>，这样才能正常的生成出<code>org.springframework.boot.autoconfigure.AutoConfiguration.imports</code>配置，不然用老的配置注解的话，只会自动生成<code>spring.factories</code></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完成上面配置之后，重新编译马上就可以看到文件生成成功了：</p>
<p><img src="https://static.didispace.com/images/202205/blogs/spring-factories-mica-auto/2022-05-24T151841.png" alt=""></p>
<p>本期视频：<a href="https://www.bilibili.com/video/BV19v4y1A7T1/" target="_blank" rel="noopener noreferrer">https://www.bilibili.com/video/BV19v4y1A7T1/</a></p>
<p>如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/202205/blogs/spring-factories-mica-auto/WX20220524-131534.png" type="image/png"/>
    </item>
    <item>
      <title>WebClient第一次请求返回 Connection reset by peer 的错误</title>
      <link>https://spring.didispace.com/article/webclient-connection-reset-by-peer.html</link>
      <guid>https://spring.didispace.com/article/webclient-connection-reset-by-peer.html</guid>
      <source url="https://spring.didispace.com/rss.xml">WebClient第一次请求返回 Connection reset by peer 的错误</source>
      <description>WebClient第一次请求返回 Connection reset by peer 的错误 我有一个需求，需要从外部系统获取用户数据。我通过 WebClient 实现的，作为声明性 HTTP 客户端的一部分。 有趣的是，在新的浏览器标签页中打开应用程序后，第一次请求返回了“Connection reset by peer”错误。 这个问题通过禁用对外请求的 keep-alive 解决了。 改造前的代码： @Configuration public class HttpProxyConfiguration { @Value(&amp;quot;${tracker.url}&amp;quot;) private String trackerUrl; @Bean TrackerClient trackerClient(WebClient.Builder builder) { var wc = builder.baseUrl(trackerUrl) .build(); var wca = WebClientAdapter.forClient(wc); return HttpServiceProxyFactory.builder() .clientAdapter(wca) .build() .createClient(TrackerClient.class); } }</description>
      <category>Spring Boot</category>
      <pubDate>Thu, 18 Jul 2024 23:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> WebClient第一次请求返回 Connection reset by peer 的错误</h1>
<p>我有一个需求，需要从外部系统获取用户数据。我通过 WebClient 实现的，作为声明性 HTTP 客户端的一部分。</p>
<p>有趣的是，在新的浏览器标签页中打开应用程序后，第一次请求返回了“Connection reset by peer”错误。</p>
<p>这个问题通过禁用对外请求的 keep-alive 解决了。</p>
<p>改造前的代码：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>改造后的代码：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在查看了这个 GitHub Issue （https://github.com/reactor/reactor-netty/issues/388#issuecomment-704069492）后，似乎禁用连接池或禁用 keep-alive 并不推荐。</p>
<p>因此，我采用了该 GitHub Issue （https://github.com/reactor/reactor-netty/issues/1774#issuecomment-908066283）中提到的解决方案，设置了某些连接超时。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上述代码已经至少在生产环境中运行了两周。到目前为止，我还没有遇到任何错误。通过设置连接池，我希望能够提高应用程序的性能，特别是由于它经常需要向另一个服务发出大量请求以获取前端所需的所有数据。我计划继续长期监控这个解决方案，以评估其影响，并确定它是否确实提升了应用程序的性能。</p>
<blockquote>
<p>本文翻译自原文：https://medium.com/@jskim1991/spring-boot-how-to-solve-webclient-connection-reset-by-peer-error-b1fa38e4106a</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot、Spring Cloud、Spring Cloud Alibaba等框架之间不得不说的版本关系</title>
      <link>https://spring.didispace.com/spring-boot-2/1-1-version-intro.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/1-1-version-intro.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot、Spring Cloud、Spring Cloud Alibaba等框架之间不得不说的版本关系</source>
      <description>Spring Boot、Spring Cloud、Spring Cloud Alibaba等框架之间不得不说的版本关系 这篇博文是临时增加出来的内容，主要是由于最近连载《Spring Cloud Alibaba基础教程》系列的时候，碰到读者咨询的大量问题中存在一个比较普遍的问题：版本的选择。其实这类问题，在之前写Spring Cloud基础教程的时候，就已经发过一篇《聊聊Spring Cloud版本的那些事儿》，来说明Spring Boot和Spring Cloud版本之间的关系。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot、Spring Cloud、Spring Cloud Alibaba等框架之间不得不说的版本关系</h1>
<p>这篇博文是临时增加出来的内容，主要是由于最近连载《Spring Cloud Alibaba基础教程》系列的时候，碰到读者咨询的大量问题中存在一个比较普遍的问题：版本的选择。其实这类问题，在之前写Spring Cloud基础教程的时候，就已经发过一篇<a href="https://blog.didispace.com/springcloud-version/" target="_blank" rel="noopener noreferrer">《聊聊Spring Cloud版本的那些事儿》</a>，来说明Spring Boot和Spring Cloud版本之间的关系。</p>
<h2> Spring Cloud Alibaba现阶段版本的特殊性</h2>
<p>现在的Spring Cloud Alibaba由于没有纳入到Spring Cloud的主版本管理中，所以我们需要自己去引入其版本信息，比如之前教程中的例子：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>而不是像以往使用Spring Cloud的时候，直接引入Spring Cloud的主版本（Dalston、Edgware、Finchley、Greenwich这些）就可以的。我们需要像上面的例子那样，单独的引入<code>spring-cloud-alibaba-dependencies</code>来管理Spring Cloud Alibaba下的组件版本。</p>
<p>由于Spring Cloud基于Spring Boot构建，而Spring Cloud Alibaba又基于Spring Cloud Common的规范实现，所以当我们使用Spring Cloud Alibaba来构建微服务应用的时候，需要知道这三者之间的版本关系。</p>
<p>下表整理了目前Spring Cloud Alibaba的版本与Spring Boot、Spring Cloud版本的兼容关系：</p>
<table>
<thead>
<tr>
<th>Spring Boot</th>
<th>Spring Cloud</th>
<th>Spring Cloud Alibaba</th>
</tr>
</thead>
<tbody>
<tr>
<td>2.1.x</td>
<td>Greenwich</td>
<td>0.9.x</td>
</tr>
<tr>
<td>2.0.x</td>
<td>Finchley</td>
<td>0.2.x</td>
</tr>
<tr>
<td>1.5.x</td>
<td>Edgware</td>
<td>0.1.x</td>
</tr>
<tr>
<td>1.5.x</td>
<td>Dalston</td>
<td>0.1.x</td>
</tr>
</tbody>
</table>
<p>（上表截止于本文发布时间，如果已经过时，请查看Spring官网）</p>
<p><strong>以上版本对应内容根据当前情况实时调整修改，以方便用户了解他们的对应关系变化情况</strong></p>
<p>所以，不论您是在读我的<a href="https://blog.didispace.com/Spring-Boot%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/" target="_blank" rel="noopener noreferrer">《Spring Boot基础教程》</a>、<a href="https://blog.didispace.com/spring-cloud-learning/" target="_blank" rel="noopener noreferrer">《Spring Cloud基础教程》</a>还是正在连载的《Spring Cloud Alibaba系列教程》。</p>
<p>当您照着博子的顺序，一步步做下来，但是没有调试成功的时候，强烈建议检查一下，您使用的版本是否符合上表的关系。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：快速入门</title>
      <link>https://spring.didispace.com/spring-boot-2/1-2-quick-start.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/1-2-quick-start.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：快速入门</source>
      <description>Spring Boot 2.x基础教程：快速入门 简介 在您第1次接触和学习Spring框架的时候，是否因为其繁杂的配置而退却了？在你第n次使用Spring框架的时候，是否觉得一堆反复黏贴的配置有一些厌烦？那么您就不妨来试试使用Spring Boot来让你更易上手，更简单快捷地构建Spring应用！ Spring Boot让我们的Spring应用变的更轻量化。我们不必像以前那样繁琐的构建项目、打包应用、部署到Tomcat等应用服务器中来运行我们的业务服务。通过Spring Boot实现的服务，只需要依靠一个Java类，把它打包成jar，并通过java -jar命令就可以运行起来。这一切相较于传统Spring应用来说，已经变得非常的轻便、简单。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：快速入门</h1>
<h2> 简介</h2>
<p>在您第1次接触和学习Spring框架的时候，是否因为其繁杂的配置而退却了？在你第n次使用Spring框架的时候，是否觉得一堆反复黏贴的配置有一些厌烦？那么您就不妨来试试使用Spring Boot来让你更易上手，更简单快捷地构建Spring应用！</p>
<p>Spring Boot让我们的Spring应用变的更轻量化。我们不必像以前那样繁琐的构建项目、打包应用、部署到Tomcat等应用服务器中来运行我们的业务服务。通过Spring Boot实现的服务，只需要依靠一个Java类，把它打包成jar，并通过<code>java -jar</code>命令就可以运行起来。这一切相较于传统Spring应用来说，已经变得非常的轻便、简单。</p>
<p>总结一下Spring Boot的主要优点：</p>
<ul>
<li>为所有Spring开发者更快的入门</li>
<li>开箱即用，提供各种默认配置来简化项目配置</li>
<li>内嵌式容器简化Web项目</li>
<li>没有冗余代码生成和XML配置的要求</li>
</ul>
<h2> 快速入门</h2>
<p>本文我们将学习如何快速的创建一个Spring Boot应用，并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解，并体验其结构简单、开发快速的特性。</p>
<h3> 创建基础项目</h3>
<p>Spring官方提供了非常方便的工具<a href="https://start.spring.io/" target="_blank" rel="noopener noreferrer">Spring Initializr</a>来帮助我们创建Spring Boot应用。</p>
<h4> 使用Spring Initializr页面创建</h4>
<p><strong>第一步</strong>：访问Spring Initializr：<code>https://start.spring.io/</code></p>
<p><img src="https://static.didispace.com/images/pasted-178.png" alt=""></p>
<p>如图所示，几个选项说明：</p>
<ul>
<li>Project：使用什么构建工具，Maven还是Gradle；本教程将采用大部分Java人员都熟悉的Maven，以方便更多读者入门学习。</li>
<li>Language：使用什么编程语言，Java、Kotlin还是Groovy；本教程将采用Java为主编写，以方便更多读者入门学习。</li>
<li>Spring Boot：选用的Spring Boot版本；这里将使用当前最新的<code>2.1.3</code>版本。</li>
<li>Project Metadata：项目的元数据；其实就是Maven项目的基本元素，点开More options可以看到更多设置，根据自己组织的情况输入相关数据，比如：</li>
</ul>
<p><img src="https://static.didispace.com/images/pasted-179.png" alt=""></p>
<ul>
<li>Dependencies：选择要加入的Spring Boot组件；本文将实现一个Http接口，所以可以选择Web组件，只需要输入Web，页面会自动联想显示匹配的可选组件：</li>
</ul>
<p><img src="https://static.didispace.com/images/pasted-180.png" alt=""></p>
<p>点击”+“之后，就如下图所示：</p>
<p><img src="https://static.didispace.com/images/pasted-181.png" alt=""></p>
<p><strong>第二步</strong>：点击”Generate Project“按钮生成项目；此时浏览器会下载一个与上面<code>Artifact</code>名称一样的压缩包。</p>
<p><strong>第三步</strong>：解压项目包，并用编译器以Maven项目导入，以IntelliJ IDEA为例：</p>
<ul>
<li>菜单中选择：File –&gt; New –&gt; Project from Existing Sources...</li>
</ul>
<p><img src="https://static.didispace.com/images/pasted-187.png" alt=""></p>
<ul>
<li>选择解压后的项目文件夹，点击OK</li>
<li>点击：Import project from external model，并选择Maven，点击Next到底为止。</li>
<li>若你的环境有多个版本的JDK，注意到选择Java SDK的时候请选择Java 8（具体根据你在第一步中选择的Java版本为准）</li>
</ul>
<blockquote>
<p>由于我们后续会有很多样例工程，您也可以像我们样例仓库那样，用一个基础仓库，每篇文章的样例以模块的方式保存，具体形式可见文末的案例仓库。</p>
</blockquote>
<h4> 使用IntelliJ IDEA创建</h4>
<p>如果是使用IntelliJ IDEA来写Java程序的话，那么还可以直接在编译器中创建Spring Boot应用。</p>
<p><strong>第一步</strong>：菜单栏中选择：File =&gt; New =&gt; Project..，我们可以看到如下图所示的创建功能窗口。</p>
<p><img src="https://static.didispace.com/images/pasted-183.png" alt=""></p>
<p>其中Initial Service Url指向的地址就是Spring官方提供的Spring Initializr工具地址，所以这里创建的工程实际上也是基于它的Web工具来实现的。</p>
<p><strong>第二步</strong>：点击Next，等待片刻后，我们可以看到如下图所示的工程信息窗口：</p>
<p><img src="https://static.didispace.com/images/pasted-184.png" alt=""></p>
<p>其实内容就跟我们用Web版的Spring Initializr是一模一样的，跟之前在页面上一样填写即可。</p>
<p><strong>第三步</strong>：继续点击Next，进入选择Spring Boot版本和依赖管理的窗口：</p>
<p><img src="https://static.didispace.com/images/pasted-185.png" alt=""></p>
<p>在这里值的我们关注的是，它不仅包含了Spring Boot Starter POMs中的各个依赖，还包含了Spring Cloud的各种依赖。</p>
<p><strong>第四步</strong>：点击Next，进入最后关于工程物理存储的一些细节。最后，点击Finish就能完成工程的构建了。</p>
<blockquote>
<p>Intellij中的Spring Initializr虽然还是基于官方Web实现，但是通过工具来进行调用并直接将结果构建到我们的本地文件系统中，让整个构建流程变得更加顺畅，还没有体验过此功能的Spring Boot/Cloud爱好者们不妨可以尝试一下这种不同的构建方式。</p>
</blockquote>
<h3> 项目结构解析</h3>
<p><img src="https://static.didispace.com/images/pasted-186.png" alt=""></p>
<p>通过上面步骤完成了基础项目的创建。如上图所示，Spring Boot的基础结构共三个文件（具体路径根据用户生成项目时填写的Group所有差异）：</p>
<ul>
<li><code>src/main/java</code>下的程序入口：<code>Chapter11Application</code></li>
<li><code>src/main/resources</code>下的配置文件：<code>application.properties</code></li>
<li><code>src/test/</code>下的测试入口：<code>Chapter11ApplicationTests</code></li>
</ul>
<p>生成的<code>Chapter11Application</code>和<code>Chapter11ApplicationTests</code>类都可以直接运行来启动当前创建的项目，由于目前该项目未配合任何数据访问或Web模块，程序会在加载完Spring之后结束运行。</p>
<h3> 项目依赖解析</h3>
<p>打开<code>pom.xml</code>，一起来看看Spring Boot项目的依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如上所示，主要有四个部分：</p>
<ul>
<li>项目元数据：创建时候输入的Project Metadata部分，也就是Maven项目的基本元素，包括：groupId、artifactId、version、name、description等</li>
<li>parent：继承<code>spring-boot-starter-parent</code>的依赖管理，控制版本与打包等内容</li>
<li>dependencies：项目具体依赖，这里包含了<code>spring-boot-starter-web</code>用于实现HTTP接口（该依赖中包含了Spring MVC）；<code>spring-boot-starter-test</code>用于编写单元测试的依赖包。更多功能模块的使用我们将在后面的教程中逐步展开。</li>
<li>build：构建配置部分。默认使用了<code>spring-boot-maven-plugin</code>，配合<code>spring-boot-starter-parent</code>就可以把Spring Boot应用打包成JAR来直接运行。</li>
</ul>
<h3> 编写一个HTTP接口</h3>
<ul>
<li>创建package命名为com.didispace.web（根据实际情况修改）</li>
<li>创建<code>HelloController</code>类，内容如下：</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>启动主程序，使用PostMan等工具发起请求：<code>http://localhost:8080/hello</code>，可以看到页面返回：Hello World</li>
</ul>
<h3> 编写单元测试用例</h3>
<p>打开的<code>src/test/</code>下的测试入口<code>Chapter11ApplicationTests</code>类。下面编写一个简单的单元测试来模拟http请求，具体如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>使用<code>MockServletContext</code>来构建一个空的<code>WebApplicationContext</code>，这样我们创建的<code>HelloController</code>就可以在<code>@Before</code>函数中创建并传递到<code>MockMvcBuilders.standaloneSetup()</code>函数中。</p>
<p><strong>注意引入下面内容，让status、content、equalTo函数可用</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>至此已完成目标，通过Maven构建了一个空白Spring Boot项目，再通过引入web模块实现了一个简单的请求处理。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter1-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-178.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：工程结构推荐</title>
      <link>https://spring.didispace.com/spring-boot-2/1-3-project-struct.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/1-3-project-struct.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：工程结构推荐</source>
      <description>Spring Boot 2.x基础教程：工程结构推荐 Spring Boot框架本身并没有对工程结构有特别的要求，但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑，尤其是Spring包扫描机制的存在，如果您使用最佳实践的工程结构，可以免去不少特殊的配置工作。 典型示例 以下结构是比较推荐的package组织方式： com +- example +- myproject +- Application.java | +- domain | +- Customer.java | +- CustomerRepository.java | +- service | +- CustomerService.java | +- web | +- CustomerController.java |</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：工程结构推荐</h1>
<p>Spring Boot框架本身并没有对工程结构有特别的要求，但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑，尤其是Spring包扫描机制的存在，如果您使用最佳实践的工程结构，可以免去不少特殊的配置工作。</p>
<h2> 典型示例</h2>
<p>以下结构是比较推荐的package组织方式：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>root package</code>：<code>com.example.myproject</code>，所有的类和其他package都在root package之下。</li>
<li>应用主类：<code>Application.java</code>，该类直接位于<code>root package</code>下。通常我们会在应用主类中做一些框架配置扫描等配置，我们放在root package下可以帮助程序减少手工配置来加载到我们希望被Spring加载的内容</li>
<li><code>com.example.myproject.domain</code>包：用于定义实体映射关系与数据访问相关的接口和实现</li>
<li><code>com.example.myproject.service</code>包：用于编写业务逻辑相关的接口与实现</li>
<li><code>com.example.myproject.web</code>：用于编写Web层相关的实现，比如：Spring MVC的Controller等</li>
</ul>
<p>上面的结构中，<code>root package</code>与应用主类的位置是整个结构的关键。由于应用主类在<code>root package</code>中，所以按照上面的规则定义的所有其他类都处于<code>root package</code>下的其他子包之后。默认情况下，Spring Boot的应用主类会自动扫描<code>root package</code>以及所有子包下的所有类来进行初始化。</p>
<p>什么意思呢？举个例子，假设我们将<code>com.example.myproject.web</code>包与上面所述的<code>root package</code>：<code>com.example.myproject</code>放在同一级，像下面这样：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这个时候，应用主类<code>Application.java</code>在默认情况下就无法扫描到<code>com.example.myproject.web</code>中的Controller定义，就无法初始化Controller中定义的接口。</p>
<h2> 非典型结构下的初始化</h2>
<p>那么如果，我们一定要加载非<code>root package</code>下的内容怎么办呢？</p>
<p><strong>方法一</strong>：使用<code>@ComponentScan</code>注解指定具体的加载包，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方法通过注解直接指定要扫描的包，比较直观。如果有这样的需求也是可以用的，但是原则上还是推荐以上面的典型结构来定义，这样也可以少写一些注解，代码更加简洁。</p>
<p><strong>方法二</strong>：使用<code>@Bean</code>注解来初始化，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方法在业务开发的时候并不是特别推荐，更适合用于框架封装等场景，关于更多封装上的技巧，后面我们在进阶教程中详细讲解。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本教程配套仓库：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
    </item>
    <item>
      <title>为什么加了@Transactional注解，事务没有回滚？</title>
      <link>https://spring.didispace.com/spring-boot-2/10-1.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-1.html</guid>
      <source url="https://spring.didispace.com/rss.xml">为什么加了@Transactional注解，事务没有回滚？</source>
      <description>为什么加了@Transactional注解，事务没有回滚？ 在昨天的《事务管理入门》一文发布之后，有读者联系说根据文章尝试，加了@Transactional注解之后，事务并没有回滚。 经过一顿沟通排查之后，找到了原因，在此记录一下，给后面如果碰到类似问题的童鞋一个参考。 问题原因</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 为什么加了@Transactional注解，事务没有回滚？</h1>
<p>在昨天的<a href="https://blog.didispace.com/spring-boot-learning-21-3-10/" target="_blank" rel="noopener noreferrer">《事务管理入门》</a>一文发布之后，有读者联系说根据文章尝试，加了<code>@Transactional</code>注解之后，事务并没有回滚。</p>
<p>经过一顿沟通排查之后，找到了原因，在此记录一下，给后面如果碰到类似问题的童鞋一个参考。</p>
<h2> 问题原因</h2>
<p>在前文的描述中，我漏了一个细节，其实在示例代码中，与之前拿的基础例子在配置中有一个关键属性没有提到，就是下面这个配置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这里的<code>spring.jpa.database-platform</code>配置主要用来设置hibernate使用的方言。这里特地采用了<code>MySQL5InnoDBDialect</code>，主要为了保障在使用Spring Data JPA时候，Hibernate自动创建表的时候使用InnoDB存储引擎，不然就会以默认存储引擎MyISAM来建表，而MyISAM存储引擎是没有事务的。</p>
<p>所以，如果你的事务没有生效，那么可以看看创建的表，是不是使用了MyISAM存储引擎，如果是的话，那就是这个原因了！</p>
<h2> 扩展阅读</h2>
<p><code>@Transactional</code>注解不生效，是Spring使用者非常常见的一类问题，上面我们讲了一种，其他还有一些可能的原因，这里作为扩展阅读一并列出。</p>
<p>如果你当前碰到的原因不是上面的情况，那就看看下面这几种情况是否存在：</p>
<ol>
<li>
<p><code>@Transactional</code>注解修饰的函数中<code>catch</code>了异常，并没有往方法外抛。不过，也有一写复杂场景可能不一样，比如我这里出的四个题中的test4：<a href="https://blog.didispace.com/will-this-transcation-rollback/" target="_blank" rel="noopener noreferrer">我来出个题：这个事务会不会回滚？</a></p>
</li>
<li>
<p><code>@Transactional</code>注解修饰的函数不是<code>public</code>类型</p>
</li>
<li>
<p>异常类型错误，如果有通过rollbackFor指定回滚的异常类型，那么抛出的异常与指定的是否一致。</p>
</li>
<li>
<p>数据源没有配置事务管理器</p>
</li>
<li>
<p>在一个类中调用自己的方法。建议分开写，互相调用。</p>
</li>
<li>
<p>对应数据库使用的存储引擎不支持事务，比如：MyISAM。</p>
</li>
</ol>
<p>如果你想与更多有趣的灵魂碰撞，也可以加入我们的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">技术交流群</a>一起探讨我们的技术人生！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Request header is too large 如何解决？</title>
      <link>https://spring.didispace.com/spring-boot-2/10-2.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-2.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Request header is too large 如何解决？</source>
      <description>Request header is too large 如何解决？ 今天看到群里有小伙伴问，这个异常要怎么解决： java.lang.IllegalArgumentException: Request header is too large</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Request header is too large 如何解决？</h1>
<p>今天看到群里有小伙伴问，这个异常要怎么解决：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 异常原因</h3>
<p>根据Exception Message<code>Request header is too large</code>，就可以判断这个错误原因是HTTP请求头过大导致的。</p>
<h3> 如何解决</h3>
<p>解决方法主要两个方向：</p>
<p><strong>方向一： 配置应用服务器使其允许的最大值 &gt; 你实用实用的请求头数据大小</strong></p>
<p>如果用Spring Boot的话，只需要在配置文件里配置这个参数即可：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>方向二：规避请求头过大的情况</strong></p>
<p>虽然上面的配置可以在解决，但是如果无节制的使用header部分，那么这个参数就会变得不可控。</p>
<p>对于请求头部分的数据其实本身并不建议放太大的数据，所以，还是建议把这些数据放到body里更为合理。</p>
<p>那么当你碰到这个异常的时候，是使用什么方法来解决的呢？Spring 开发遇到问题？<a href="https://spring4all.com/" target="_blank" rel="noopener noreferrer">来这里提问吧，有问必答！</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.0新特性：配置绑定 2.0 全解析</title>
      <link>https://spring.didispace.com/spring-boot-2/10-3.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-3.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.0新特性：配置绑定 2.0 全解析</source>
      <description>Spring Boot 2.0新特性：配置绑定 2.0 全解析 在Spring Boot 2.0中推出了Relaxed Binding 2.0，对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。 配置文件绑定 简单类型 在Spring Boot 2.0中对配置属性加载的时候会除了像1.x版本时候那样移除特殊字符外，还会将配置均以全小写的方式进行匹配和加载。所以，下面的4种配置方式都是等价的：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.0新特性：配置绑定 2.0 全解析</h1>
<blockquote>
<p>在Spring Boot 2.0中推出了Relaxed Binding 2.0，对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。</p>
</blockquote>
<h2> 配置文件绑定</h2>
<h3> 简单类型</h3>
<p>在Spring Boot 2.0中对配置属性加载的时候会除了像1.x版本时候那样<strong>移除特殊字符</strong>外，还会将配置均以<strong>全小写</strong>的方式进行匹配和加载。所以，下面的4种配置方式都是等价的：</p>
<ul>
<li>properties格式：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>yaml格式：</li>
</ul>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Tips：推荐使用全小写配合<code>-</code>分隔符的方式来配置，比如：<code>spring.jpa.database-platform=mysql</code></strong></p>
<h3> List类型</h3>
<p>在properties文件中使用<code>[]</code>来定位列表类型，比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>也支持使用<strong>逗号</strong>分割的配置方式，上面与下面的配置是等价的：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>而在yaml文件中使用可以使用如下配置：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>也支持<strong>逗号</strong>分割的方式：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：在Spring Boot 2.0中对于List类型的配置必须是连续的，不然会抛出<code>UnboundConfigurationPropertiesException</code>异常，所以如下配置是不允许的：</strong></p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>在Spring Boot 1.x中上述配置是可以的，<code>foo[1]</code>由于没有配置，它的值会是<code>null</code></strong></p>
<h3> Map类型</h3>
<p>Map类型在properties和yaml中的标准配置方式如下：</p>
<ul>
<li>properties格式：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>yaml格式：</li>
</ul>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：如果Map类型的key包含非字母数字和<code>-</code>的字符，需要用<code>[]</code>括起来，比如：</strong></p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 环境属性绑定</h2>
<p><strong>简单类型</strong></p>
<p>在环境变量中通过小写转换与<code>.</code>替换<code>_</code>来映射配置文件中的内容，比如：环境变量<code>SPRING_JPA_DATABASEPLATFORM=mysql</code>的配置会产生与在配置文件中设置<code>spring.jpa.databaseplatform=mysql</code>一样的效果。</p>
<p><strong>List类型</strong></p>
<p>由于环境变量中无法使用<code>[</code>和<code>]</code>符号，所以使用<code>_</code>来替代。任何由下划线包围的数字都会被认为是<code>[]</code>的数组形式。比如：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外，最后环境变量最后是以数字和下划线结尾的话，最后的下划线可以省略，比如上面例子中的第一条和第三条等价于下面的配置：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h2> 系统属性绑定</h2>
<p><strong>简单类型</strong></p>
<p>系统属性与文件配置中的类似，都以移除特殊字符并转化小写后实现绑定，比如下面的命令行参数都会实现配置<code>spring.jpa.databaseplatform=mysql</code>的效果：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>List类型</strong></p>
<p>系统属性的绑定也与文件属性的绑定类似，通过<code>[]</code>来标示，比如：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>同样的，他也支持逗号分割的方式，比如：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 属性的读取</h2>
<p>上文介绍了Spring Boot 2.0中对属性绑定的内容，可以看到对于一个属性我们可以有多种不同的表达，但是如果我们要在Spring应用程序的environment中读取属性的时候，每个属性的唯一名称符合如下规则：</p>
<ul>
<li>通过<code>.</code>分离各个元素</li>
<li>最后一个<code>.</code>将前缀与属性名称分开</li>
<li>必须是字母（a-z）和数字(0-9)</li>
<li>必须是小写字母</li>
<li>用连字符<code>-</code>来分隔单词</li>
<li>唯一允许的其他字符是<code>[</code>和<code>]</code>，用于List的索引</li>
<li>不能以数字开头</li>
</ul>
<p>所以，如果我们要读取配置文件中<code>spring.jpa.database-platform</code>的配置，可以这样写：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>而下面的方式是无法获取到<code>spring.jpa.database-platform</code>配置内容的：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>注意：使用<code>@Value</code>获取配置内容的时候也需要这样的特点</strong></p>
<h2> 全新的绑定API</h2>
<p>在Spring Boot 2.0中增加了新的绑定API来帮助我们更容易的获取配置信息。下面举个例子来帮助大家更容易的理解：</p>
<p><strong>例子一：简单类型</strong></p>
<p>假设在propertes配置中有这样一个配置：<code>com.didispace.foo=bar</code></p>
<p>我们为它创建对应的配置类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来，通过最新的<code>Binder</code>就可以这样来拿配置信息了：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>例子二：List类型</strong></p>
<p>如果配置内容是List类型呢？比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要获取这些配置依然很简单，可以这样实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>Chapter2-2-1</code>目录：</p>
<ul>
<li><a href="https://github.com/dyc87112/SpringBoot-Learning" target="_blank" rel="noopener noreferrer">Github：https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li><a href="https://gitee.com/didispace/SpringBoot-Learning" target="_blank" rel="noopener noreferrer">Gitee：https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>Spring Booot 2.0 新特性详解正在连载，<a href="https://blog.didispace.com/Spring-Boot-2-0-feature/" target="_blank" rel="noopener noreferrer">点击看看都有哪些解读</a></strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.0新特性：新增事件ApplicationStartedEvent</title>
      <link>https://spring.didispace.com/spring-boot-2/10-4.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-4.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.0新特性：新增事件ApplicationStartedEvent</source>
      <description>Spring Boot 2.0新特性：新增事件ApplicationStartedEvent 今天继续来聊Spring Boot 2.0的新特性。本文将具体说说2.0版本中的事件模型，尤其是新增的事件：ApplicationStartedEvent。 在Spring Boot 2.0中对事件模型做了一些增强，主要就是增加了ApplicationStartedEvent事件，所以在2.0版本中所有的事件按执行的先后顺序如下： ApplicationStartingEvent ApplicationEnvironmentPreparedEvent ApplicationPreparedEvent ApplicationStartedEvent &amp;lt;= 新增的事件 ApplicationReadyEvent ApplicationFailedEvent</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.0新特性：新增事件ApplicationStartedEvent</h1>
<blockquote>
<p>今天继续来聊Spring Boot 2.0的新特性。本文将具体说说2.0版本中的事件模型，尤其是新增的事件：<code>ApplicationStartedEvent</code>。</p>
</blockquote>
<p>在Spring Boot 2.0中对事件模型做了一些增强，主要就是增加了<code>ApplicationStartedEvent</code>事件，所以在2.0版本中所有的事件按执行的先后顺序如下：</p>
<ul>
<li><code>ApplicationStartingEvent</code></li>
<li><code>ApplicationEnvironmentPreparedEvent</code></li>
<li><code>ApplicationPreparedEvent</code></li>
<li><code>ApplicationStartedEvent</code> &lt;= 新增的事件</li>
<li><code>ApplicationReadyEvent</code></li>
<li><code>ApplicationFailedEvent</code></li>
</ul>
<p>从上面的列表中，我们可以看到<code>ApplicationStartedEvent</code>位于<code>ApplicationPreparedEvent</code>之后，<code>ApplicationReadyEvent</code>之前。</p>
<p>下面我们通过代码的方式来直观的感受这个事件的切入位置，以便与将来我们在这个切入点加入自己需要的逻辑。</p>
<p>第一步：我们可以编写<code>ApplicationPreparedEvent</code>、<code>ApplicationStartedEvent</code>以及<code>ApplicationReadyEvent</code>三个事件的监听器，然后在这三个事件触发的时候打印一些日志来观察它们各自的切入点，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二步：在<code>/src/main/resources/</code>目录下新建：<code>META-INF/spring.factories</code>配置文件，通过配置<code>org.springframework.context.ApplicationListener</code>来加载上面我们编写的监听器。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时，我们运行Spring Boot应用可以获得类似如下日志输出：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>从日志中我们可以看到清晰的看到<code>ApplicationPreparedEvent</code>、<code>ApplicationStartedEvent</code>以及<code>ApplicationReadyEvent</code>三个事件的切入点。通过这个例子可能读者会感到疑问：<code>ApplicationStartedEvent</code>和<code>ApplicationReadyEvent</code>从事件命名和日志输出位置来看，都是应用加载完成之后的事件，它们是否有什么区别呢？</p>
<p>下面可以看看官方文档对<code>ApplicationStartedEvent</code>和<code>ApplicationReadyEvent</code>的解释：</p>
<blockquote>
<p>An ApplicationStartedEvent is sent after the context has been refreshed but before any application and command-line runners have been called.An ApplicationReadyEvent is sent after any application and command-line runners have been called. It indicates that the application is ready to service requests</p>
</blockquote>
<p>从文档中我们可以知道他们两中间还有一个过程就是<code>command-line runners</code>被调用的内容。所以，为了更准确的感受这两个事件的区别，我们在应用主类中加入<code>CommandLineRunner</code>的实现，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，我们再运行程序，此时我们可以发现这两个事件中间输出了上面定义的<code>DataLoader</code>的输出内容，具体如下：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>Chapter1-2-1</code>目录：</p>
<ul>
<li><a href="https://github.com/dyc87112/SpringBoot-Learning" target="_blank" rel="noopener noreferrer">Github：https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li><a href="https://gitee.com/didispace/SpringBoot-Learning" target="_blank" rel="noopener noreferrer">Gitee：https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>Spring Booot 2.0 新特性详解正在连载，<a href="https://blog.didispace.com/Spring-Boot-2-0-feature/" target="_blank" rel="noopener noreferrer">点击看看都有哪些解读</a></strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x 到 3.2 的升级指南</title>
      <link>https://spring.didispace.com/spring-boot-2/10-5.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-5.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x 到 3.2 的升级指南</source>
      <description>Spring Boot 2.x 到 3.2 的升级指南 Spring Framework 是一种流行的开源企业级框架，用于创建在 Java Virtual Machine (JVM) 上运行的独立、生产级应用程序。而Spring Boot 是一个工具，可以让使用 Spring 框架更快、更轻松地开发 Web 应用程序和微服务。随着 Spring Boot 的不断发展，开发人员必须跟上最新的升级和变化。 最近，Spring Boot 宣布发布 3.2.x 版本，该版本带来了多项新功能、错误修复和增强功能，鉴于对 Spring Boot 2.7.x 版本的支持已于 2023 年 11 月 18 日结束，这是一个非常重要且强制性的关注用于将 Spring Boot 应用程序升级到最新的 3.x 版本。</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 29 Dec 2023 04:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x 到 3.2 的升级指南</h1>
<p>Spring Framework 是一种流行的开源企业级框架，用于创建在 Java Virtual Machine (JVM) 上运行的独立、生产级应用程序。而Spring Boot 是一个工具，可以让使用 Spring 框架更快、更轻松地开发 Web 应用程序和微服务。随着 Spring Boot 的不断发展，开发人员必须跟上最新的升级和变化。</p>
<p>最近，Spring Boot 宣布发布 3.2.x 版本，该版本带来了多项新功能、错误修复和增强功能，鉴于对 Spring Boot 2.7.x 版本的支持已于 2023 年 11 月 18 日结束，这是一个非常重要且强制性的关注用于将 Spring Boot 应用程序升级到最新的 3.x 版本。</p>
<p>因此，在本文中，我们将讨论如何从 Spring Boot 2.x 迁移到 3.x，以及升级的优势以及开发人员在此过程中可能遇到的潜在困难。</p>
<h2> 升级指南</h2>
<h3> 1. 升级 JDK 17</h3>
<p>Spring Boot 3.0 需要 Java 17 作为最低版本。</p>
<p>如果您当前使用的是 Java 8 或 Java 11，则需要在 Spring Boot 迁移之前升级 JDK。</p>
<h3> 2. 升级到 Spring Boot 3</h3>
<p>查看项目及其依赖项的状态后，请升级到 Spring Boot 3.0 的最新维护版本。</p>
<p>我们将使用 Spring Boot 3.2.0 进行升级。</p>
<p>打开项目的 <code>pom.xml</code> 并更新 Spring Boot 的版本，如下所示。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 3. 配置属性迁移</h3>
<p>在 Spring Boot 3.0 中，一些配置属性被重命名/删除，开发人员需要相应地更新其 <code>application.properties/application.yml</code>。</p>
<p>为了帮助您实现这一点，Spring Boot 提供了一个 <code>spring-boot-properties-migrator</code> 模块。</p>
<p>我们可以通过将以下内容添加到 Maven <code>pom.xml</code> 来添加迁移器：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 4. 升级到 Jakarta EE</h3>
<p>由于 Java EE 已更改为 Jakarta EE，Spring Boot 3.x 的所有依赖项 API 也从 Java EE 升级为 Jakarta EE。</p>
<p>简单来说，您需要将所有 <code>javax</code> 的 imports 都替换为 <code>jakarta</code>。具体如下：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 5. 调整<code>@ConstructorBinding</code>注解</h3>
<p><code>@ConstructorBinding</code> 在 <code>@ConfigurationProperties</code> 类的类型级别不再需要，应将其删除。</p>
<p>当一个类或记录有多个构造函数时，它仍然可以在构造函数上使用，以指示应使用哪一个构造函数进行属性绑定。</p>
<h3> 6. Spring MVC 和 WebFlux的URL匹配更改</h3>
<p>从 Spring Framework 6.0 开始，尾部斜杠匹配配置选项已为 deprecated，其默认值设置为 false。</p>
<p>这意味着以前，以下控制器将匹配<code>GET /health</code>和<code>GET /health/</code></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 7. RestTemplate 中的 Apache HttpClient</h3>
<p>Spring Framework 6.0 中已删除对 Apache HttpClient 的支持，现在由 <code>org.apache.httpcomponents.client5:httpclient5</code> 取代（注意：此依赖项具有不同的 groupId）。</p>
<p>如果您注意到 HTTP 客户端行为存在问题，则 <code>RestTemplate</code> 可能会回退到 JDK 客户端。</p>
<p><code>org.apache.httpcomponents:httpclient</code> 可以由其他依赖项传递传递，因此您的应用程序可能依赖此依赖项而不声明它。</p>
<p>下面是迁移后的<code>RestTemplate</code>示例：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 8. 升级 Spring Security</h3>
<p>Spring Boot 3.0 已升级到 Spring Security 6.0。</p>
<p>因此，<code>WebSecurityConfigurerAdapter</code> 已被弃用。 Spring鼓励用户转向基于组件的安全配置。</p>
<p>为了演示新的配置风格，我们使用 Spring Security lambda DSL 和方法 <code>HttpSecurity#authorizeHttpRequests</code> 来定义我们的授权规则。</p>
<p>下面是使用 <code>WebSecurityConfigurerAdapter</code> 的示例配置，它通过 HTTP Basic 保护所有端点：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>展望未来，推荐的方法是注册一个 <code>SecurityFilterChain</code> bean：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 9. Spring Kafka 模板升级</h3>
<p><code>KafkaTemplate</code> 方法现在返回 <code>CompleteableFuture</code> 而不是 <code>ListenableFuture</code>，后者已被弃用。</p>
<p>Spring Boot 2.x 中带有 <code>ListenableFuture</code> 的 Kafka 模板:</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Spring Boot 3.x 中带有 <code>CompletableFuture</code> 的 Kafka 模板：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 10. Spring Doc OpenAPI 升级</h3>
<p><code>springdoc-openapi</code>用于为Spring Boot 项目自动生成 API 文档。</p>
<p><code>springdoc-openapi</code>的工作原理是在运行时检查应用程序，以根据 spring 配置、类结构和各种注释推断 API 语义。</p>
<p>对于 spring-boot 3 支持，请确保使用 springdoc-openapi v2。</p>
<h4> WebMVC 项目的 Spring Doc OpenAPI 升级</h4>
<p>对于 WebMVC 项目，您需要在 <code>pom.xml</code>. 文件中包含以下依赖项。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> WebFlux 项目的 Spring Doc OpenAPI 升级</h4>
<p>对于 WebFlux 项目，您需要在 <code>pom.xml</code>. 文件中包含以下依赖项。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>今日分享就到这里，感谢阅读！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="/spring-boot-2/" target="blank">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot整合Camunda实现工作流</title>
      <link>https://spring.didispace.com/spring-boot-2/10-6.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-6.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot整合Camunda实现工作流</source>
      <description>Spring Boot整合Camunda实现工作流 工作流是我们开发企业应用几乎必备的一项功能，工作流引擎发展至今已经有非常多的产品。最近正好在接触Camunda，所以来做个简单的入门整合介绍。如果您也刚好在调研或者刚开始计划接入，希望本文对您有所帮助。如果您是一名Java开发或Spring框架爱好者，欢迎关注我程序猿DD，持续非常技术干货。 Camunda简介</description>
      <category>Spring Boot</category>
      <pubDate>Fri, 12 Jan 2024 13:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot整合Camunda实现工作流</h1>
<p>工作流是我们开发企业应用几乎必备的一项功能，工作流引擎发展至今已经有非常多的产品。最近正好在接触<code>Camunda</code>，所以来做个简单的入门整合介绍。如果您也刚好在调研或者刚开始计划接入，希望本文对您有所帮助。如果您是一名Java开发或Spring框架爱好者，欢迎关注我<a href="https://www.didispace.com/" target="_blank" rel="noopener noreferrer">程序猿DD</a>，持续非常技术干货。</p>
<h2> Camunda简介</h2>
<p><img src="https://static.didispace.com/images3/4fc5ec094ea0807fca26a744ec711063.png" alt=""></p>
<p>Camunda是一个灵活的工作流和流程自动化框架。其核心是一个运行在Java虚拟机内部的原生BPMN 2.0流程引擎。它可以嵌入到任何Java应用程序和任何运行时容器中。</p>
<ul>
<li>官网网站: https://www.camunda.org/</li>
<li>入门文档: https://docs.camunda.org/get-started/</li>
</ul>
<h2> 动手整合Camunda</h2>
<p>下面就来一步步动手尝试一下吧。</p>
<h3> 准备工作</h3>
<ol>
<li>使用Camunda提供的项目初始化工具<a href="https://start.camunda.com/" target="_blank" rel="noopener noreferrer">Camunda Automation Platform 7 Initializr</a></li>
</ol>
<p><img src="https://static.didispace.com/images3/fb2c472158d84487acc07a43abee7eb0.png" alt=""></p>
<p>如上图，包名之类的根据自己需要做好配置，最后输入管理账号和密码，点击<code>Generate Project</code>按钮，自动下载工程。</p>
<ol start="2">
<li>解压下载后的工程，使用<a href="https://www.didispace.com/idea-tips/performance/m1-performance.html" target="_blank" rel="noopener noreferrer">IntelliJ IDEA</a>打开，其项目结构</li>
</ol>
<p><img src="https://static.didispace.com/images3/7df6da30d097c1765c739ec7d994d929.png" alt=""></p>
<ol start="3">
<li>打开<code>pom.xml</code>文件，添加camunda依赖：</li>
</ol>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>由于Camunda Automation Platform 7 Initializr默认的Spring Boot版本已经是3.1了，所以如果要做一些降级调整，可以手工修改<code>pom.xml</code>中<code>dependencyManagement</code>配置，比如下面这样：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>打开配置文件<code>application.yaml</code>，可以看到类似下面的内容</li>
</ol>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>spring.datasource.url</code>：工作流引擎使用的数据库配置，您也可以根据官网文档去调整到其他数据库中（尤其生产环境）。</li>
<li><code>camunda.bpm.admin-user</code>：管理员账户配置，可以在这里修改用户名和密码</li>
</ul>
<h3> 创建一个简单的工作流</h3>
<p>下面我们尝试创建一个简单的工作流：</p>
<p>第一步，我们将请求用户提供两个输入：name和message
第二步，我们将这些输入传递给我们的服务以创建消息输出</p>
<p>开始编码：</p>
<ol>
<li>创建第一步提到的数据模型</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="2">
<li>根据第二步，创建接收消息的接口</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="3">
<li>下面我们可以开始创建工作流程图。在<a href="https://camunda.com/download/modeler/" target="_blank" rel="noopener noreferrer">Camunda Modeler</a>中打开我们项目的<code>resources</code>下的<code>process.bpmn</code>，我们将看到类似下面的流程图：</li>
</ol>
<p><img src="https://static.didispace.com/images3/86cc37c2f1a2b8f78eb293468196178b.png" alt=""></p>
<p>图中带有小人的框称为<code>User Tasks</code>，是执行与用户相关的操作的步骤。如前面部分所述，在工作流程的第一步中，我们将请求用户输入两个输入：姓名和消息。无需添加新任务，更新现有的<code>User Tasks</code>即可解决问题。单击<code>User Tasks</code>，打开属性面板，在打开的面板中定义适合我们案例内容。</p>
<p><img src="https://static.didispace.com/images3/1dbfc77cf55a6d629e319a8dfe76e61c.png" alt=""></p>
<ol start="4">
<li>完成基本信息填写后，转到<code>Form</code>选项卡。</li>
</ol>
<p><img src="https://static.didispace.com/images3/901eced0a6602724cbc8a1756a148051.png" alt=""></p>
<p>这是定义呈现给用户的表单选项卡。由于我们需要用户输入姓名和消息，因此我们定义两个名为“name”和“message”的表单字段。要定义表单字段，请单击“表单字段”旁边的加号图标。在打开的表单中，相应地填写 ID、类型和标签字段。对每个表单字段重复相同的步骤。</p>
<ol start="5">
<li>开始配置第二步，调用我们的接口。添加<code>Service Task</code>。</li>
</ol>
<p>具体操作方法：单击左侧菜单中的<code>Create Task</code>图标，然后将任务拖放到随机位置。单击任务后，单击<code>Change Type</code>图标，然后从菜单中选择<code>Service Task</code>。</p>
<p><img src="https://static.didispace.com/images3/65fdd64a816cd7fc2a02509375e7d40b.png" alt=""></p>
<ol start="6">
<li>填写基本信息</li>
</ol>
<p><img src="https://static.didispace.com/images3/66daedc605814dd2b06dba13199d232d.png" alt=""></p>
<ol start="7">
<li>切换到<code>Connector</code>选项卡。这是定义 HTTP 信息和有关服务的数据的选项卡，在这里配置刚才定义的接口，具体如下图所示：</li>
</ol>
<p><img src="https://static.didispace.com/images3/6691fb6aa5da51204a1d42d01bd731bb.png" alt=""></p>
<ol start="8">
<li>将<code>Service Task</code>连接到工作流程中。先删除<code>User Tasks</code>和<code>End Event</code>之间的箭头。然后，单击<code>User Tasks</code>并从菜单中选择箭头图标。将箭头连接到<code>Service Task</code>。最后，再连接<code>Service Task</code>和<code>End Event</code>。</li>
</ol>
<p><img src="https://static.didispace.com/images3/a72d16766d9cf66f3355041497570410.png" alt=""></p>
<h3> 启动测试</h3>
<p>在完成了上面的编码和工作流程配置后，我们就可以在调试模式下运行项目了。</p>
<p>启动完成后，在浏览器上访问地址<code>http://localhost:8080/</code>，您将看到 Camunda 登录页面：</p>
<p><img src="https://static.didispace.com/images3/e77bc3f1cf5b1049534509d453d61881.png" alt=""></p>
<p>输入您在<code>application.yaml</code>中配置的管理员配置信息，进入后台：</p>
<p><img src="https://static.didispace.com/images3/caad9bf31591359f024cbdbe57685068.png" alt=""></p>
<p>从应用程序主页中选择<code>Tasklist</code>，可看到如下界面：</p>
<p><img src="https://static.didispace.com/images3/1a4342c6ec332faae5131f91fd92df75.png" alt=""></p>
<p>然后在任务列表页面上单击<code>Add a simple filter</code>选项。单击后，您将看到名为<code>All Tasks (0)</code>的过滤器已添加到列表中，继续单击<code>Start process</code>选项来运行我们准备好的工作流程。</p>
<p>选择您的工作流进程，然后单击<code>Start button</code>，无需提供任何其他信息。</p>
<p><img src="https://static.didispace.com/images3/d5111d0daf04c2c0a0fc6cbdab00697b.png" alt=""></p>
<p>最后，单击<code>Created</code>下列出的<code>Get Input</code>任务。如果您没有看到该任务，请刷新页面。</p>
<p><img src="https://static.didispace.com/images3/85aa338fa3086b72a91a64785cd478f5.png" alt=""></p>
<p>您将看到我们在第一步中定义的表单。要填写表格，请单击右上角<code>Claim</code>选项。然后，根据您的喜好填写表格并单击<code>Complete</code>按钮。</p>
<p><img src="https://static.didispace.com/images3/e64cbdfead8f3096eac28da607561683.png" alt=""></p>
<p>当工作流执行<code>Service Task</code>并且服务运行时，您将看到列表再次变空。如果工作流成功执行了第二步，我们应该能够在控制台中看到输出。</p>
<h2> 小结</h2>
<p>本文介绍了使用Spring Boot和Camunda创建一个简单工作流的完整步骤，希望对您有所帮助。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/4fc5ec094ea0807fca26a744ec711063.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot整合Postgres实现轻量级全文搜索</title>
      <link>https://spring.didispace.com/spring-boot-2/10-7.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/10-7.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot整合Postgres实现轻量级全文搜索</source>
      <description>Spring Boot整合Postgres实现轻量级全文搜索 有这样一个带有搜索功能的用户界面需求： 搜索流程如下所示： 这个需求涉及两个实体：</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 19 Feb 2024 13:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot整合Postgres实现轻量级全文搜索</h1>
<p>有这样一个带有搜索功能的用户界面需求：</p>
<p><img src="https://static.didispace.com/images3/bf50bfd4773f969b74f6a158fcd9761c.png" alt=""></p>
<p>搜索流程如下所示：</p>
<p><img src="https://static.didispace.com/images3/4f8d3daceb85f1539f27b04bc7464c3f.png" alt=""></p>
<p>这个需求涉及两个实体：</p>
<ul>
<li>“评分（Rating）、用户名（Username）”数据与<code>User</code>实体相关</li>
<li>“创建日期（create date）、观看次数（number of views）、标题（title）、正文（body）”与<code>Story</code>实体相关</li>
</ul>
<p>需要支持的功能对<code>User</code>实体中的评分（Rating）的频繁修改以及下列搜索功能：</p>
<ul>
<li>按User评分进行范围搜索</li>
<li>按Story创建日期进行范围搜索</li>
<li>按Story浏览量进行范围搜索</li>
<li>按Story标题进行全文搜索</li>
<li>按Story正文进行全文搜索</li>
</ul>
<h2> Postgres中创建表结构和索引</h2>
<p>创建<code>users</code>表和<code>stories</code>表以及对应搜索需求相关的索引，包括：</p>
<ul>
<li>使用 btree 索引来支持按User评分搜索</li>
<li>使用 btree 索引来支持按Story创建日期、查看次数的搜索</li>
<li>使用 gin 索引来支持全文搜索内容（同时创建全文搜索列<code>fulltext</code>，类型使用<code>tsvector</code>以支持全文搜索）</li>
</ul>
<p>具体创建脚本如下：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 创建Spring Boot应用</h2>
<ol>
<li>项目依赖关系（这里使用Gradle构建）：</li>
</ol>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="2">
<li><code>application.yaml</code>中配置数据库连接信息</li>
</ol>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="3">
<li>数据模型</li>
</ol>
<p>定义需要用到的各种数据模型：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里使用<a href="https://www.didispace.com/java-features/java16/jep395-records.html" target="_blank" rel="noopener noreferrer">Java 16推出的新特性record</a>实现，所以代码非常简洁。如果您还不了解的话，可以前往<a href="https://www.didispace.com/java-features/java16/jep395-records.html" target="_blank" rel="noopener noreferrer">程序猿DD的Java新特性专栏</a>补全一下知识点。</p>
<ol start="4">
<li>数据访问（Repository）</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="5">
<li>Controller实现</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 小结</h2>
<p>本文介绍了如何在Spring Boot中结合Postgres数据库实现全文搜索的功能，该方法比起使用Elasticsearch更为轻量级，非常适合一些小项目场景使用。希望本文内容对您有所帮助。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<h2> 参考资料</h2>
<ul>
<li><a href="https://medium.com/@svosh2/postgres-full-text-search-spring-boot-integration-13cbeb7af570" target="_blank" rel="noopener noreferrer">Postgres full-text search Spring boot integration</a></li>
<li><a href="https://www.didispace.com/java-features/java16/jep395-records.html" target="_blank" rel="noopener noreferrer">Java 16新特性：record</a></li>
</ul>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/bf50bfd4773f969b74f6a158fcd9761c.png" type="image/png"/>
    </item>
    <item>
      <title>使用JavaMailSender发送邮件</title>
      <link>https://spring.didispace.com/spring-boot-2/11-1.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/11-1.html</guid>
      <source url="https://spring.didispace.com/rss.xml">使用JavaMailSender发送邮件</source>
      <description>使用JavaMailSender发送邮件 相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。下面通过实例看看如何在Spring Boot中使用JavaMailSender发送邮件。 快速入门 在Spring Boot的工程中的pom.xml中引入spring-boot-starter-mail依赖：</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 09 Oct 2023 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 使用JavaMailSender发送邮件</h1>
<p>相信使用过Spring的众多开发者都知道Spring提供了非常好用的<code>JavaMailSender</code>接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。下面通过实例看看如何在Spring Boot中使用<code>JavaMailSender</code>发送邮件。</p>
<h2> 快速入门</h2>
<p>在Spring Boot的工程中的<code>pom.xml</code>中引入<code>spring-boot-starter-mail</code>依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如其他自动化配置模块一样，在完成了依赖引入之后，只需要在<code>application.properties</code>中配置相应的属性内容。</p>
<p>下面我们以QQ邮箱为例，在<code>application.properties</code>中加入如下配置（注意替换自己的用户名和密码）：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过单元测试来实现一封简单邮件的发送：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>到这里，一个简单的邮件发送就完成了，运行一下该单元测试，看看效果如何？</p>
<blockquote>
<p>由于Spring Boot的starter模块提供了自动化配置，所以在引入了<code>spring-boot-starter-mail</code>依赖之后，会根据配置文件中的内容去创建<code>JavaMailSender</code>实例，因此我们可以直接在需要使用的地方直接<code>@Autowired</code>来引入邮件发送对象。</p>
</blockquote>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-5-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot发送带附件的邮件</title>
      <link>https://spring.didispace.com/spring-boot-2/11-2.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/11-2.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot发送带附件的邮件</source>
      <description>Spring Boot发送带附件的邮件 上一篇，我们通过使用SimpleMailMessage实现了简单的邮件发送，但是实际使用过程中，我们还可能会带上附件、或是使用邮件模块等。这个时候我们就需要使用MimeMessage来设置复杂一些的邮件内容，下面我们就来依次实现一下。 发送附件邮件 在上面单元测试中加入如下测试用例（通过MimeMessageHelper来发送一封带有附件的邮件）：</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 09 Oct 2023 18:01:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot发送带附件的邮件</h1>
<p><a href="/spring-boot-2/11-1.html" target="blank">上一篇</a>，我们通过使用<code>SimpleMailMessage</code>实现了简单的邮件发送，但是实际使用过程中，我们还可能会带上附件、或是使用邮件模块等。这个时候我们就需要使用<code>MimeMessage</code>来设置复杂一些的邮件内容，下面我们就来依次实现一下。</p>
<h2> 发送附件邮件</h2>
<p>在上面单元测试中加入如下测试用例（通过MimeMessageHelper来发送一封带有附件的邮件）：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-5-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot发送邮件的时候引入图片等静态资源</title>
      <link>https://spring.didispace.com/spring-boot-2/11-3.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/11-3.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot发送邮件的时候引入图片等静态资源</source>
      <description>Spring Boot发送邮件的时候引入图片等静态资源 有时候我们发送邮件的时候，除了需要添加附件之外，可能还希望通过嵌入图片等静态资源，让邮件获得更好的阅读体验，而不是从附件中查看具体图片。 所以，下面一起来学习一下如何通过MimeMessageHelper实现在邮件正文中嵌入静态资源。 	@Test 	public void sendInlineMail() throws Exception { 		MimeMessage mimeMessage = mailSender.createMimeMessage(); 		MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); 		helper.setFrom(&amp;quot;dyc87112@qq.com&amp;quot;); 		helper.setTo(&amp;quot;dyc87112@qq.com&amp;quot;); 		helper.setSubject(&amp;quot;主题：嵌入静态资源&amp;quot;); 		helper.setText(&amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;img src=\&amp;quot;cid:weixin\&amp;quot; &amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;, true); 		FileSystemResource file = new FileSystemResource(new File(&amp;quot;weixin.jpg&amp;quot;)); 		helper.addInline(&amp;quot;weixin&amp;quot;, file); 		mailSender.send(mimeMessage); 	}</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 09 Oct 2023 18:03:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot发送邮件的时候引入图片等静态资源</h1>
<p>有时候我们发送邮件的时候，除了需要<a href="/spring-boot-2/11-2.html" target="blank">添加附件</a>之外，可能还希望通过嵌入图片等静态资源，让邮件获得更好的阅读体验，而不是从附件中查看具体图片。</p>
<p>所以，下面一起来学习一下如何通过<code>MimeMessageHelper</code>实现在邮件正文中嵌入静态资源。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>这里需要注意的是<code>addInline</code>函数中资源名称<code>weixin</code>需要与正文中<code>cid:weixin</code>对应起来</strong></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-5-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot中使用邮件模版发送邮件</title>
      <link>https://spring.didispace.com/spring-boot-2/11-4.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/11-4.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot中使用邮件模版发送邮件</source>
      <description>Spring Boot中使用邮件模版发送邮件 通常我们使用邮件发送服务的时候，都会有一些固定的场景，比如：重置密码、注册确认等，给每个用户发送的内容可能只有小部分是变化的。 所以，很多时候我们会使用模板引擎来为各类邮件设置成模板，这样我们只需要在发送时去替换变化部分的参数即可。 在Spring Boot中使用模板引擎来实现模板化的邮件发送也是非常容易的，下面我们以velocity为例实现一下。 引入velocity模块的依赖： &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;spring-boot-starter-velocity&amp;lt;/artifactId&amp;gt; &amp;lt;/dependency&amp;gt;</description>
      <category>Spring Boot</category>
      <pubDate>Mon, 09 Oct 2023 18:05:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot中使用邮件模版发送邮件</h1>
<p>通常我们使用邮件发送服务的时候，都会有一些固定的场景，比如：重置密码、注册确认等，给每个用户发送的内容可能只有小部分是变化的。</p>
<p>所以，很多时候我们会使用模板引擎来为各类邮件设置成模板，这样我们只需要在发送时去替换变化部分的参数即可。</p>
<p>在Spring Boot中使用模板引擎来实现模板化的邮件发送也是非常容易的，下面我们以velocity为例实现一下。</p>
<p>引入velocity模块的依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在<code>resources/templates/</code>下，创建一个模板页面<code>template.vm</code>：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>我们之前在Spring Boot中开发Web应用时，提到过在Spring Boot的自动化配置下，模板默认位于<code>resources/templates/</code>目录下</strong></p>
<p>最后，我们在单元测试中加入发送模板邮件的测试用例，具体如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>尝试运行一下，就可以收到内容为<code>你好， didi, 这是一封模板邮件!</code>的邮件。这里，我们通过传入username的参数，在邮件内容中替换了模板中的<code>${username}</code>变量。</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-5-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/1.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot中发送邮件如何让发件人显示别名</title>
      <link>https://spring.didispace.com/spring-boot-2/11-5.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/11-5.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot中发送邮件如何让发件人显示别名</source>
      <description>Spring Boot中发送邮件如何让发件人显示别名 之前，我们通过一系列文章，介绍了如何在Spring Boot中发送邮件： 发送邮件 添加附件 引用静态资源 邮件模版</description>
      <category>Spring Boot</category>
      <pubDate>Wed, 11 Oct 2023 08:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot中发送邮件如何让发件人显示别名</h1>
<p>之前，我们通过一系列文章，介绍了如何在Spring Boot中发送邮件：</p>
<ul>
<li><a href="/spring-boot-2/11-1.html" target="blank">发送邮件</a></li>
<li><a href="/spring-boot-2/11-2.html" target="blank">添加附件</a></li>
<li><a href="/spring-boot-2/11-3.html" target="blank">引用静态资源</a></li>
<li><a href="/spring-boot-2/11-4.html" target="blank">邮件模版</a></li>
</ul>
<p>已经包含了大部分的应用场景。但最近DD在做<a href="https://youtube-dubbing.com/" target="_blank" rel="noopener noreferrer">YouTube中文配音</a>的时候，碰到一个问题：</p>
<p><img src="https://static.didispace.com/images3/0d8ce1f6a6eb4938ec94760087845926.png" alt=""></p>
<p>如上图所示，收件人在客户端收到的时候，显示的名称是邮箱的前缀，而不是我们的产品名称，也就是邮箱别名。</p>
<p>开始一直在从Mail的配置类里寻找相关配置项，结果就下面这些内容：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，并没有关于别名的配置项。那么如何设置发件人的别名呢？</p>
<p>最后才发现，原来是在定义发送内容的时候设置的，具体看看下面这个例子：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果你是组织比较复杂的邮件，使用<code>MimeMessage</code>的话也是一样。在<code>setFrom</code>的时候，像上面这样写就可以了。</p>
<p>今日分享就到这里，感谢阅读！如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="/spring-boot-2/" target="blank">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/0d8ce1f6a6eb4938ec94760087845926.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：配置文件详解</title>
      <link>https://spring.didispace.com/spring-boot-2/2-1-config.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/2-1-config.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：配置文件详解</source>
      <description>Spring Boot 2.x基础教程：配置文件详解 在快速入门一节中，我们轻松的实现了一个简单的RESTful API应用，体验了一下Spring Boot给我们带来的诸多优点，我们用非常少的代码量就成功的实现了一个Web应用，这是传统的Spring应用无法办到的，虽然我们在实现Controller时用到的代码是一样的，但是在配置方面，相信大家也注意到了，在上面的例子中，除了Maven的配置之后，就没有引入任何的配置。 这就是之前我们所提到的，Spring Boot针对我们常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。但是，我们还是需要去了解如何在Spring Boot中修改这些自动化的配置内容，以应对一些特殊的场景需求，比如：我们在同一台主机上需要启动多个基于Spring Boot的web应用，若我们不为每个应用指定特别的端口号，那么默认的8080端口必将导致冲突。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：配置文件详解</h1>
<p>在<a href="/spring-boot-2/1-2-quick-start">快速入门</a>一节中，我们轻松的实现了一个简单的RESTful API应用，体验了一下Spring Boot给我们带来的诸多优点，我们用非常少的代码量就成功的实现了一个Web应用，这是传统的Spring应用无法办到的，虽然我们在实现Controller时用到的代码是一样的，但是在配置方面，相信大家也注意到了，在上面的例子中，除了Maven的配置之后，就没有引入任何的配置。</p>
<p>这就是之前我们所提到的，Spring Boot针对我们常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。但是，我们还是需要去了解如何在Spring Boot中修改这些自动化的配置内容，以应对一些特殊的场景需求，比如：我们在同一台主机上需要启动多个基于Spring Boot的web应用，若我们不为每个应用指定特别的端口号，那么默认的8080端口必将导致冲突。</p>
<p>如果您还有在读我的<a href="https://blog.didispace.com/spring-cloud-learning/" target="_blank" rel="noopener noreferrer">Spring Cloud系列教程</a>，其实有大量的工作都会是针对配置文件的。所以我们有必要深入的了解一些关于Spring Boot中的配置文件的知识，比如：它的配置方式、如何实现多环境配置，配置信息的加载顺序等。</p>
<h2> 配置基础</h2>
<p>在<a href="/spring-boot-2/1-2-quick-start">快速入门</a>示例中，我们介绍Spring Boot的工程结构时，有提到过 <code>src/main/resources</code>目录是Spring Boot的配置目录，所以我们要为应用创建配置个性化配置时，就是在该目录之下。</p>
<p>Spring Boot的默认配置文件位置为： <code>src/main/resources/application.properties</code>。关于Spring Boot应用的配置内容都可以集中在该文件中了，根据我们引入的不同Starter模块，可以在这里定义诸如：容器端口名、数据库链接信息、日志级别等各种配置信息。比如，我们需要自定义web模块的服务端口号，可以在<code>application.properties</code>中添加<code>server.port=8888</code>来指定服务端口为8888，也可以通过<code>spring.application.name=hello</code>来指定应用名（该名字在Spring Cloud应用中会被注册为服务名）。</p>
<p>Spring Boot的配置文件除了可以使用传统的properties文件之外，还支持现在被广泛推荐使用的YAML文件。</p>
<blockquote>
<p>YAML（英语发音：/ˈjæməl/，尾音类似camel骆驼）是一个可读性高，用来表达资料序列的格式。YAML参考了其他多种语言，包括：C语言、Python、Perl，并从XML、电子邮件的数据格式（RFC 2822）中获得灵感。Clark Evans在2001年首次发表了这种语言，另外Ingy döt Net与Oren Ben-Kiki也是这语言的共同设计者。目前已经有数种编程语言或脚本语言支援（或者说解析）这种语言。YAML是"YAML Ain't a Markup Language"（YAML不是一种标记语言）的递回缩写。在开发的这种语言时，YAML 的意思其实是："Yet Another Markup Language"（仍是一种标记语言），但为了强调这种语言以数据做为中心，而不是以标记语言为重点，而用反向缩略语重新命名。AML的语法和其他高阶语言类似，并且可以简单表达清单、散列表，标量等资料形态。它使用空白符号缩排和大量依赖外观的特色，特别适合用来表达或编辑数据结构、各种设定档、倾印除错内容、文件大纲（例如：许多电子邮件标题格式和YAML非常接近）。尽管它比较适合用来表达阶层式（hierarchical model）的数据结构，不过也有精致的语法可以表示关联性（relational model）的资料。由于YAML使用空白字元和分行来分隔资料，使得它特别适合用grep／Python／Perl／Ruby操作。其让人最容易上手的特色是巧妙避开各种封闭符号，如：引号、各种括号等，这些符号在巢状结构时会变得复杂而难以辨认。	—— 维基百科</p>
</blockquote>
<p>YAML采用的配置格式不像properties的配置那样以单纯的键值对形式来表示，而是以类似大纲的缩进形式来表示。比如：下面的一段YAML配置信息</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>与其等价的properties配置如下。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过YAML的配置方式，我们可以看到配置信息利用阶梯化缩进的方式，其结构显得更为清晰易读，同时配置内容的字符量也得到显著的减少。除此之外，YAML还可以在一个单个文件中通过使用<code>spring.profiles</code>属性来定义多个不同的环境配置。例如下面的内容，在指定为test环境时，<code>server.port</code>将使用8882端口；而在prod环境，<code>server.port</code>将使用8883端口；如果没有指定环境，<code>server.port</code>将使用8881端口。</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：YAML目前还有一些不足，它无法通过<code>@PropertySource</code>注解来加载配置。但是，YAML加载属性到内存中保存的时候是有序的，所以当配置文件中的信息需要具备顺序含义时，YAML的配置方式比起properties配置文件更有优势。</strong></p>
<h3> 自定义参数</h3>
<p>我们除了可以在Spring Boot的配置文件中设置各个Starter模块中预定义的配置属性，也可以在配置文件中定义一些我们需要的自定义属性。比如在<code>application.properties</code>中添加：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>然后，在应用中我们可以通过<code>@Value</code>注解来加载这些自定义的参数，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>@Value</code>注解加载属性值的时候可以支持两种表达式来进行配置：</p>
<ul>
<li>一种是我们上面介绍的PlaceHolder方式，格式为&nbsp;<code>${...}&nbsp;</code>，大括号内为PlaceHolder</li>
<li>另外还可以使用SpEL表达式（Spring Expression Language），&nbsp;格式为&nbsp;<code>#{...}&nbsp;</code>，大括号内为SpEL表达式</li>
</ul>
<h3> 参数引用</h3>
<p>在<code>application.properties</code>中的各个参数之间，我们也可以直接通过使用PlaceHolder的方式来进行引用，就像下面的设置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>book.desc</code>参数引用了上文中定义的<code>book.name</code>和<code>book.author</code>属性，最后该属性的值就是<code>ZhaiYongchao  is writing《SpringCloud》</code>。</p>
<h3> 使用随机数</h3>
<p>在一些特殊情况下，有些参数我们希望它每次加载的时候不是一个固定的值，比如：密钥、服务端口等。在Spring Boot的属性配置文件中，我们可以通过使用<code>${random}</code>配置来产生随机的int值、long值或者string字符串，这样我们就可以容易的通过配置来属性的随机生成，而不是在程序中通过编码来实现这些逻辑。</p>
<p><code>${random}</code>的配置方式主要有一下几种，读者可作为参考使用。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>该配置方式可以用于设置应用端口等场景，避免在本地调试时出现端口冲突的麻烦</strong></p>
<h3> 命令行参数</h3>
<p>回顾一下在本章的快速入门中，我们还介绍了如何启动Spring Boot应用，其中提到了使用命令<code>java -jar</code>命令来启动的方式。该命令除了启动应用之外，还可以在命令行中来指定应用的参数，比如：<code>java -jar xxx.jar --server.port=8888</code>，直接以命令行的方式，来设置server.port属性，另启动应用的端口设为8888。</p>
<p>在命令行方式启动Spring Boot应用时，连续的两个减号<code>--</code>就是对<code>application.properties</code>中的属性值进行赋值的标识。所以，<code>java -jar xxx.jar --server.port=8888</code>命令，等价于我们在<code>application.properties</code>中添加属性<code>server.port=8888</code>。</p>
<p>通过命令行来修改属性值是Spring Boot非常重要的一个特性，通过此特性，理论上已经使得我们应用的属性在启动前是可变的，所以其中端口号也好、数据库连接也好，都是可以在应用启动时发生改变，而不同于以往的Spring应用通过Maven的Profile在编译器进行不同环境的构建。其最大的区别就是，Spring Boot的这种方式，可以让应用程序的打包内容，贯穿开发、测试以及线上部署，而Maven不同Profile的方案每个环境所构建的包，其内容本质上是不同的。但是，如果每个参数都需要通过命令行来指定，这显然也不是一个好的方案，所以下面我们看看如果在Spring Boot中实现多环境的配置。</p>
<h3> 多环境配置</h3>
<p>我们在开发任何应用的时候，通常同一套程序会被应用和安装到几个不同的环境，比如：开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同，如果在为不同环境打包时都要频繁修改配置文件的话，那必将是个非常繁琐且容易发生错误的事。</p>
<p>对于多环境的配置，各种项目构建工具或是框架的基本思路是一致的，通过配置多份不同环境的配置文件，再通过打包命令指定需要打包的内容之后进行区分打包，Spring Boot也不例外，或者说更加简单。</p>
<p>在Spring Boot中多环境配置文件名需要满足<code>application-{profile}.properties</code>的格式，其中<code>{profile}</code>对应你的环境标识，比如：</p>
<ul>
<li><code>application-dev.properties</code>：开发环境</li>
<li><code>application-test.properties</code>：测试环境</li>
<li><code>application-prod.properties</code>：生产环境</li>
</ul>
<p>至于哪个具体的配置文件会被加载，需要在<code>application.properties</code>文件中通过<code>spring.profiles.active</code>属性来设置，其值对应配置文件中的<code>{profile}</code>值。如：<code>spring.profiles.active=test</code>就会加载<code>application-test.properties</code>配置文件内容。</p>
<p>下面，以不同环境配置不同的服务端口为例，进行样例实验。</p>
<ul>
<li>
<p>针对各环境新建不同的配置文件<code>application-dev.properties</code>、<code>application-test.properties</code>、<code>application-prod.properties</code></p>
</li>
<li>
<p>在这三个文件均都设置不同的<code>server.port</code>属性，如：dev环境设置为1111，test环境设置为2222，prod环境设置为3333</p>
</li>
<li>
<p>application.properties中设置<code>spring.profiles.active=dev</code>，就是说默认以dev环境设置</p>
</li>
<li>
<p>测试不同配置的加载</p>
</li>
<li>
<p>执行<code>java -jar xxx.jar</code>，可以观察到服务端口被设置为<code>1111</code>，也就是默认的开发环境（dev）</p>
</li>
<li>
<p>执行<code>java -jar xxx.jar --spring.profiles.active=test</code>，可以观察到服务端口被设置为<code>2222</code>，也就是测试环境的配置（test）</p>
</li>
<li>
<p>执行<code>java -jar xxx.jar --spring.profiles.active=prod</code>，可以观察到服务端口被设置为<code>3333</code>，也就是生产环境的配置（prod）</p>
</li>
</ul>
<p>按照上面的实验，可以如下总结多环境的配置思路：</p>
<ul>
<li><code>application.properties</code>中配置通用内容，并设置<code>spring.profiles.active=dev</code>，以开发环境为默认配置</li>
<li><code>application-{profile}.properties</code>中配置各个环境不同的内容</li>
<li>通过命令行方式去激活不同环境的配置</li>
</ul>
<h3> 加载顺序</h3>
<p>在上面的例子中，我们将Spring Boot应用需要的配置内容都放在了项目工程中，虽然我们已经能够通过<code>spring.profiles.active</code>或是通过Maven来实现多环境的支持。但是，当我们的团队逐渐壮大，分工越来越细致之后，往往我们不需要让开发人员知道测试或是生成环境的细节，而是希望由每个环境各自的负责人（QA或是运维）来集中维护这些信息。那么如果还是以这样的方式存储配置内容，对于不同环境配置的修改就不得不去获取工程内容来修改这些配置内容，当应用非常多的时候就变得非常不方便。同时，配置内容都对开发人员可见，本身这也是一种安全隐患。对此，现在出现了很多将配置内容外部化的框架和工具，后续将要介绍的Spring Cloud Config就是其中之一，为了后续能更好的理解Spring Cloud Config的加载机制，我们需要对Spring Boot对数据文件的加载机制有一定的了解。</p>
<p>Spring Boot为了能够更合理的重写各属性的值，使用了下面这种较为特别的属性加载顺序：</p>
<ol>
<li>命令行中传入的参数。</li>
<li><code>SPRING_APPLICATION_JSON</code>中的属性。<code>SPRING_APPLICATION_JSON</code>是以JSON格式配置在系统环境变量中的内容。</li>
<li><code>java:comp/env</code>中的<code>JNDI</code>属性。</li>
<li>Java的系统属性，可以通过<code>System.getProperties()</code>获得的内容。</li>
<li>操作系统的环境变量</li>
<li>通过<code>random.*</code>配置的随机属性</li>
<li>位于当前应用jar包之外，针对不同<code>{profile}</code>环境的配置文件内容，例如：<code>application-{profile}.properties</code>或是<code>YAML</code>定义的配置文件</li>
<li>位于当前应用jar包之内，针对不同<code>{profile}</code>环境的配置文件内容，例如：<code>application-{profile}.properties</code>或是<code>YAML</code>定义的配置文件</li>
<li>位于当前应用jar包之外的<code>application.properties</code>和<code>YAML</code>配置内容</li>
<li>位于当前应用jar包之内的<code>application.properties</code>和<code>YAML</code>配置内容</li>
<li>在<code>@Configuration</code>注解修改的类中，通过<code>@PropertySource</code>注解定义的属性</li>
<li>应用默认属性，使用<code>SpringApplication.setDefaultProperties</code>定义的内容</li>
</ol>
<p><strong>优先级按上面的顺序有高到低，数字越小优先级越高。</strong></p>
<p>可以看到，其中第7项和第9项都是从应用jar包之外读取配置文件，所以，实现外部化配置的原理就是从此切入，为其指定外部配置文件的加载位置来取代jar包之内的配置内容。通过这样的实现，我们的工程在配置中就变的非常干净，我们只需要在本地放置开发需要的配置即可，而其他环境的配置就可以不用关心，由其对应环境的负责人去维护即可。</p>
<h2> 2.x 新特性</h2>
<p>在Spring Boot 2.0中推出了Relaxed Binding 2.0，对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。</p>
<h3> 配置文件绑定</h3>
<h4> 简单类型</h4>
<p>在Spring Boot 2.0中对配置属性加载的时候会除了像1.x版本时候那样<strong>移除特殊字符</strong>外，还会将配置均以<strong>全小写</strong>的方式进行匹配和加载。所以，下面的4种配置方式都是等价的：</p>
<ul>
<li>properties格式：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>yaml格式：</li>
</ul>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>Tips：推荐使用全小写配合<code>-</code>分隔符的方式来配置，比如：<code>spring.jpa.database-platform=mysql</code></strong></p>
<h4> List类型</h4>
<p>在properties文件中使用<code>[]</code>来定位列表类型，比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>也支持使用<strong>逗号</strong>分割的配置方式，上面与下面的配置是等价的：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>而在yaml文件中使用可以使用如下配置：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>也支持<strong>逗号</strong>分割的方式：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：在Spring Boot 2.0中对于List类型的配置必须是连续的，不然会抛出<code>UnboundConfigurationPropertiesException</code>异常，所以如下配置是不允许的：</strong></p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>在Spring Boot 1.x中上述配置是可以的，<code>foo[1]</code>由于没有配置，它的值会是<code>null</code></strong></p>
<h4> Map类型</h4>
<p>Map类型在properties和yaml中的标准配置方式如下：</p>
<ul>
<li>properties格式：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>yaml格式：</li>
</ul>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：如果Map类型的key包含非字母数字和<code>-</code>的字符，需要用<code>[]</code>括起来，比如：</strong></p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 环境属性绑定</h3>
<p><strong>简单类型</strong></p>
<p>在环境变量中通过小写转换与<code>.</code>替换<code>_</code>来映射配置文件中的内容，比如：环境变量<code>SPRING_JPA_DATABASEPLATFORM=mysql</code>的配置会产生与在配置文件中设置<code>spring.jpa.databaseplatform=mysql</code>一样的效果。</p>
<p><strong>List类型</strong></p>
<p>由于环境变量中无法使用<code>[</code>和<code>]</code>符号，所以使用<code>_</code>来替代。任何由下划线包围的数字都会被认为是<code>[]</code>的数组形式。比如：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外，最后环境变量最后是以数字和下划线结尾的话，最后的下划线可以省略，比如上面例子中的第一条和第三条等价于下面的配置：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><h3> 系统属性绑定</h3>
<p><strong>简单类型</strong></p>
<p>系统属性与文件配置中的类似，都以移除特殊字符并转化小写后实现绑定，比如下面的命令行参数都会实现配置<code>spring.jpa.databaseplatform=mysql</code>的效果：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>List类型</strong></p>
<p>系统属性的绑定也与文件属性的绑定类似，通过<code>[]</code>来标示，比如：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>同样的，他也支持逗号分割的方式，比如：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 属性的读取</h3>
<p>上文介绍了Spring Boot 2.0中对属性绑定的内容，可以看到对于一个属性我们可以有多种不同的表达，但是如果我们要在Spring应用程序的environment中读取属性的时候，每个属性的唯一名称符合如下规则：</p>
<ul>
<li>通过<code>.</code>分离各个元素</li>
<li>最后一个<code>.</code>将前缀与属性名称分开</li>
<li>必须是字母（a-z）和数字(0-9)</li>
<li>必须是小写字母</li>
<li>用连字符<code>-</code>来分隔单词</li>
<li>唯一允许的其他字符是<code>[</code>和<code>]</code>，用于List的索引</li>
<li>不能以数字开头</li>
</ul>
<p>所以，如果我们要读取配置文件中<code>spring.jpa.database-platform</code>的配置，可以这样写：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>而下面的方式是无法获取到<code>spring.jpa.database-platform</code>配置内容的：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>注意：使用<code>@Value</code>获取配置内容的时候也需要这样的特点</strong></p>
<h3> 全新的绑定API</h3>
<p>在Spring Boot 2.0中增加了新的绑定API来帮助我们更容易的获取配置信息。下面举个例子来帮助大家更容易的理解：</p>
<p><strong>例子一：简单类型</strong></p>
<p>假设在propertes配置中有这样一个配置：<code>com.didispace.foo=bar</code></p>
<p>我们为它创建对应的配置类：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来，通过最新的<code>Binder</code>就可以这样来拿配置信息了：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>例子二：List类型</strong></p>
<p>如果配置内容是List类型呢？比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>要获取这些配置依然很简单，可以这样实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本教程配套仓库：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎Star、Follow支持！您的关注是我坚持的动力！</strong></p>
<h3> 相关阅读</h3>
<ul>
<li><a href="https://blog.didispace.com/springbootproperties/" target="_blank" rel="noopener noreferrer">Spring Boot 1.x：属性配置文件详解</a></li>
<li><a href="https://blog.didispace.com/Spring-Boot-2-0-feature-1-relaxed-binding-2/" target="_blank" rel="noopener noreferrer">Spring Boot 2.0：配置绑定 2.0 全解析</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.4 对多环境配置的支持更改</title>
      <link>https://spring.didispace.com/spring-boot-2/2-2-env.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/2-2-env.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.4 对多环境配置的支持更改</source>
      <description>Spring Boot 2.4 对多环境配置的支持更改 在目前最新的Spring Boot 2.4版本中，对配置的加载机制做了较大的调整。相关的问题最近也被问的比较多，所以今天就花点时间，给大家讲讲Spring Boot 2.4的多环境配置较之前版本有哪些变化。 多环境配置 2.4版本之前 先回顾下，2.4版本之前，我们在yaml配置文件中，使用spring.profiles来定义不同环境的标识，比如下面这样： spring: profiles: &amp;quot;dev&amp;quot; name: dev.didispace.com --- spring: profiles: &amp;quot;test&amp;quot; name: test.didispace.com --- spring: profiles: &amp;quot;prod&amp;quot; name: prod.didispace.com</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.4 对多环境配置的支持更改</h1>
<p>在目前最新的Spring Boot 2.4版本中，对配置的加载机制做了较大的调整。相关的问题最近也被问的比较多，所以今天就花点时间，给大家讲讲Spring Boot 2.4的多环境配置较之前版本有哪些变化。</p>
<h2> 多环境配置</h2>
<p><strong>2.4版本之前</strong></p>
<p>先回顾下，2.4版本之前，我们在yaml配置文件中，使用<code>spring.profiles</code>来定义不同环境的标识，比如下面这样：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>2.4版本之后</strong></p>
<p>而在本次2.4版本升级之后，我们需要将<code>spring.profiles</code>配置用<code>spring.config.activate.on-profile</code>替代，比如上面的配置需要修改为如下配置：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>指定环境启动</strong></p>
<p>应用启动的时候，我们要加载不同的环境配置的参数不变，依然采用<code>spring.profiles.active</code>参数，对应值采用<code>spring.config.activate.on-profile</code>定义的标识名称。比如下面的命令就能激活<code>dev</code>环境的配置。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>在应用启动的时候，我们也能看到对应的配置激活日志：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>我们也可以将<code>spring.profiles.active</code>写入yaml配置中，这样的作用就可以指定默认使用某一个环境的配置，通常我们可以设置成开发环境，这样有利于我们平时的开发调试，而真正部署到其他环境的时候则多以命令参数激活为主。</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter1-2</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
<h2> 相关阅读</h2>
<ul>
<li><a href="https://blog.didispace.com/springbootproperties/" target="_blank" rel="noopener noreferrer">Spring Boot 1.x：属性配置文件详解</a></li>
<li><a href="https://blog.didispace.com/Spring-Boot-2-0-feature-1-relaxed-binding-2/" target="_blank" rel="noopener noreferrer">Spring Boot 2.0：配置绑定 2.0 全解析</a></li>
<li><a href="https://blog.didispace.com/spring-boot-learning-21-1-3/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：配置文件详解</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响</title>
      <link>https://spring.didispace.com/spring-boot-2/2-3-group.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/2-3-group.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响</source>
      <description>Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响 前几天在《Spring Boot 2.4 对多环境配置的支持更改》一文中，给大家讲解了Spring Boot 2.4版本对多环境配置的配置变化。除此之外，还有一些其他配置变化，所以今天我们就继续讲讲其他的更新内容！ spring.profiles.include对于这个配置项，你是否熟悉呢？从字面意思也不难理解，应该就是用来引入一些其他配置的配置（因为有个include嘛），实际作用也确实如此！</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响</h1>
<p>前几天在<a href="/spring-boot-2/2-2-env">《Spring Boot 2.4 对多环境配置的支持更改》</a>一文中，给大家讲解了Spring Boot 2.4版本对多环境配置的配置变化。除此之外，还有一些其他配置变化，所以今天我们就继续讲讲其他的更新内容！</p>
<p><code>spring.profiles.include</code>对于这个配置项，你是否熟悉呢？从字面意思也不难理解，应该就是用来引入一些其他配置的配置（因为有个include嘛），实际作用也确实如此！</p>
<p>当我们的应用有很多配置信息的时候，比如当用到了很多中间件MySQL、Redis、MQ等，每个中间件的配置都是一大串的，那么这个时候我们为了配置更简洁一些，可能就会对其做分组。</p>
<p>如果你有用过这样的配置方式，那么在升级2.4版本的时候一定要注意，因为原来的配置方法会失效！</p>
<h2> 2.4之前的分组配置</h2>
<p>先来看看2.4版本之前的分组配置，我们用下面这个例子来介绍：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中：</p>
<ol>
<li>第一个<code>spring.profiles.active: dev</code>，代表默认激活<code>dev</code>配置</li>
<li>第二段<code>dev</code>配置中使用了<code>spring.profiles.include</code>来引入其他配置信息，这里模拟一下一个是dev的db配置，一个是dev的mq配置。在2.3和之前版本的时候，我们通常就是这样来分组配置不同中间件的。</li>
</ol>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>文末我们提供一个样例工程，你可以通过修改spring boot版本到2.3和配置信息使用上面的样例，来启动应用看看这种配置效果。不出意外，你可以在启动日志开头，看到激活的配置信息如下：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>同时激活了dev、dev-db、dev-mq三组配置，include是生效的。</p>
<h2> 2.4的分组配置</h2>
<p>在升级spring boot到2.4之后，再启动之前的应用，你会发现配置就没有生效了，这里不仅是因为<code>spring.profiles</code>失效的原因，即使我们将其都修改为<code>spring.config.activate.on-profile</code>，也依然无法激活dev-db和dev-mq的配置。因为在2.4版本之后，我们需要使用spring.profiles.group来配置了，同时组织结构也发生了变化。</p>
<p>可以尝试把配置修改成如下格式：</p>
<div class="language-yaml line-numbers-mode" data-ext="yml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>启动应用可以在日志开头看到激活的配置组（默认激活的是dev，所以对应的就是dev-db和dev-mq）：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>、修改<code>spring.profiles.active: "prod"</code>直接切换到另外一个环境。</p>
<p>重启应用可以在日志开头看到激活的配置组：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>可以看到，在2.4版本的配置中，不同环境的配置定义都在第一段默认配置中了，所有的环境定义都转移到了<code>spring.profiles.group</code>的key字段（上面配置了<code>dev</code>和<code>prod</code>），value字段则代表了每个环境需要加载的不同配置分组。</p>
<p>回忆一下我们在<a href="https://blog.didispace.com/spring-boot-learning-24-1-4" target="_blank" rel="noopener noreferrer">《Spring Boot 2.4 对多环境配置的支持更改》</a>中提到的多环境配置，是不是不同环境的配置标识都集中定义在了每个<code>spring.config.activate.on-profile</code>里。而这次分组的配置改变，让激活配置、环境配置集中到了默认配置里，其他的profile定义是环境+配置分组的组合内容。</p>
<p>对于这样的调整，最直观的感受就是选择环境的时候，我不需要往下找有哪些profile就能知道有哪些可选项了，还是方便不少。那么你对这样的配置调整怎么看呢？欢迎留言讨论！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter1-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
<h2> 相关阅读</h2>
<ul>
<li><a href="https://blog.didispace.com/springbootproperties/" target="_blank" rel="noopener noreferrer">Spring Boot 1.x：属性配置文件详解</a></li>
<li><a href="https://blog.didispace.com/Spring-Boot-2-0-feature-1-relaxed-binding-2/" target="_blank" rel="noopener noreferrer">Spring Boot 2.0：配置绑定 2.0 全解析</a></li>
<li><a href="https://blog.didispace.com/spring-boot-learning-21-1-3/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：配置文件详解</a></li>
<li><a href="https://blog.didispace.com/spring-boot-learning-24-1-4" target="_blank" rel="noopener noreferrer">Spring Boot 2.4 对多环境配置的支持更改</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：配置元数据的应用</title>
      <link>https://spring.didispace.com/spring-boot-2/2-4-metadata.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/2-4-metadata.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：配置元数据的应用</source>
      <description>Spring Boot 2.x基础教程：配置元数据的应用 在使用Spring Boot开发应用的时候，你是否有发现这样的情况：自定义属性是有高量背景的，鼠标放上去，有一个Cannot resolve configuration property的配置警告。 如果不对于这个警告觉得烦，想要去掉，那么可以通过设置来去除：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：配置元数据的应用</h1>
<p>在使用Spring Boot开发应用的时候，你是否有发现这样的情况：自定义属性是有高量背景的，鼠标放上去，有一个<code>Cannot resolve configuration property</code>的配置警告。</p>
<p><img src="https://static.didispace.com/images/pasted-416.png" alt=""></p>
<p>如果不对于这个警告觉得烦，想要去掉，那么可以通过设置来去除：</p>
<p><img src="https://static.didispace.com/images/pasted-417.png" alt=""></p>
<p>但是，我的建议是不要去掉，因为这个警告正好可以通过高亮来区分你的自定义配置以及框架配置，可以让你快速的分辨哪些是自定义的。</p>
<p>如果你实在想去掉，那么也不建议用上面说的方法，而是建议通过完善配置元数据的方式来完成。所以，今天就来具体说说配置元数据的应用！</p>
<h2> 啥是配置元数据？</h2>
<p>我们不妨打开一个已经创建好的Spring Boot项目，查看一下它的Spring Boot依赖包，可以找到如下图的一个json文件：</p>
<p><img src="https://static.didispace.com/images/pasted-418.png" alt=""></p>
<p>这里报错的就是配置的元数据信息。有没有发现这些<code>name</code>的值都很熟悉？其中<code>description</code>是不是也很熟悉？对，这些就是我们常用的Spring Boot原生配置的元数据信息。</p>
<p>这下知道配置元数据可以用来做啥了吧？它可以帮助IDE来完成配置联想和配置提示的展示。</p>
<p>而我们自定义配置之所以会报警告，同时也没有提示信息，就是因为没有这个元数据的配置文件！</p>
<h2> 配置元数据的自动生成</h2>
<p>既然知道了原理，那么接下来我们尝试用一下配置元数据试试！</p>
<p><strong>第一步</strong>：创建一个配置类，定义一个自定义配置</p>
<div class="language-Java line-numbers-mode" data-ext="Java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：在<code>pom.xml</code>中添加自动生成配置元数据的依赖</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：<code>mvn install</code>下这个项目。</p>
<p>此时我们可以在工程target目录下找到元数据文件：</p>
<p><img src="https://static.didispace.com/images/pasted-419.png" alt=""></p>
<p>同时，我们在配置文件中尝试编写这个自定义的配置项时，可以看到编译器给出了联想和提示：</p>
<p><img src="https://static.didispace.com/images/pasted-420.png" alt=""></p>
<p>并且，编写完配置之后，也没有高亮警告了！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter1-4</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-416.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：加密配置中的敏感信息</title>
      <link>https://spring.didispace.com/spring-boot-2/2-5-jasypt.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/2-5-jasypt.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：加密配置中的敏感信息</source>
      <description>Spring Boot 2.x基础教程：加密配置中的敏感信息 在之前的系列教程中，我们已经介绍了非常多关于Spring Boot配置文件中的各种细节用法，比如：参数间的引用、随机数的应用、命令行参数的使用、多环境的配置管理等等。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：加密配置中的敏感信息</h1>
<p>在之前的系列教程中，我们已经介绍了非常多关于Spring Boot配置文件中的各种细节用法，比如：<a href="https://blog.didispace.com/spring-boot-learning-21-1-3/" target="_blank" rel="noopener noreferrer">参数间的引用</a>、<a href="https://blog.didispace.com/spring-boot-learning-21-1-3/" target="_blank" rel="noopener noreferrer">随机数的应用</a>、<a href="https://blog.didispace.com/spring-boot-learning-21-1-3/" target="_blank" rel="noopener noreferrer">命令行参数的使用</a>、<a href="https://blog.didispace.com/spring-boot-learning-24-1-4/" target="_blank" rel="noopener noreferrer">多环境的配置管理</a>等等。</p>
<p>这些配置相关的知识都是Spring Boot原生就提供的，而今天我们将介绍的功能并非Spring Boot原生就支持，但却非常有用：<strong>配置内容的加密</strong>。</p>
<h2> 为什么要加密？</h2>
<p>可能很多初学者，对于配置信息的加密并不敏感，因为开始主要接触本地的开发，对于很多安全问题并没有太多的考虑。而现实中，我们的配置文件中，其实包含着大量与安全相关的敏感信息，比如：数据库的账号密码、一些服务的密钥等。这些信息一旦泄露，对于企业的重要数据资产，那是相当危险的。 所以，对于这些配置文件中存在的敏感信息进行加密，是每个成熟开发团队都一定会去的事。</p>
<p>如果您是DD的老读者，也许马上会想到Spring Cloud Config就提供配置的加密功能，之前在我的Spring Cloud系列教程和《Spring Cloud微服务实战》一书中都有详细的介绍，感兴趣的话可以点击<a href="https://blog.didispace.com/spring-cloud-starter-dalston-3-2/" target="_blank" rel="noopener noreferrer">《Spring Cloud构建微服务架构：分布式配置中心（加密解密）》</a>一探究竟。</p>
<p>既然以前写过类似内容，那为什么还要写呢？因为并不是所有的开发场景都会搭建Spring Cloud的那套基础设施，同时也不一定会使用Spring Cloud Config作为配置中心。所以，本文主要说说，当我们只使用Spring Boot的时候，如何实现对配置中敏感信息的加密。</p>
<h2> 动手试试</h2>
<p>下面我们将使用<code>https://github.com/ulisesbocchio/jasypt-spring-boot</code>这个开源项目提供的实现和插件，来帮助我们轻松的完成配置信息的加密。</p>
<p>赶紧跟着我下面的步骤动手试试吧！</p>
<p><strong>第一步</strong>：创建一个基础的Spring Boot项目（如果您还不会，可以参考这篇文章：<a href="https://blog.didispace.com/spring-boot-learning-21-1-1/" target="_blank" rel="noopener noreferrer">快速入门</a></p>
<p><strong>第二步</strong>：设计一个参数和单元测试，用来输出这个配置信息</p>
<p>准备加密的配置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>用来输出配置信息的单元测试：</p>
<div class="language-Java line-numbers-mode" data-ext="Java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>执行这个单元测试，会输出：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>这里还没开始加密，下面我们开始引入加密的操作！</p>
<p><strong>第三步</strong>：在<code>pom.xml</code>中引入jasypt提供的Spring Boot Starter</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在插件配置中加入：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：在配置文件中加入加密需要使用的密码</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>同时，修改要加密的内容，用<code>DEC()</code>将待加密内容包裹起来，比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>第五步</strong>：使用<code>jasypt-maven-plugin</code>插件来给<code>DEC()</code>包裹的内容实现批量加密。</p>
<p>在终端中执行下面的命令：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><blockquote>
<p><strong>注意</strong>：这里<code>-Djasypt.encryptor.password</code>参数必须与配置文件中的一致，不然后面会解密失败。</p>
</blockquote>
<p>执行之后，重新查看配置文件，可以看到，自动变成了</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中，<code>ENC()</code>跟<code>DEC()</code>一样都是jasypt提供的标识，分别用来标识括号内的是加密后的内容和待加密的内容。</p>
<p>如果当前配置文件已经都是<code>ENC()</code>内容了，那么我们可以通过下面的命令来解密配置文件，查看原始信息：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>该操作不会修改配置文件，只会在控制台输出解密结果，比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第六步</strong>：此时，我们的配置文件中的敏感信息已经被<code>ENC()</code>修饰了，再执行一下单元测试，不出意外的话，依然可以得到之前一样的结果：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>而此时，配置文件中已经是加密内容了，敏感信息得到了保护。</p>
<blockquote>
<p><strong>注意</strong>：如果在尝试的时候，出现报错：DecryptionException: Unable to decrypt，<a href="https://blog.didispace.com/jasyptspringboot-decryption-exception" target="_blank" rel="noopener noreferrer">点击本文查看可能的原因</a> 。</p>
</blockquote>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a> ，欢迎收藏与转发！如果学习过程中如遇困难？可以加入我们<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a> ，参与交流与讨论，更好的学习与进步！</p>
<h2> 进一步思考</h2>
<p>根据上面的步骤，爱思考的你，也许会发现这样的问题：虽然敏感信息是加密了，但是我们通过配置文件也能看到<code>jasypt.encryptor.password</code>信息，我们是不是通过利用这个再把原始信息解密出来，这样的话岂不是还是不安全？</p>
<p>上面的实现方式的确是会有这样的问题！所以，在实际应用的过程中，<code>jasypt.encryptor.password</code>的配置，可以通过运维小伙伴在环境变量或启动参数中注入，而不是由开发人员在配置文件中指定。</p>
<p>同时，为了应对更高的安全要求，jasypt也提供自定义的加密解密方式，这里就不做具体展开了，有兴趣的小伙伴可以前往<a href="https://github.com/ulisesbocchio/jasypt-spring-boot" target="_blank" rel="noopener noreferrer">jasypt的仓库</a>查看使用细节。</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中<code>2.x</code>目录下的<code>chapter1-5</code>工程：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：构建RESTful API与单元测试</title>
      <link>https://spring.didispace.com/spring-boot-2/3-1-restful-api.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-1-restful-api.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：构建RESTful API与单元测试</source>
      <description>Spring Boot 2.x基础教程：构建RESTful API与单元测试 首先，回顾并详细说明一下在快速入门中使用的@Controller、@RestController、@RequestMapping注解。如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例，建议先看一下快速入门的内容。 @Controller：修饰class，用来创建处理http请求的对象 @RestController：Spring4之后加入的注解，原来在@Controller中返回json需要@ResponseBody来配合，如果直接用@RestController替代@Controller就不需要再配置@ResponseBody，默认返回json格式 @RequestMapping：配置url映射。现在更多的也会直接用以Http Method直接关联的映射注解来定义，比如：GetMapping、PostMapping、DeleteMapping、PutMapping等</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：构建RESTful API与单元测试</h1>
<p>首先，回顾并详细说明一下在<a href="/spring-boot-2/1-2-quick-start">快速入门</a>中使用的<code>@Controller</code>、<code>@RestController</code>、<code>@RequestMapping</code>注解。如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例，建议先看一下<a href="/spring-boot-2/1-2-quick-start">快速入门</a>的内容。</p>
<ul>
<li><code>@Controller</code>：修饰class，用来创建处理http请求的对象</li>
<li><code>@RestController</code>：Spring4之后加入的注解，原来在<code>@Controller</code>中返回json需要<code>@ResponseBody</code>来配合，如果直接用<code>@RestController</code>替代<code>@Controller</code>就不需要再配置<code>@ResponseBody</code>，默认返回json格式</li>
<li><code>@RequestMapping</code>：配置url映射。现在更多的也会直接用以Http Method直接关联的映射注解来定义，比如：<code>GetMapping</code>、<code>PostMapping</code>、<code>DeleteMapping</code>、<code>PutMapping</code>等</li>
</ul>
<p>下面我们通过使用Spring MVC来实现一组对User对象操作的RESTful API，配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。</p>
<p>** RESTful API具体设计如下：**</p>
<p><img src="https://static.didispace.com/content/images/posts/springbootrestfulapi-1.png" alt=""></p>
<h2> 定义User实体</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>注意：相比<a href="https://blog.didispace.com/springbootrestfulapi/" target="_blank" rel="noopener noreferrer">1.x版本教程</a>中自定义set和get函数的方式，这里使用<code>@Data</code>注解可以实现在编译器自动添加set和get函数的效果。该注解是lombok提供的，只需要在pom中引入加入下面的依赖就可以支持：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 实现对User对象的操作接口</h2>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>这里相较<a href="https://blog.didispace.com/springbootrestfulapi/" target="_blank" rel="noopener noreferrer">1.x版本教程</a>中，用更细化的<code>@GetMapping</code>、<code>@PostMapping</code>等系列注解替换了以前的<code>@RequestMaping</code>注解；另外，还使用<code>@RequestBody</code>替换了<code>@ModelAttribute</code>的参数绑定。</p>
</blockquote>
<h2> 编写单元测试</h2>
<p>下面针对该Controller编写测试用例验证正确性，具体如下。当然也可以通过浏览器插件等进行请求提交验证。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>对MockMvc不熟悉的读者，可能会碰到一些函数不存在而报错。必须引入下面这些静态函数的引用：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>这里相较<a href="https://blog.didispace.com/springbootrestfulapi/" target="_blank" rel="noopener noreferrer">1.x版本教程</a>中，主要有两个地方不同。测试类采用<code>@RunWith(SpringRunner.class)</code>和<code>@SpringBootTest</code>修饰启动；另外，由于POST和PUT接口的参数采用<code>@RequestBody</code>注解，所以提交的会是一个json字符串，而不是之前的参数形式，这里在定义请求的时候使用<code>contentType(MediaType.APPLICATION_JSON)</code>指定提交内容为json格式，使用<code>content</code>传入要提交的json字符串。如果用@ModelAttribute的话就得用<code>param</code>方法添加参数，具体可以看<a href="https://blog.didispace.com/springbootrestfulapi/" target="_blank" rel="noopener noreferrer">1.x版本的教程</a>。</p>
</blockquote>
<p>至此，我们通过引入web模块（没有做其他的任何配置），就可以轻松利用Spring MVC的功能，以非常简洁的代码完成了对User对象的RESTful API的创建以及单元测试的编写。其中同时介绍了Spring MVC中最为常用的几个核心注解：<code>@RestController</code>,<code>RequestMapping</code>以及一些参数绑定的注解：<code>@PathVariable</code>,<code>@RequestBody</code>等。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter2-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/content/images/posts/springbootrestfulapi-1.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</title>
      <link>https://spring.didispace.com/spring-boot-2/3-2-swagger-1.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-2-swagger-1.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</source>
      <description>Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档 随着前后端分离架构和微服务架构的流行，我们使用Spring Boot来构建RESTful API项目的场景越来越多。通常我们的一个RESTful API就有可能要服务于多个不同的开发人员或开发团队：IOS开发、Android开发、Web开发甚至其他的后端服务等。为了减少与其他团队平时开发期间的频繁沟通成本，传统做法就是创建一份RESTful API文档来记录所有接口细节，然而这样的做法有以下几个问题： 由于接口众多，并且细节复杂（需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等），高质量地创建这份文档本身就是件非常吃力的事，下游的抱怨声不绝于耳。 随着时间推移，不断修改接口实现的时候都必须同步修改接口文档，而文档与代码又处于两个不同的媒介，除非有严格的管理机制，不然很容易导致不一致现象。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</h1>
<p>随着前后端分离架构和微服务架构的流行，我们使用Spring Boot来构建RESTful API项目的场景越来越多。通常我们的一个RESTful API就有可能要服务于多个不同的开发人员或开发团队：IOS开发、Android开发、Web开发甚至其他的后端服务等。为了减少与其他团队平时开发期间的频繁沟通成本，传统做法就是创建一份RESTful API文档来记录所有接口细节，然而这样的做法有以下几个问题：</p>
<ul>
<li>由于接口众多，并且细节复杂（需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等），高质量地创建这份文档本身就是件非常吃力的事，下游的抱怨声不绝于耳。</li>
<li>随着时间推移，不断修改接口实现的时候都必须同步修改接口文档，而文档与代码又处于两个不同的媒介，除非有严格的管理机制，不然很容易导致不一致现象。</li>
</ul>
<p>为了解决上面这样的问题，本文将介绍RESTful API的重磅好伙伴Swagger2，它可以轻松的整合到Spring Boot中，并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量，同时说明内容又整合入实现代码中，让维护文档和修改代码整合为一体，可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示：</p>
<p><img src="http://img.didispace.com/Frp7Fhk44jt5NzkRM5qxJqoXMWiS" alt=""></p>
<p>下面来具体介绍，在Spring Boot中使用我们自己实现的starter来整合Swagger。该整合项目的Github：<a href="https://github.com/SpringForAll/spring-boot-starter-swagger" target="_blank" rel="noopener noreferrer">https://github.com/SpringForAll/spring-boot-starter-swagger</a>。如果您觉得它好用，欢迎Star支持我们！</p>
<h2> 准备工作</h2>
<p>首先，我们需要一个Spring Boot实现的RESTful API工程，若您没有做过这类内容，建议先阅读上一篇教程：
<a href="/spring-boot-2/3-1-restful-api">构建RESTful API与单元测试</a>构建一个。或者也可以直接使用上一篇教程中的样例作为基础，即下面仓库中的<code>chapter2-1</code>工程：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/tree/2.x</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/tree/2.x</a></li>
</ul>
<h2> 整合Swagger2</h2>
<p>下面，我们以上面仓库中的<code>chapter2-1</code>工程进行整合改造。</p>
<p><strong>第一步</strong>：添加swagger-spring-boot-starter依赖</p>
<p>在<code>pom.xml</code>中加入依赖，具体如下：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：应用主类中添加<code>@EnableSwagger2Doc</code>注解，具体如下</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：<code>application.properties</code>中配置文档相关内容，比如</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>各参数配置含义如下：</p>
<ul>
<li><code>swagger.title</code>：标题</li>
<li><code>swagger.description</code>：描述</li>
<li><code>swagger.version</code>：版本</li>
<li><code>swagger.license</code>：许可证</li>
<li><code>swagger.licenseUrl</code>：许可证URL</li>
<li><code>swagger.termsOfServiceUrl</code>：服务条款URL</li>
<li><code>swagger.contact.name</code>：维护人</li>
<li><code>swagger.contact.url</code>：维护人URL</li>
<li><code>swagger.contact.email</code>：维护人email</li>
<li><code>swagger.base-package</code>：swagger扫描的基础包，默认：全扫描</li>
<li><code>swagger.base-path</code>：需要处理的基础URL规则，默认：/**</li>
</ul>
<p>更多配置说明可见官方说明：<a href="https://github.com/SpringForAll/spring-boot-starter-swagger" target="_blank" rel="noopener noreferrer">https://github.com/SpringForAll/spring-boot-starter-swagger</a></p>
<p><strong>第四步</strong>：启动应用，访问：<code>http://localhost:8080/swagger-ui.html</code>，就可以看到如下的接口文档页面：</p>
<p><img src="http://img.didispace.com/Frp7Fhk44jt5NzkRM5qxJqoXMWiS" alt=""></p>
<h2> 添加文档内容</h2>
<p>在整合完Swagger之后，在<code>http://localhost:8080/swagger-ui.html</code>页面中可以看到，关于各个接口的描述还都是英文或遵循代码定义的名称产生的。这些内容对用户并不友好，所以我们需要自己增加一些说明来丰富文档内容。如下所示，我们通过<code>@Api</code>，<code>@ApiOperation</code>注解来给API增加说明、通过<code>@ApiImplicitParam</code>、<code>@ApiModel</code>、<code>@ApiModelProperty</code>注解来给参数增加说明。</p>
<p>比如下面的例子：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完成上述代码添加后，启动Spring Boot程序，访问：<code>http://localhost:8080/swagger-ui.html</code>，就能看到下面这样带中文说明的文档了（其中标出了各个注解与文档元素的对应关系以供参考）：</p>
<p><img src="http://img.didispace.com/FoxwzIgdkIIx6Z5_U8DZq5MqVQf_" alt=""></p>
<p><img src="http://img.didispace.com/Fjc9yvgYhnQCrM9-2VaQiGwK0v6M" alt=""></p>
<h2> API文档访问与调试</h2>
<p>在上图请求的页面中，我们看到user的Value是个输入框？是的，Swagger除了查看接口功能外，还提供了调试测试功能，我们可以点击上图中右侧的Model Schema（黄色区域：它指明了User的数据结构），此时Value中就有了user对象的模板，我们只需要稍适修改，点击下方<code>“Try it out！”</code>按钮，即可完成了一次请求调用！</p>
<p>此时，你也可以通过几个GET请求来验证之前的POST请求是否正确。</p>
<p>相比为这些接口编写文档的工作，我们增加的配置内容是非常少而且精简的，对于原有代码的侵入也在忍受范围之内。因此，在构建RESTful API的同时，加入Swagger来对API文档进行管理，是个不错的选择。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter2-2</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="http://img.didispace.com/Frp7Fhk44jt5NzkRM5qxJqoXMWiS" type="image/"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：JSR-303实现请求参数校验</title>
      <link>https://spring.didispace.com/spring-boot-2/3-3-jsr303.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-3-jsr303.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：JSR-303实现请求参数校验</source>
      <description>Spring Boot 2.x基础教程：JSR-303实现请求参数校验 请求参数的校验是很多新手开发非常容易犯错，或存在较多改进点的常见场景。比较常见的问题主要表现在以下几个方面： 仅依靠前端框架解决参数校验，缺失服务端的校验。这种情况常见于需要同时开发前后端的时候，虽然程序的正常使用不会有问题，但是开发者忽略了非正常操作。比如绕过前端程序，直接模拟客户端请求，这时候就会突然在前端预设的各种限制，直击各种数据访问接口，使得我们的系统存在安全隐患。 大量地使用if/else语句嵌套实现，校验逻辑晦涩难通，不利于长期维护。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：JSR-303实现请求参数校验</h1>
<p>请求参数的校验是很多新手开发非常容易犯错，或存在较多改进点的常见场景。比较常见的问题主要表现在以下几个方面：</p>
<ul>
<li>仅依靠前端框架解决参数校验，缺失服务端的校验。这种情况常见于需要同时开发前后端的时候，虽然程序的正常使用不会有问题，但是开发者忽略了非正常操作。比如绕过前端程序，直接模拟客户端请求，这时候就会突然在前端预设的各种限制，直击各种数据访问接口，使得我们的系统存在安全隐患。</li>
<li>大量地使用<code>if/else</code>语句嵌套实现，校验逻辑晦涩难通，不利于长期维护。</li>
</ul>
<p>所以，针对上面的问题，建议服务端开发在实现接口的时候，对于请求参数必须要有服务端校验以保障数据安全与稳定的系统运行。同时，对于参数的校验实现需要足够优雅，要满足逻辑易读、易维护的基本特点。</p>
<p>接下来，我们就在本篇教程中详细说说，如何优雅地实现Spring Boot服务端的请求参数校验。</p>
<h2> JSR-303</h2>
<p>在开始动手实践之前，我们先了解一下接下来我们将使用的一项标准规范：JSR-303</p>
<p><strong>什么是JSR？</strong></p>
<p>JSR是Java Specification Requests的缩写，意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR，以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。</p>
<p><strong>JSR-303定义的是什么标准？</strong></p>
<p>JSR-303 是JAVA EE 6 中的一项子规范，叫做Bean Validation，Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现，除此之外还有一些附加的 constraint。</p>
<p><strong>Bean Validation中内置的constraint</strong></p>
<p><img src="http://img.didispace.com/Fugzgq1zvxjKur4qdm_N-xV5twMj" alt=""></p>
<p><strong>Hibernate Validator附加的constraint</strong></p>
<p><img src="http://img.didispace.com/FnNRRGx1eWbniJFHQz2m-pUIEWKa" alt=""></p>
<p>在JSR-303的标准之下，我们可以通过上面这些注解，优雅的定义各个请求参数的校验。更多关于JSR的内容可以参与官方文档或参考资料中的引文[1]。</p>
<h2> 动手实践</h2>
<p>已经了解了JSR-303之后，接下来我们就来尝试一下，基于此规范如何实现参数的校验！</p>
<h3> 准备工作</h3>
<p>读者可以拿任何一个使用Spring Boot 2.x构建的提供RESTful API的项目作为基础。也可以使用<a href="/spring-boot-2/3-2-swagger-1">Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</a>中构建的实验工程作为基础，您可以通过下面仓库中的<code>chapter2-2</code>目录取得：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/tree/2.x</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/tree/2.x</a></li>
</ul>
<p>当然，您也可以根据前文再构建一个作为复习，也是完全没有问题的。</p>
<h3> 快速入门</h3>
<p>我们先来做一个简单的例子，比如：定义字段不能为<code>Null</code>。只需要两步</p>
<p><strong>第一步</strong>：在要校验的字段上添加上<code>@NotNull</code>注解，具体如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：在需要校验的参数实体前添加<code>@Valid</code>注解，具体如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完成上面配置之后，启动应用，并用POST请求访问<code>localhost:8080/users/</code>接口，body使用一个空对象，<code>{}</code>。你可以用Postman等测试工具发起，也可以使用curl发起，比如这样：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>不出意外，你可以得到如下结果：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中返回内容的各参数含义如下：</p>
<ul>
<li><code>timestamp</code>：请求时间</li>
<li><code>status</code>：HTTP返回的状态码，这里返回400，即：请求无效、错误的请求，通常参数校验不通过均为400</li>
<li><code>error</code>：HTTP返回的错误描述，这里对应的就是400状态的错误描述：Bad Request</li>
<li><code>errors</code>：具体错误原因，是一个数组类型；因为错误校验可能存在多个字段的错误，比如这里因为定义了两个参数不能为<code>Null</code>，所以存在两条错误记录信息</li>
<li><code>message</code>：概要错误消息，返回内容中很容易可以知道，这里的错误原因是对user对象的校验失败，其中错误数量为<code>2</code>，而具体的错误信息就定义在上面的<code>errors</code>数组中</li>
<li><code>path</code>：请求路径</li>
</ul>
<p>请求的调用端在拿到这个规范化的错误信息之后，就可以方便的解析并作出对应的措施以完成自己的业务逻辑了。</p>
<h3> 尝试一些其他校验</h3>
<p>在完成了上面的例子之后，我们还可以增加一些校验规则，比如：校验字符串的长度、校验数字的大小、校验字符串格式是否为邮箱等。下面我们就来定义一些复杂的校验定义，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>发起一个可以出发<code>name</code>、<code>age</code>、<code>email</code>都校验不通过的请求，比如下面这样：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们将得到如下的错误返回：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>从<code>errors</code>数组中的各个错误明细中，知道各个字段的<code>defaultMessage</code>，可以看到很清晰的错误描述。</p>
<h3> Swagger文档中的体现</h3>
<p>可能有读者会问了，我的接口中是定了这么多。上一篇教程中，不是还教了如何自动生成文档么，那么对于参数的校验逻辑该如何描述呢？</p>
<p>这里要分两种情况，Swagger自身对JSR-303有一定的支持，但是支持的并那么完善，并没有覆盖所有的注解的。</p>
<p>比如，上面我们使用的注解是可以自动生成的，启动上面我们的实验工程，然后访问<code>http://localhost:8080/swagger-ui.html</code>，在<code>Models</code>不是，我们可以看到如下图所示的内容：</p>
<p><img src="http://img.didispace.com/FjCx_hTf40k4A5EqtZPsi6wR69xq" alt=""></p>
<p>其中：<code>name</code>和<code>age</code>字段相比上一篇教程中的文档描述，多了一些关于校验相关的说明；而<code>email</code>字段则没有体现相关校验说明。目前，Swagger共支持以下几个注解：<code>@NotNull</code>、<code>@Max</code>、<code>@Min</code>、<code>@Size</code>、<code>@Pattern</code>。在实际开发过程中，我们需要分情况来处理，对于Swagger支自动生成的可以利用原生支持来产生，如果有部分字段无法产生，则可以在<code>@ApiModelProperty</code>注解的描述中他，添加相应的校验说明，以便于使用方查看。</p>
<h3> 番外：也许你会有这些疑问</h3>
<p><strong>当请求参数校验出现错误信息的时候，错误格式可以修改吗？</strong></p>
<p>答案是肯定的。这里的错误信息实际上由Spring Boot的异常处理机制统一组织并返回的，我们将在后面的教程中详细介绍，Spring Boot是如何统一处理异常返回以及我们该如何定时异常返回。</p>
<p><strong><code>spring-boot-starter-validation</code>是必须的吗？</strong></p>
<p>有读者之前问过，看到很多教程都写了还要引入<code>spring-boot-starter-validation</code>依赖，这个依赖到底是否需要？（本篇中并没有引入）</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其实，只需要仔细看一下<code>spring-boot-starter-validation</code>依赖主要是为了引入了什么，再根据当前自己使用的Spring Boot版本来判断即可。实际上，<code>spring-boot-starter-validation</code>依赖主要是为了引入下面这个依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们可以看看当前工程的依赖中是否有它，就可以判断是否还需要额外引入。在Spring Boot 2.1版本中，该依然其实已经包含在了<code>spring-boot-starter-web</code>依赖中，并不需要额外引入，所以您在本文中找不到这一步。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter2-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="http://img.didispace.com/Fugzgq1zvxjKur4qdm_N-xV5twMj" type="image/"/>
    </item>
    <item>
      <title>Spring Boot 中使用 JSON Schema 来校验复杂JSON数据</title>
      <link>https://spring.didispace.com/spring-boot-2/3-3-spring-boot-json-schema.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-3-spring-boot-json-schema.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 中使用 JSON Schema 来校验复杂JSON数据</source>
      <description>Spring Boot 中使用 JSON Schema 来校验复杂JSON数据 JSON是我们编写API时候用于数据传递的常用格式，那么你是否知道JSON Schema呢？ 在数据交换领域，JSON Schema 以其强大的标准化能力，为定义和规范 JSON 数据的结构与规则提供了有力支持。通过一系列精心设计的关键字，JSON Schema 能够详尽地描述数据的各项属性。然而，仅凭 JSON Schema 本身，尚不足以验证 JSON 实例是否严格遵循预设的模式。此时，JSON Schema 验证器的角色便显得尤为关键。这些验证器如同严格的检查官，确保每一个 JSON 文档都能忠实地反映出模式的定义。JSON Schema 验证器，作为实现 JSON Schema 规范的技术工具，其灵活的集成能力使得无论项目规模大小，都能轻松地将 JSON Schema 融入开发流程，从而提升数据处理的效率与准确性。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 06 Aug 2024 10:30:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 中使用 JSON Schema 来校验复杂JSON数据</h1>
<p>JSON是我们编写API时候用于数据传递的常用格式，那么你是否知道JSON Schema呢？</p>
<p>在数据交换领域，JSON Schema 以其强大的标准化能力，为定义和规范 JSON 数据的结构与规则提供了有力支持。通过一系列精心设计的关键字，JSON Schema 能够详尽地描述数据的各项属性。然而，仅凭 JSON Schema 本身，尚不足以验证 JSON 实例是否严格遵循预设的模式。此时，JSON Schema 验证器的角色便显得尤为关键。这些验证器如同严格的检查官，确保每一个 JSON 文档都能忠实地反映出模式的定义。JSON Schema 验证器，作为实现 JSON Schema 规范的技术工具，其灵活的集成能力使得无论项目规模大小，都能轻松地将 JSON Schema 融入开发流程，从而提升数据处理的效率与准确性。</p>
<p><img src="https://static.didispace.com/images3/d5a1a06897bb1d65a2b791ed0221dbbc.png" alt=""></p>
<p>下面我们来看看如何在Spring Boot应用中使用JSON Schema校验JSON数据</p>
<h2> 动手试试</h2>
<ol>
<li>
<p>创建一个基本的Spring Boot应用，如果还不会可以<a href="https://www.didispace.com/spring-boot-2/1-2-quick-start.html" target="_blank" rel="noopener noreferrer">点击查看快速入门</a></p>
</li>
<li>
<p>在<code>pom.xml</code>中添加<code>json-schema-validator</code>依赖</p>
</li>
</ol>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="3">
<li>创建JSON Schema</li>
</ol>
<p>在<code>src/main/resources</code>目录下创建一个<code>validation.json</code>文件，然后在里面制定一套详尽的验证规则，比如下面这样：</p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>创建 JsonSchema 的 Bean</li>
</ol>
<p>当然，你也可以直接new来创建，但实战中还是推荐用Spring管理这些实例，比如 下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="5">
<li>使用 JsonSchema</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="6">
<li>在 Web 层的应用</li>
</ol>
<p>创建一个Controller，当接收到来自客户端的JSON数据之后，就可以像下面这样对json数据进行校验：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="7">
<li>测试一下</li>
</ol>
<p>启动 Sprint Boot 应用，然后使用你喜欢的http客户端工具对<code>/test</code>接口发送测试请求：</p>
<p>比如，下面使用Curl来进行测试：</p>
<ul>
<li>符合规则的合法请求：</li>
</ul>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>校验通过，返回：[]，没有错误信息</p>
<ul>
<li>不符合规则的非法请求（却少order id）：</li>
</ul>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>校验失败，将返回错误信息：<code>[$.order_id: is missing but it is required]</code></p>
<p>好了，今天的分享就到这里，希望对您有用。如果您学习过程中如遇困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<h2> 相关资料</h2>
<ul>
<li><a href="https://json-schema.org/overview/what-is-jsonschema" target="_blank" rel="noopener noreferrer">What is JSON Schema?</a></li>
<li><a href="https://www.jsonschemavalidator.net/" target="_blank" rel="noopener noreferrer">JSON Schema validator</a></li>
</ul>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/d5a1a06897bb1d65a2b791ed0221dbbc.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解</title>
      <link>https://spring.didispace.com/spring-boot-2/3-4-swagger-2.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-4-swagger-2.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解</source>
      <description>Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解 之前通过使用Swagger2构建强大的API文档一文，我们学习了如何使用Swagger为Spring Boot项目自动生成API文档，有不少用户留言问了关于文档内容的组织以及排序问题。所以，就特别开一篇详细说说Swagger中文档内容如何来组织以及其中各个元素如何控制前后顺序的具体配置方法。 接口的分组 我们在Spring Boot中定义各个接口是以Controller作为第一级维度来进行组织的，Controller与具体接口之间的关系是一对多的关系。我们可以将同属一个模块的接口定义在一个Controller里。默认情况下，Swagger是以Controller为单位，对接口进行分组管理的。这个分组的元素在Swagger中称为Tag，但是这里的Tag与接口的关系并不是一对多的，它支持更丰富的多对多关系。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解</h1>
<p>之前通过<a href="/spring-boot-2/3-2-swagger-1">使用Swagger2构建强大的API文档</a>一文，我们学习了如何使用Swagger为Spring Boot项目自动生成API文档，有不少用户留言问了关于文档内容的组织以及排序问题。所以，就特别开一篇详细说说Swagger中文档内容如何来组织以及其中各个元素如何控制前后顺序的具体配置方法。</p>
<h2> 接口的分组</h2>
<p>我们在Spring Boot中定义各个接口是以<code>Controller</code>作为第一级维度来进行组织的，<code>Controller</code>与具体接口之间的关系是一对多的关系。我们可以将同属一个模块的接口定义在一个<code>Controller</code>里。默认情况下，Swagger是以<code>Controller</code>为单位，对接口进行分组管理的。这个分组的元素在Swagger中称为<code>Tag</code>，但是这里的<code>Tag</code>与接口的关系并不是一对多的，它支持更丰富的多对多关系。</p>
<h3> 默认分组</h3>
<p>首先，我们通过一个简单的例子，来看一下默认情况，Swagger是如何根据Controller来组织Tag与接口关系的。定义两个<code>Controller</code>，分别负责教师管理与学生管理接口，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>启动应用之后，我们可以看到Swagger中这两个Controller是这样组织的：</p>
<p><img src="http://img.didispace.com/FpBD_IuM7mukpyPg7Pbs77XwJQmD" alt=""></p>
<p>图中标出了Swagger默认生成的<code>Tag</code>与Spring Boot中<code>Controller</code>展示的内容与位置。</p>
<h3> 自定义默认分组的名称</h3>
<p>接着，我们可以再试一下，通过<code>@Api</code>注解来自定义<code>Tag</code>，比如这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再次启动应用之后，我们就看到了如下的分组内容，代码中<code>@Api</code>定义的<code>tags</code>内容替代了默认产生的<code>teacher-controller</code>和<code>student-controller</code>。</p>
<p><img src="http://img.didispace.com/FvRCKdu8fm7LXS4rAqjXfiZNnrLH" alt=""></p>
<h3> 合并Controller分组</h3>
<p>到这里，我们还都只是使用了<code>Tag</code>与<code>Controller</code>一一对应的情况，Swagger中还支持更灵活的分组！从<code>@Api</code>注解的属性中，相信聪明的读者一定已经发现<code>tags</code>属性其实是个数组类型：</p>
<p><img src="http://img.didispace.com/FvcnP816K7R5VKMzhzGOyKyXEpJ1" alt=""></p>
<p>我们可以通过定义同名的<code>Tag</code>来汇总<code>Controller</code>中的接口，比如我们可以定义一个<code>Tag</code>为“教学管理”，让这个分组同时包含教师管理和学生管理的所有接口，可以这样来实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最终效果如下：</p>
<p><img src="http://img.didispace.com/Fk9cuVSrQpm_dvBA2ZHlEOI6diuw" alt=""></p>
<h3> 更细粒度的接口分组</h3>
<p>通过<code>@Api</code>可以实现将<code>Controller</code>中的接口合并到一个<code>Tag</code>中，但是如果我们希望精确到某个接口的合并呢？比如这样的需求：“教学管理”包含“教师管理”中所有接口以及“学生管理”管理中的“获取学生清单”接口（不是全部接口）。</p>
<p>那么上面的实现方式就无法满足了。这时候发，我们可以通过使用<code>@ApiOperation</code>注解中的<code>tags</code>属性做更细粒度的接口分类定义，比如上面的需求就可以这样子写：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>效果如下图所示：</p>
<p><img src="http://img.didispace.com/FhgA4mEswuyqOAmVGZCLgeyYDU3r" alt=""></p>
<h2> 内容的顺序</h2>
<p>在完成了接口分组之后，对于接口内容的展现顺序又是众多用户特别关注的点，其中主要涉及三个方面：分组的排序、接口的排序以及参数的排序，下面我们就来逐个说说如何配置与使用。</p>
<h3> 分组的排序</h3>
<p>关于分组排序，也就是Tag的排序。目前版本的Swagger支持并不太好，通过文档我们可以找到关于Tag排序的配置方法。</p>
<p>第一种：原生Swagger用户，可以通过如下方式：</p>
<p><img src="http://img.didispace.com/Fu_RLHsfrEnt5wA0J-oxCkUw2sCq" alt="file"></p>
<p>第二种：Swagger Starter用户，可以通过修改配置的方式：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>似乎找到了希望，但是其实这块并没有什么可选项，一看源码便知：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>是的，Swagger只提供了一个选项，就是按字母顺序排列。那么我们要如何实现排序呢？这里笔者给一个不需要扩展源码，仅依靠使用方式的定义来实现排序的建议：为Tag的命名做编号。比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>由于原本存在按字母排序的机制在，通过命名中增加数字来帮助排序，可以简单而粗暴的解决分组问题，最后效果如下：</p>
<p><img src="http://img.didispace.com/Fp9jpkif87ESb0T_ynmClggPdOU6" alt=""></p>
<h3> 接口的排序</h3>
<p>在完成了分组排序问题（虽然不太优雅...）之后，在来看看同一分组内各个接口该如何实现排序。同样的，凡事先查文档，可以看到Swagger也提供了相应的配置，下面也分两种配置方式介绍：</p>
<p>第一种：原生Swagger用户，可以通过如下方式：</p>
<p><img src="http://img.didispace.com/Fj283ftc5WXw7yCH6UFl7VvCshrC" alt=""></p>
<p>第二种：Swagger Starter用户，可以通过修改配置的方式：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>很庆幸，这个配置不像Tag的排序配置没有可选项。它提供了两个配置项：<code>alpha</code>和<code>method</code>，分别代表了按字母表排序以及按方法定义顺序排序。当我们不配置的时候，改配置默认为<code>alpha</code>。两种配置的效果对比如下图所示：</p>
<p><img src="http://img.didispace.com/FkVlyiqqmpHMGoU6WlN4uNzEfKh4" alt=""></p>
<h3> 参数的排序</h3>
<p>完成了接口的排序之后，更细粒度的就是请求参数的排序了。默认情况下，Swagger对Model参数内容的展现也是按字母顺序排列的。所以之前教程中的User对象在文章中展现如下：</p>
<p><img src="http://img.didispace.com/Fmaw4Z3RduSZ63Au84Je-zbv9mDA" alt=""></p>
<p>如果我们希望可以按照Model中定义的成员变量顺序来展现，那么需要我们通过<code>@ApiModelProperty</code>注解的<code>position</code>参数来实现位置的设置，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最终效果如下：</p>
<p><img src="http://img.didispace.com/Ft2_dN_iFrtwfc7kFW_8TDeknsXA" alt=""></p>
<h2> 小结</h2>
<p>本文详细的介绍了Swagger中对接口内容的组织控制。有些问题并没有通过配置来解决，也可能是对文档或源码内容的了解不够深入。如果读者有更好的实现方案，欢迎提出与交流。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter2-4</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="http://img.didispace.com/FpBD_IuM7mukpyPg7Pbs77XwJQmD" type="image/"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：Swagger静态文档的生成</title>
      <link>https://spring.didispace.com/spring-boot-2/3-5-swagger-3.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-5-swagger-3.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：Swagger静态文档的生成</source>
      <description>Spring Boot 2.x基础教程：Swagger静态文档的生成 前言 通过之前的两篇关于Swagger入门以及具体使用细节的介绍之后，我们已经能够轻松地为Spring MVC的Web项目自动构建出API文档了。如果您还不熟悉这块，可以先阅读： Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档 Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：Swagger静态文档的生成</h1>
<h2> 前言</h2>
<p>通过之前的两篇关于Swagger入门以及具体使用细节的介绍之后，我们已经能够轻松地为Spring MVC的Web项目自动构建出API文档了。如果您还不熟悉这块，可以先阅读：</p>
<ul>
<li><a href="https://blog.didispace.com/spring-boot-learning-21-2-2/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</a></li>
<li><a href="https://blog.didispace.com/spring-boot-learning-21-2-4/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：Swagger接口分类与各元素排序问题详解</a></li>
</ul>
<p>在这两篇文章中，我们构建的文档必须通过在项目中整合<code>swagger-ui</code>、或使用单独部署的<code>swagger-ui</code>和<code>/v2/api-docs</code>返回的配置信息才能展现出您所构建的API文档。而有些时候，我们可能只需要提供静态文档给其他对接方的时候，我们要如何快速轻便的产生静态API文档呢？</p>
<p>接下来我们就来学习一个解决该问题的工具：<strong>Swagger2Markup</strong>。</p>
<h2> Swagger2Markup简介</h2>
<p>Swagger2Markup是Github上的一个开源项目。该项目主要用来将Swagger自动生成的文档转换成几种流行的格式以便于静态部署和使用，比如：AsciiDoc、Markdown、Confluence。</p>
<p>项目主页：<a href="https://github.com/Swagger2Markup/swagger2markup" target="_blank" rel="noopener noreferrer">https://github.com/Swagger2Markup/swagger2markup</a></p>
<h2> 如何使用</h2>
<p>在使用Swagger2Markup之前，我们先需要准备一个使用了Swagger的Web项目，可以是直接使用Swagger2的项目，也可以使用<a href="https://blog.didispace.com/spring-boot-learning-21-2-2/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：使用Swagger2构建强大的API文档</a>一文中构建的项目。读者可以通过下面的仓库获取：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/tree/2.x</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/tree/2.x</a></li>
</ul>
<p>接下来，我们将利用这个项目中的<code>chapter2-2</code>模块作为基础来来生成几种不同格式的静态文档。</p>
<h3> 生成 AsciiDoc 文档</h3>
<p>生成 AsciiDoc 文档的方式有两种：</p>
<h4> 通过Java代码来生成</h4>
<p><strong>第一步</strong>：编辑<code>pom.xml</code>增加需要使用的相关依赖和仓库</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>本身这个工具主要就临时用一下，所以这里我们把<code>scope</code>设置为test，这样这个依赖就不会打包到正常运行环境中去。</p>
<p><strong>第二步</strong>：编写一个单元测试用例来生成执行生成文档的代码</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>以上代码内容很简单，大致说明几个关键内容：</p>
<ul>
<li><code>MarkupLanguage.ASCIIDOC</code>：指定了要输出的最终格式。除了<code>ASCIIDOC</code>之外，还有<code>MARKDOWN</code>和<code>CONFLUENCE_MARKUP</code>，分别定义了其他格式，后面会具体举例。</li>
<li><code>from(remoteSwaggerFile</code>：指定了生成静态部署文档的源头配置，可以是这样的URL形式，也可以是符合Swagger规范的String类型或者从文件中读取的流。如果是对当前使用的Swagger项目，我们通过使用访问本地Swagger接口的方式，如果是从外部获取的Swagger文档配置文件，就可以通过字符串或读文件的方式</li>
<li><code>toFolder(outputDirectory)</code>：指定最终生成文件的具体<em>目录</em>位置</li>
</ul>
<p>在执行了上面的测试用例之后，我们就能在当前项目的src目录下获得如下内容：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，这种方式在运行之后就生成出了4个不同的静态文件。</p>
<p><strong>输出到单个文件</strong></p>
<p>如果不想分割结果文件，也可以通过替换<code>toFolder(Paths.get("src/docs/asciidoc/generated")</code>为<code>toFile(Paths.get("src/docs/asciidoc/generated/all"))</code>，将转换结果输出到一个单一的文件中，这样可以最终生成html的也是单一的。</p>
<h4> 通过 Maven 插件来生成</h4>
<p>除了通过上面编写Java代码来生成的方式之外，swagger2markup还提供了对应的Maven插件来使用。对于上面的生成方式，完全可以通过在<code>pom.xml</code>中增加如下插件来完成静态内容的生成。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在使用插件生成前，需要先启动应用。然后执行插件，就可以在<code>src/docs/asciidoc/generated-by-plugin</code>目录下看到也生成了上面一样的adoc文件了。</p>
<h4> 生成HTML</h4>
<p>在完成了从Swagger文档配置文件到AsciiDoc的源文件转换之后，就是如何将AsciiDoc转换成可部署的HTML内容了。这里继续在上面的工程基础上，引入一个Maven插件来完成。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过上面的配置，执行该插件的<code>asciidoctor:process-asciidoc</code>命令之后，就能在<code>src/docs/asciidoc/html</code>目录下生成最终可用的静态部署HTML了。在完成生成之后，可以直接通过浏览器来看查看，你就能看到类似下图的静态部署结果：</p>
<p><img src="https://static.didispace.com/images/pasted-280.png" alt=""></p>
<p><strong>是不是感觉似曾相识呢？是的，Spring Cloud的E版之前的文档也是这样的！！！</strong></p>
<h3> Markdown 与 Confluence 的支持</h3>
<p>要生成Markdown和Confluence的方式非常简单，与上一篇中的方法类似，只需要修改一个参数即可。</p>
<h4> 生成 Markdown 和 Confluence 文档</h4>
<p>生成方式有一下两种：</p>
<ul>
<li>通过Java代码来生成：只需要修改<code>withMarkupLanguage</code>属性来指定不同的格式以及<code>toFolder</code>属性为结果指定不同的输出目录。</li>
</ul>
<p>生成markdown的代码片段：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>生成confluence的代码片段：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在执行了上面的设置内容之后，我们就能在当前项目的src目录下获得如下内容：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，运行之后分别在markdown和confluence目录下输出了不同格式的转换内容。如果读者想要通过插件来生成，直接参考上一节内容，只需要修改插件配置中的<code>swagger2markup.markupLanguage</code>即可支持输出其他格式内容。</p>
<p>最后，我们一起来看看生成的Markdown和Confluence文档要怎么使用</p>
<h4> Markdown的部署</h4>
<p>Markdown目前在文档编写中使用非常常见，所以可用的静态部署工具也非常多，比如：Hexo、Jekyll等都可以轻松地实现静态化部署，也可以使用一些SaaS版本的文档工具，比如：语雀等。具体使用方法，这里按照这些工具的文档都非常详细，这里就不具体介绍了。</p>
<h4> Confluence的部署</h4>
<p>相信很多团队都使用Confluence作为文档管理系统，所以下面具体说说Confluence格式生成结果的使用。</p>
<p><strong>第一步</strong>：在Confluence的新建页面的工具栏中选择<code>{}Markup</code></p>
<p><img src="https://static.didispace.com/images/pasted-281.png" alt=""></p>
<p><strong>第二步</strong>：在弹出框的<code>Insert</code>选项中选择<code>Confluence Wiki</code>，然后将生成的txt文件中的内容，黏贴在左侧的输入框中；此时，在右侧的阅览框可以看到如下图的效果了。</p>
<p><img src="https://static.didispace.com/images/pasted-282.png" alt=""></p>
<p>注意：所以<code>Insert</code>选项中也提供了Markdown格式，我们也可以用上面生成的Markdown结果来使用，但是效果并不好，所以在Confluence中使用专门的生成结果为佳。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter2-5</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-280.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：找回启动日志中的请求路径列表</title>
      <link>https://spring.didispace.com/spring-boot-2/3-6-requestmapping.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-6-requestmapping.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：找回启动日志中的请求路径列表</source>
      <description>Spring Boot 2.x基础教程：找回启动日志中的请求路径列表 如果您看过之前的Spring Boot 1.x教程，或者自己原本就对Spring Boot有一些经验，或者对Spring MVC很熟悉。那么对于Spring构建的Web应用在启动的时候，都会输出当前应用创建的HTTP接口列表。 比如下面的这段日志： 2020-02-11 15:32:39.293 INFO 48395 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-02-11 15:32:39.482 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@7ff95560: startup date [Tue Feb 11 15:32:37 CST 2020]; root of context hierarchy 2020-02-11 15:32:39.568 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/users/{id}],methods=[GET]}&amp;quot; onto public com.didispace.chapter26.User com.didispace.chapter26.UserController.getUser(java.lang.Long) 2020-02-11 15:32:39.569 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/users/{id}],methods=[PUT]}&amp;quot; onto public java.lang.String com.didispace.chapter26.UserController.putUser(java.lang.Long,com.didispace.chapter26.User) 2020-02-11 15:32:39.570 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/users/],methods=[GET]}&amp;quot; onto public java.util.List&amp;lt;com.didispace.chapter26.User&amp;gt; com.didispace.chapter26.UserController.getUserList() 2020-02-11 15:32:39.570 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/users/],methods=[POST]}&amp;quot; onto public java.lang.String com.didispace.chapter26.UserController.postUser(com.didispace.chapter26.User) 2020-02-11 15:32:39.570 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/users/{id}],methods=[DELETE]}&amp;quot; onto public java.lang.String com.didispace.chapter26.UserController.deleteUser(java.lang.Long) 2020-02-11 15:32:39.573 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/error]}&amp;quot; onto public org.springframework.http.ResponseEntity&amp;lt;java.util.Map&amp;lt;java.lang.String, java.lang.Object&amp;gt;&amp;gt; org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2020-02-11 15:32:39.573 INFO 48395 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped &amp;quot;{[/error],produces=[text/html]}&amp;quot; onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2020-02-11 15:32:39.590 INFO 48395 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-02-11 15:32:39.590 INFO 48395 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：找回启动日志中的请求路径列表</h1>
<p>如果您看过之前的Spring Boot 1.x教程，或者自己原本就对Spring Boot有一些经验，或者对Spring MVC很熟悉。那么对于Spring构建的Web应用在启动的时候，都会输出当前应用创建的HTTP接口列表。</p>
<p>比如下面的这段日志：</p>
<div class="language-log line-numbers-mode" data-ext="log"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这些日志接口信息是由<code>org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping</code>类在启动的时候，通过扫描Spring MVC的<code>@Controller</code>、<code>@RequestMapping</code>等注解去发现应用提供的所有接口信息。然后在日志中打印，以方便开发者排查关于接口相关的启动是否正确。</p>
<p>但是，足够仔细的开发者可能已经发现，从Spring Boot 2.1.0版本开始，就不再打印这些信息了，完整的启动日志变的非常少，以<a href="https://blog.didispace.com/spring-boot-learning-21-2-1/" target="_blank" rel="noopener noreferrer">Spring Boot 2.x基础教程：构建RESTful API与单元测试</a>一文的例子为例，可以看到输入内容如下：</p>
<div class="language-log line-numbers-mode" data-ext="log"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 找回日志中请求路径列表</h2>
<p>为什么在Spring Boot 2.1.x版本中不再打印请求路径列表呢？</p>
<p>主要是由于从该版本开始，将这些日志的打印级别做了调整：从原来的<code>INFO</code>调整为<code>TRACE</code>。所以，当我们希望在应用启动的时候打印这些信息的话，只需要在配置文件增增加对<code>RequestMappingHandlerMapping</code>类的打印级别设置即可，比如在<code>application.properties</code>中增加下面这行配置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>在增加了上面的配置之后重启应用，便可以看到如下的日志打印：</p>
<div class="language-log line-numbers-mode" data-ext="log"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到在2.1.x版本之后，除了调整了日志级别之外，对于打印内容也做了调整。现在的打印内容根据接口创建的Controller类做了分类打印，这样更有助于开发者根据自己编写的Controller来查找初始化了那些HTTP接口。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter2-6</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用SpringFox 3生成Swagger文档</title>
      <link>https://spring.didispace.com/spring-boot-2/3-7-swagger-4.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-7-swagger-4.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用SpringFox 3生成Swagger文档</source>
      <description>Spring Boot 2.x基础教程：使用SpringFox 3生成Swagger文档 最近 SpringFox 3.0.0 发布了，距离上一次大版本2.9.2足足有2年多时间了。可能看到这个名字，很多读者会有点陌生。但是，只要给大家看一下这两个依赖，你就知道了！ &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.springfox&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;springfox-swagger2&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;3.0.0&amp;lt;/version&amp;gt; &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt; &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;io.springfox&amp;lt;/groupId&amp;gt; &amp;lt;artifactId&amp;gt;springfox-swagger-ui&amp;lt;/artifactId&amp;gt; &amp;lt;version&amp;gt;3.0.0&amp;lt;/version&amp;gt; &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt; &amp;lt;/dependency&amp;gt;</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用SpringFox 3生成Swagger文档</h1>
<p>最近 SpringFox 3.0.0 发布了，距离上一次大版本2.9.2足足有2年多时间了。可能看到这个名字，很多读者会有点陌生。但是，只要给大家看一下这两个依赖，你就知道了！</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当我们在使用Spring MVC写接口的时候，为了生成API文档，为了方便整合Swagger，都是用这个SpringFox的这套封装。但是，自从2.9.2版本更新之后，就一直没有什么动静，也没有更上Spring Boot的大潮流，有一段时间还一直都是写个配置类来为项目添加文档配置的。为此，之前就造了这么个轮子：</p>
<ul>
<li><a href="https://github.com/SpringForAll/spring-boot-starter-swagger" target="_blank" rel="noopener noreferrer">https://github.com/SpringForAll/spring-boot-starter-swagger</a></li>
</ul>
<p>也没什么难度，就是造的早，所以得到了不少Star。现在SpringFox出了一个starter，看了一下功能，虽然还不完美，但相较于之前我们自己的轮子来说还是好蛮多的。来看看这个版本有些什么亮点：</p>
<ul>
<li>Spring 5，Webflux 支持（仅请求映射支持，尚不支持功能端点）</li>
<li>Spring Integration 支持</li>
<li>Spring Boot 支持 springfox-boot-starter 依赖性（零配置，自动配置支持）</li>
<li>具有自动完成功能的文档化配置属性</li>
<li>更好的规范兼容性</li>
<li>支持 OpenApi 3.0.3</li>
<li>几乎零依赖性（唯一需要的库是 spring-plugin、pswagger-core）</li>
<li>现有的 swagger2 注释将继续有效，并丰富 open API 3.0 规范</li>
</ul>
<p>对于这次的更新，我觉得比较突出的几点：Webflux的支持，目前的轮子就没有做到；对OpenApi 3的支持；以及对Swagger 2的兼容（可以比较方便的做升级了）。</p>
<h2> 上手尝鲜</h2>
<p>说那么多，不如来一发程序实验下更直接！</p>
<p><strong>第一步</strong>：创建一个Spring Boot项目，这里不展开，不会的看以前的教程：<a href="https://blog.didispace.com/spring-boot-learning-21-1-1/" target="_blank" rel="noopener noreferrer">快速入门</a></p>
<p><strong>第二步</strong>：<code>pom.xml</code>中添加依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>现在简洁了不少，一个依赖搞定！</p>
<p><strong>第三步</strong>：应用主类增加注解<code>@EnableOpenApi</code>。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：配置一些接口例子，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第五步</strong>：启动应用！访问swagger页面：<code>http://localhost:8080/swagger-ui/index.html</code></p>
<p><img src="https://static.didispace.com/images/pasted-331.png" alt=""></p>
<blockquote>
<p>注意：</p>
<ol>
<li>这次更新，移除了原来默认的swagger页面路径：<code>http://host/context-path/swagger-ui.html</code>，新增了两个可访问路径：<code>http://host/context-path/swagger-ui/index.html</code>和<code>http://host/context-path/swagger-ui/</code></li>
<li>通过调整日志级别，还可以看到新版本的swagger文档接口也有新增，除了以前老版本的文档接口<code>/v2/api-docs</code>之外，还多了一个新版本的<code>/v3/api-docs</code>接口。</li>
</ol>
</blockquote>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter2-7</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-331.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：如何扩展XML格式的请求和响应</title>
      <link>https://spring.didispace.com/spring-boot-2/3-8-xml.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/3-8-xml.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：如何扩展XML格式的请求和响应</source>
      <description>Spring Boot 2.x基础教程：如何扩展XML格式的请求和响应 在之前的所有Spring Boot教程中，我们都只提到和用到了针对HTML和JSON格式的请求与响应处理。那么对于XML格式的请求要如何快速的在Controller中包装成对象，以及如何以XML的格式返回一个对象呢？ 实现原理：消息转换器（Message Converter） 在扩展上述问题之前，我们先要知道Spring Boot中处理HTTP请求的实现是采用的Spring MVC。而在Spring MVC中有一个消息转换器这个概念，它主要负责处理各种不同格式的请求数据进行处理，并包转换成对象，以提供更好的编程体验。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：如何扩展XML格式的请求和响应</h1>
<p>在之前的所有Spring Boot教程中，我们都只提到和用到了针对HTML和JSON格式的请求与响应处理。那么对于XML格式的请求要如何快速的在Controller中包装成对象，以及如何以XML的格式返回一个对象呢？</p>
<h2> 实现原理：消息转换器（Message Converter）</h2>
<p>在扩展上述问题之前，我们先要知道Spring Boot中处理HTTP请求的实现是采用的Spring MVC。而在Spring MVC中有一个消息转换器这个概念，它主要负责处理各种不同格式的请求数据进行处理，并包转换成对象，以提供更好的编程体验。</p>
<p>在Spring MVC中定义了<code>HttpMessageConverter</code>接口，抽象了消息转换器对类型的判断、对读写的判断与操作，具体可见如下定义：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>众所周知，HTTP请求的Content-Type有各种不同格式定义，如果要支持Xml格式的消息转换，就必须要使用对应的转换器。Spring MVC中默认已经有一套采用Jackson实现的转换器<code>MappingJackson2XmlHttpMessageConverter</code>。</p>
<h2> 扩展实现</h2>
<p><strong>第一步：引入Xml消息转换器</strong></p>
<p>在传统Spring应用中，我们可以通过如下配置加入对Xml格式数据的消息转换实现：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在Spring Boot应用不用像上面这么麻烦，只需要加入<code>jackson-dataformat-xml</code>依赖，Spring Boot就会自动引入<code>MappingJackson2XmlHttpMessageConverter</code>的实现：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>同时，为了配置Xml数据与维护对象属性的关系所要使用的注解也在上述依赖中，所以这个依赖也是必须的。</p>
<p><strong>第二步：定义对象与Xml的关系</strong></p>
<p>做好了基础扩展之后，下面就可以定义Xml内容对应的Java对象了，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中：<code>@Data</code>、<code>@NoArgsConstructor</code>、<code>@AllArgsConstructor</code>是lombok简化代码的注解，主要用于生成get、set以及构造函数。<code>@JacksonXmlRootElement</code>、<code>@JacksonXmlProperty</code>注解是用来维护对象属性在xml中的对应关系。</p>
<p>上述配置的User对象，其可以映射的Xml样例如下（后续可以使用上述xml来请求接口）：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步：创建接收xml请求的接口</strong></p>
<p>完成了要转换的对象之后，可以编写一个接口来接收xml并返回xml，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，启动Spring Boot应用，通过POSTMAN等请求工具，尝试一下这个接口，可以看到请求Xml，并且返回了经过处理后的Xml内容。</p>
<p><img src="https://static.didispace.com/images/pasted-514.png" alt=""></p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter2-8</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-514.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用JdbcTemplate访问MySQL数据库</title>
      <link>https://spring.didispace.com/spring-boot-2/4-1-jdbctemplate.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-1-jdbctemplate.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用JdbcTemplate访问MySQL数据库</source>
      <description>Spring Boot 2.x基础教程：使用JdbcTemplate访问MySQL数据库 在第2章节中，我们介绍了如何通过Spring Boot来实现HTTP接口，以及围绕HTTP接口相关的单元测试、文档生成等实用技能。但是，这些内容还不足以帮助我们构建一个动态应用的服务端程序。不论我们是要做App、小程序、还是传统的Web站点，对于用户的信息、相关业务的内容，通常都需要对其进行存储，而不是像第2章节中那样，把用户信息存储在内存中（重启就丢了！）。 对于信息的存储，现在已经有非常非常多的产品可以选择，其中不乏许多非常优秀的开源免费产品，比如：MySQL，Redis等。接下来，在第3章节，我们将继续学习在使用Spring Boot开发服务端程序的时候，如何实现对各流行数据存储产品的增删改查操作。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用JdbcTemplate访问MySQL数据库</h1>
<p>在第2章节中，我们介绍了如何通过Spring Boot来实现HTTP接口，以及围绕HTTP接口相关的单元测试、文档生成等实用技能。但是，这些内容还不足以帮助我们构建一个动态应用的服务端程序。不论我们是要做App、小程序、还是传统的Web站点，对于用户的信息、相关业务的内容，通常都需要对其进行存储，而不是像第2章节中那样，把用户信息存储在内存中（重启就丢了！）。</p>
<p>对于信息的存储，现在已经有非常非常多的产品可以选择，其中不乏许多非常优秀的开源免费产品，比如：MySQL，Redis等。接下来，在第3章节，我们将继续学习在使用Spring Boot开发服务端程序的时候，如何实现对各流行数据存储产品的增删改查操作。</p>
<p>作为数据访问章节的第一篇，我们将从最为常用的关系型数据库开始。通过一个简单例子，学习在Spring Boot中最基本的数据访问工具：JdbcTemplate。</p>
<h2> 数据源配置</h2>
<p>在我们访问数据库的时候，需要先配置一个数据源，下面分别介绍一下几种不同的数据库配置方式。</p>
<p>首先，为了连接数据库需要引入jdbc支持，在<code>pom.xml</code>中引入如下配置：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 嵌入式数据库支持</h4>
<p>嵌入式数据库通常用于开发和测试环境，不推荐用于生产环境。Spring Boot提供自动配置的嵌入式数据库有H2、HSQL、Derby，你不需要提供任何连接配置就能使用。</p>
<p>比如，我们可以在<code>pom.xml</code>中引入如下配置使用HSQL</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 连接生产数据源</h4>
<p>以MySQL数据库为例，先引入MySQL连接的依赖包，在<code>pom.xml</code>中加入：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在<code>src/main/resources/application.properties</code>中配置数据源信息</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>注意：因为Spring Boot 2.1.x默认使用了MySQL 8.0的驱动，所以这里采用<code>com.mysql.cj.jdbc.Driver</code>，而不是老的<code>com.mysql.jdbc.Driver</code>。</strong></p>
<h4> 连接JNDI数据源</h4>
<p>当你将应用部署于应用服务器上的时候想让数据源由应用服务器管理，那么可以使用如下配置方式引入JNDI数据源。</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 使用JdbcTemplate操作数据库</h2>
<p>Spring的JdbcTemplate是自动配置的，你可以直接使用<code>@Autowired</code>或构造函数（推荐）来注入到你自己的bean中来使用。</p>
<p>下面就来一起完成一个增删改查的例子：</p>
<h4> 准备数据库</h4>
<p>先创建<code>User</code>表，包含属性<code>name</code>、<code>age</code>。可以通过执行下面的建表语句：：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 编写领域对象</h4>
<p>根据数据库中创建的<code>User</code>表，创建领域对象：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里使用了Lombok的<code>@Data</code>和<code>@NoArgsConstructor</code>注解来自动生成各参数的Set、Get函数以及不带参数的构造函数。如果您对Lombok还不了解，可以看看这篇文章：<a href="https://blog.didispace.com/java-lombok-how-to-use/" target="_blank" rel="noopener noreferrer">Java开发神器Lombok的使用与原理</a>。</p>
<h4> 编写数据访问对象</h4>
<ul>
<li>定义包含有插入、删除、查询的抽象接口UserService</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>通过<code>JdbcTemplate</code>实现<code>UserService</code>中定义的数据访问操作</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4> 编写单元测试用例</h4>
<ul>
<li>创建对UserService的单元测试用例，通过创建、删除和查询来验证数据库操作的正确性。</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><em>上面介绍的<code>JdbcTemplate</code>只是最基本的几个操作，更多其他数据访问操作的使用请参考：<a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html" target="_blank" rel="noopener noreferrer">JdbcTemplate API</a></em></p>
<p>通过上面这个简单的例子，我们可以看到在Spring Boot下访问数据库的配置依然秉承了框架的初衷：简单。我们只需要在pom.xml中加入数据库依赖，再到application.properties中配置连接信息，不需要像Spring应用中创建JdbcTemplate的Bean，就可以直接在自己的对象中注入使用。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：2.5版本后数据脚本初始化的变动</title>
      <link>https://spring.didispace.com/spring-boot-2/4-10-25-data-script.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-10-25-data-script.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：2.5版本后数据脚本初始化的变动</source>
      <description>Spring Boot 2.x基础教程：2.5版本后数据脚本初始化的变动 前几天Spring Boot 2.5.0发布了，其中提到了关于Datasource初始化机制的调整，有读者私信想了解这方面做了什么调整。那么今天就要详细说说这个重新设计的配置内容，并结合实际情况说说我的理解和实践建议。 弃用内容 先来纠正一个误区。主要之前在版本更新介绍的时候，存在一些表述上的问题。导致部分读者认为这次的更新是Datasource本身初始化的调整，但其实并不是。这次重新设计的只是对Datasource脚本初始化机制的重新设计。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：2.5版本后数据脚本初始化的变动</h1>
<p>前几天<a href="https://blog.didispace.com/spring-boot-2-5-0-release/" target="_blank" rel="noopener noreferrer">Spring Boot 2.5.0</a>发布了，其中提到了关于Datasource初始化机制的调整，有读者私信想了解这方面做了什么调整。那么今天就要详细说说这个重新设计的配置内容，并结合实际情况说说我的理解和实践建议。</p>
<h2> 弃用内容</h2>
<p>先来纠正一个误区。主要之前在版本更新介绍的时候，存在一些表述上的问题。导致部分读者认为这次的更新是Datasource本身初始化的调整，但其实并不是。这次重新设计的只是对Datasource脚本初始化机制的重新设计。</p>
<p>先来看看这次被弃用部分的内容（位于<code>org.springframework.boot.autoconfigure.jdbc.DataSourceProperties</code>），如果你有用过这些配置内容，那么新配置就很容易理解了。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>对应到配置文件里的属性如下（这里仅列出部分，就不全部列出了，主要就是对应上面源码中的属性）：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这些配置主要用来指定数据源初始化之后要用什么用户、去执行哪些脚本、遇到错误是否继续等功能。</p>
<h2> 新的设计</h2>
<p>Spring Boot 2.5.0开始，启用了全新的配置方式，我们可以从这个类<code>org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties</code>里看到详情。</p>
<p>下面我们通过一个简单的例子来体验这个功能的作用。</p>
<ul>
<li>创建一个Spring Boot的基础应用，并在pom.xml中引入和mysql的依赖：</li>
</ul>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>在配置文件中增加数据源和初始化数据源的配置，具体如下：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>根据上面配置的定义，接下来就在<code>resource</code>目录下，创建脚本文件<code>schema-all.sql</code>，并写入一些初始化表结构的脚本</li>
</ul>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>完成上面步骤之后，启动应用。然后打开MySQL客户端，可以看到在<code>test</code>库下，多了一个<code>user_info</code>表</li>
</ul>
<p>通过上面的例子，不难想到这样的功能主要可以用来管理应用启动与数据库配置的自动执行，以减少应用部署过程中手工执行的内容，降低应用部署的执行步骤。</p>
<h2> 配置详解</h2>
<p>除了上面用到的配置属性之外，还有一些其他的配置，下面详细讲解一下作用。</p>
<ul>
<li><code>spring.sql.init.enabled</code>：是否启动初始化的开关，默认是true。如果不想执行初始化脚本，设置为false即可。通过-D的命令行参数会更容易控制。</li>
<li><code>spring.sql.init.username</code>和<code>spring.sql.init.password</code>：配置执行初始化脚本的用户名与密码。这个非常有必要，因为安全管理要求，通常给业务应用分配的用户对一些建表删表等命令没有权限。这样就可以与datasource中的用户分开管理。</li>
<li><code>spring.sql.init.schema-locations</code>：配置与schema变更相关的sql脚本，可配置多个（默认用<code>;</code>分割）</li>
<li><code>spring.sql.init.data-locations</code>：用来配置与数据相关的sql脚本，可配置多个（默认用<code>;</code>分割）</li>
<li><code>spring.sql.init.encoding</code>：配置脚本文件的编码</li>
<li><code>spring.sql.init.separator</code>：配置多个sql文件的分隔符，默认是<code>;</code></li>
<li><code>spring.sql.init.continue-on-error：如果执行脚本过程中碰到错误是否继续，默认是</code>false`；所以，上面的例子第二次执行的时候会报错并启动失败，因为第一次执行的时候表已经存在。</li>
</ul>
<h2> 应用建议</h2>
<p>关于这些配置的应用，相信聪明的你一定会把它与数据库的版本管理联系起来（因为可以自动的执行脚本）。</p>
<p>那么依靠这些配置，是否可以胜任业务应用部署时候数据库初始化的自动化实现呢？</p>
<p>个人认为就上述所介绍的配置，虽然具备了一定的自动执行能力。但由于缺失对当前环境的判断能力，所以要应对实际的部署场景来说，还是远远不够的。</p>
<p>如果要自动化的管理数据库表结构、初始化数据的话，我的建议是：</p>
<ol>
<li>默认提供的这个初始化功能可以且仅用于单元测试，自动创建数据库结构与初始化数据，使用完毕后销毁。可以方便的控制每次单元测试的执行环境一致。</li>
<li>应用在环境部署的时候，还是要使用之前介绍过的Flyway来实现，如何使用可见之前的分享：<a href="/spring-boot-2/4-8-flyway/" target="blank">使用Flyway来管理数据库版本</a>。</li>
<li>联合Flyway一同使用，通过<code>org.springframework.jdbc.datasource.init.DataSourceInitializer</code>来定义更复杂的执行逻辑。</li>
</ol>
<p>本系列教程<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-13</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>原创不易，如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Data JPA自动生成表时列顺序混乱的解决办法（最新版）</title>
      <link>https://spring.didispace.com/spring-boot-2/4-11-jpa-column-order.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-11-jpa-column-order.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Data JPA自动生成表时列顺序混乱的解决办法（最新版）</source>
      <description>Spring Data JPA自动生成表时列顺序混乱的解决办法（最新版） 最近把Spring Boot的版本升级到了3.3.5，突然发现一个问题：当使用Spring Data JPA自动生成表的时候，所产生的列顺序与Entity类中的变量顺序不一致了。比如，有一个下面这样的Entity： @Data @Entity(name = &amp;quot;t_config&amp;quot;) @EntityListeners(AuditingEntityListener.class) public class Config { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 20) private String itemKey; @Column(length = 200) private String itemValue; @Column(length = 200) private String itemDesc; @CreatedDate private Date createTime; @LastModifiedDate private Date modifyTime; }</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 26 Nov 2024 16:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Data JPA自动生成表时列顺序混乱的解决办法（最新版）</h1>
<p>最近把Spring Boot的版本升级到了3.3.5，突然发现一个问题：当使用Spring Data JPA自动生成表的时候，所产生的列顺序与Entity类中的变量顺序不一致了。比如，有一个下面这样的Entity：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>实际自动创建出来的是这样的：</p>
<p><img src="https://static.didispace.com/images3/dc75cb8b20fbceba2cc0878f52e5a89e.png" alt=""></p>
<p>自动创建的表结构中各个列与Entity类中的变量顺序不一致。其实该问题是一个老生常谈的问题了，在<a href="https://didispace.com" target="_blank" rel="noopener noreferrer">DD</a>这次升级的工程里是有做过解决方案的。只是升级了Spring Boot版本之后，之前的解决方案失效了。</p>
<p>搜索了一番，同时还问了一下AI，发现给出的方案还都是老的解决方案，所以今天特别写一篇来记录下新版本之下，要如何解决这个问题。如果您刚好遇到类似的问题，可以参考本文来解决。</p>
<h2> 老版本解决方案</h2>
<p>新老版本的解决思路是类似的，都是替换Hibernate的实现，下面是老版本的解决步骤：</p>
<ol>
<li>在工程中新建<code>org.hibernate.cfg</code>包</li>
<li>找到<code>hibernate-core</code>包下的<code>org.hibernate.cfg</code>下的<code>PropertyContainer</code>类，复制到本工程的<code>org.hibernate.cfg</code>包下</li>
<li>把<code>PropertyContainer</code>类中定义的<code>persistentAttributeMap</code>类型从<code>TreeMap</code>修改为<code>LinkedHashMap</code></li>
</ol>
<h2> 新版本解决方案</h2>
<p>虽然之前的方案失效了，但思路应该还是对的，所以第一反应是看看当前版本下的<code>PropertyContainer</code>类，具体如下（省略了一些不重要的内容）：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到有两个重要变化部分：</p>
<ol>
<li><code>PropertyContainer</code>类的包名从<code>org.hibernate.cfg</code>改到了<code>org.hibernate.boot.model.internal</code></li>
<li>之前的<code>TreeMap&lt;String XProperty&gt; persistentAttributeMap</code>变量没有了，但多了一个<code>List&lt;XProperty&gt; persistentAttributes</code>。进一步观察这个新变量的处理过程，可以看到如下逻辑：</li>
</ol>
<p><img src="https://static.didispace.com/images3/38851675e83966b7f5c600e71b6bfb67.png" alt=""></p>
<p>所以，新版的方案就以下两个步骤：</p>
<ol>
<li>在工程中新建<code>org.hibernate.boot.model.internal</code>包</li>
<li>找到<code>hibernate-core</code>包下的<code>org.hibernate.boot.model.internal</code>下的<code>PropertyContainer</code>类，复制到本工程的<code>org.hibernate.boot.model.internal</code>包下</li>
<li>把<code>PropertyContainer</code>类中，上面图中红色圈出部门定义的<code>localAttributeMap = new TreeMap&lt;&gt;();</code>修改为<code>localAttributeMap = new LinkedHashMap&lt;&gt;();</code></li>
</ol>
<p>到这里，在新版本中的这个问题就解决了。如果你也遇到了类似的问题，希望本文对你有所帮助。另外，欢迎加入我们的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<p><strong>原创不易，转载或参考请将本文链接加入引用内容或参考资料</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images3/dc75cb8b20fbceba2cc0878f52e5a89e.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：默认数据源Hikari的配置详解</title>
      <link>https://spring.didispace.com/spring-boot-2/4-2-hikari.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-2-hikari.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：默认数据源Hikari的配置详解</source>
      <description>Spring Boot 2.x基础教程：默认数据源Hikari的配置详解 通过上一节的学习，我们已经学会如何应用Spring中的JdbcTemplate来完成对MySQL的数据库读写操作。接下来通过本篇文章，重点说说在访问数据库过程中的一个重要概念：数据源（Data Source），以及Spring Boot中对数据源的创建与配置。 基本概念 在开始说明Spring Boot中的数据源配置之前，我们先搞清楚关于数据访问的这些基本概念：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：默认数据源Hikari的配置详解</h1>
<p>通过<a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">上一节</a>的学习，我们已经学会如何应用Spring中的<code>JdbcTemplate</code>来完成对MySQL的数据库读写操作。接下来通过本篇文章，重点说说在访问数据库过程中的一个重要概念：数据源（Data Source），以及Spring Boot中对数据源的创建与配置。</p>
<h2> 基本概念</h2>
<p>在开始说明Spring Boot中的数据源配置之前，我们先搞清楚关于数据访问的这些基本概念：</p>
<p><strong>什么是JDBC？</strong></p>
<p>Java数据库连接（Java Database Connectivity，简称JDBC）是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口，提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。</p>
<p>JDBC API主要位于JDK中的<code>java.sql</code>包中（之后扩展的内容位于<code>javax.sql</code>包中），主要包括（斜体代表接口，需驱动程序提供者来具体实现）：</p>
<ul>
<li>DriverManager：负责加载各种不同驱动程序（Driver），并根据不同的请求，向调用者返回相应的数据库连接（Connection）。</li>
<li>Driver：驱动程序，会将自身加载到DriverManager中去，并处理相应的请求并返回相应的数据库连接（Connection）。</li>
<li>Connection：数据库连接，负责与进行数据库间通讯，SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。</li>
<li>Statement：用以执行SQL查询和更新（针对静态SQL语句和单次执行）。PreparedStatement：用以执行包含动态参数的SQL查询和更新（在服务器端编译，允许重复执行以提高效率）。</li>
<li>CallableStatement：用以调用数据库中的存储过程。</li>
<li>SQLException：代表在数据库连接的建立和关闭和SQL语句的执行过程中发生了例外情况（即错误）。</li>
</ul>
<p><strong>什么是数据源？</strong></p>
<p>可以看到，在<code>java.sql</code>中并没有数据源（Data Source）的概念。这是由于在<code>java.sql</code>中包含的是JDBC内核API，另外还有个<code>javax.sql</code>包，其中包含了JDBC标准的扩展API。而关于数据源（Data Source）的定义，就在<code>javax.sql</code>这个扩展包中。</p>
<p>实际上，在JDBC内核API的实现下，就已经可以实现对数据库的访问了，那么我们为什么还需要数据源呢？主要出于以下几个目的：</p>
<ol>
<li>封装关于数据库访问的各种参数，实现统一管理</li>
<li>通过对数据库的连接池管理，节省开销并提高效率</li>
</ol>
<p>在Java这个自由开放的生态中，已经有非常多优秀的开源数据源可以供大家选择，比如：DBCP、C3P0、Druid、HikariCP等。</p>
<p>而在Spring Boot 2.x中，对数据源的选择也紧跟潮流，采用了目前性能最佳的<a href="https://github.com/brettwooldridge/HikariCP" target="_blank" rel="noopener noreferrer">HikariCP</a>。接下来，我们就来具体说说，这个Spring Boot中的默认数据源配置。</p>
<h2> 默认数据源：HikariCP</h2>
<p>由于Spring Boot的自动化配置机制，大部分对于数据源的配置都可以通过配置参数的方式去改变。只有一些特殊情况，比如：更换默认数据源，多数据源共存等情况才需要去修改覆盖初始化的Bean内容。本节我们主要讲Hikari的配置，所以对于使用其他数据源或者多数据源的情况，在之后的教程中学习。</p>
<p>在Spring Boot自动化配置中，对于数据源的配置可以分为两类：</p>
<ul>
<li>通用配置：以<code>spring.datasource.*</code>的形式存在，主要是对一些即使使用不同数据源也都需要配置的一些常规内容。比如：数据库链接地址、用户名、密码等。这里就不做过多说明了，通常就这些配置：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>数据源连接池配置：以<code>spring.datasource.&lt;数据源名称&gt;.*</code>的形式存在，比如：Hikari的配置参数就是<code>spring.datasource.hikari.*</code>形式。下面这个是我们最常用的几个配置项及对应说明：</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这些配置的含义：</p>
<ul>
<li><code>spring.datasource.hikari.minimum-idle</code>: 最小空闲连接，默认值10，小于0或大于maximum-pool-size，都会重置为maximum-pool-size</li>
<li><code>spring.datasource.hikari.maximum-pool-size</code>: 最大连接数，小于等于0会被重置为默认值10；大于零小于1会被重置为minimum-idle的值</li>
<li><code>spring.datasource.hikari.idle-timeout</code>: 空闲连接超时时间，默认值600000（10分钟），大于等于max-lifetime且max-lifetime&gt;0，会被重置为0；不等于0且小于10秒，会被重置为10秒。</li>
<li><code>spring.datasource.hikari.max-lifetime</code>: 连接最大存活时间，不等于0且小于30秒，会被重置为默认值30分钟.设置应该比mysql设置的超时时间短</li>
<li><code>spring.datasource.hikari.connection-timeout</code>: 连接超时时间：毫秒，小于250毫秒，否则被重置为默认值30秒</li>
<li><code>spring.datasource.hikari.connection-test-query</code>: 用于测试连接是否可用的查询语句</li>
</ul>
<p>更多完整配置项可查看下表：</p>
<table>
<thead>
<tr>
<th><strong>name</strong></th>
<th><strong>描述</strong></th>
<th><strong>构造器默认值</strong></th>
<th><strong>默认配置validate之后的值</strong></th>
<th><strong>validate重置</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>autoCommit</td>
<td>自动提交从池中返回的连接</td>
<td>TRUE</td>
<td>TRUE</td>
<td>–</td>
</tr>
<tr>
<td>connectionTimeout</td>
<td>等待来自池的连接的最大毫秒数</td>
<td>SECONDS.toMillis(30) = 30000</td>
<td>30000</td>
<td>如果小于250毫秒，则被重置回30秒</td>
</tr>
<tr>
<td>idleTimeout</td>
<td>连接允许在池中闲置的最长时间</td>
<td>MINUTES.toMillis(10) = 600000</td>
<td>600000</td>
<td>如果idleTimeout+1秒&gt;maxLifetime 且 maxLifetime&gt;0，则会被重置为0（代表永远不会退出）；如果idleTimeout!=0且小于10秒，则会被重置为10秒</td>
</tr>
<tr>
<td>maxLifetime</td>
<td>池中连接最长生命周期</td>
<td>MINUTES.toMillis(30) = 1800000</td>
<td>1800000</td>
<td>如果不等于0且小于30秒则会被重置回30分钟</td>
</tr>
<tr>
<td>connectionTestQuery</td>
<td>如果您的驱动程序支持JDBC4，我们强烈建议您不要设置此属性</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>minimumIdle</td>
<td>池中维护的最小空闲连接数</td>
<td>-1</td>
<td>10</td>
<td>minIdle&lt;0或者minIdle&gt;maxPoolSize,则被重置为maxPoolSize</td>
</tr>
<tr>
<td>maximumPoolSize</td>
<td>池中最大连接数，包括闲置和使用中的连接</td>
<td>-1</td>
<td>10</td>
<td>如果maxPoolSize小于1，则会被重置。当minIdle&lt;=0被重置为DEFAULT_POOL_SIZE则为10;如果minIdle&gt;0则重置为minIdle的值</td>
</tr>
<tr>
<td>metricRegistry</td>
<td>该属性允许您指定一个 Codahale / Dropwizard MetricRegistry 的实例，供池使用以记录各种指标</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>healthCheckRegistry</td>
<td>该属性允许您指定池使用的Codahale / Dropwizard HealthCheckRegistry的实例来报告当前健康信息</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>poolName</td>
<td>连接池的用户定义名称，主要出现在日志记录和JMX管理控制台中以识别池和池配置</td>
<td>null</td>
<td>HikariPool-1</td>
<td>–</td>
</tr>
<tr>
<td>initializationFailTimeout</td>
<td>如果池无法成功初始化连接，则此属性控制池是否将 fail fast</td>
<td>1</td>
<td>1</td>
<td>–</td>
</tr>
<tr>
<td>isolateInternalQueries</td>
<td>是否在其自己的事务中隔离内部池查询，例如连接活动测试</td>
<td>FALSE</td>
<td>FALSE</td>
<td>–</td>
</tr>
<tr>
<td>allowPoolSuspension</td>
<td>控制池是否可以通过JMX暂停和恢复</td>
<td>FALSE</td>
<td>FALSE</td>
<td>–</td>
</tr>
<tr>
<td>readOnly</td>
<td>从池中获取的连接是否默认处于只读模式</td>
<td>FALSE</td>
<td>FALSE</td>
<td>–</td>
</tr>
<tr>
<td>registerMbeans</td>
<td>是否注册JMX管理Bean（MBeans）</td>
<td>FALSE</td>
<td>FALSE</td>
<td>–</td>
</tr>
<tr>
<td>catalog</td>
<td>为支持 catalog 概念的数据库设置默认 catalog</td>
<td>driver default</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>connectionInitSql</td>
<td>该属性设置一个SQL语句，在将每个新连接创建后，将其添加到池中之前执行该语句。</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>driverClassName</td>
<td>HikariCP将尝试通过仅基于jdbcUrl的DriverManager解析驱动程序，但对于一些较旧的驱动程序，还必须指定driverClassName</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>transactionIsolation</td>
<td>控制从池返回的连接的默认事务隔离级别</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>validationTimeout</td>
<td>连接将被测试活动的最大时间量</td>
<td>SECONDS.toMillis(5) = 5000</td>
<td>5000</td>
<td>如果小于250毫秒，则会被重置回5秒</td>
</tr>
<tr>
<td>leakDetectionThreshold</td>
<td>记录消息之前连接可能离开池的时间量，表示可能的连接泄漏</td>
<td>0</td>
<td>0</td>
<td>如果大于0且不是单元测试，则进一步判断：(leakDetectionThreshold &lt; SECONDS.toMillis(2) or (leakDetectionThreshold &gt; maxLifetime &amp;&amp; maxLifetime &gt; 0)，会被重置为0 . 即如果要生效则必须&gt;0，而且不能小于2秒，而且当maxLifetime &gt; 0时不能大于maxLifetime</td>
</tr>
<tr>
<td>dataSource</td>
<td>这个属性允许你直接设置数据源的实例被池包装，而不是让HikariCP通过反射来构造它</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>schema</td>
<td>该属性为支持模式概念的数据库设置默认模式</td>
<td>driver default</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>threadFactory</td>
<td>此属性允许您设置将用于创建池使用的所有线程的java.util.concurrent.ThreadFactory的实例。</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
<tr>
<td>scheduledExecutor</td>
<td>此属性允许您设置将用于各种内部计划任务的java.util.concurrent.ScheduledExecutorService实例</td>
<td>null</td>
<td>null</td>
<td>–</td>
</tr>
</tbody>
</table>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-2</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用国产数据库连接池Druid</title>
      <link>https://spring.didispace.com/spring-boot-2/4-3-druid.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-3-druid.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用国产数据库连接池Druid</source>
      <description>Spring Boot 2.x基础教程：使用国产数据库连接池Druid 上一节，我们介绍了Spring Boot在JDBC模块中自动化配置使用的默认数据源HikariCP。接下来这一节，我们将介绍另外一个被广泛应用的开源数据源：Druid。 Druid是由阿里巴巴数据库事业部出品的开源项目。它除了是一个高性能数据库连接池之外，更是一个自带监控的数据库连接池。虽然HikariCP已经很优秀，但是对于国内用户来说，可能对于Druid更为熟悉。所以，对于如何在Spring Boot中使用Druid是后端开发人员必须要掌握的基本技能。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用国产数据库连接池Druid</h1>
<p><a href="/spring-boot-2/4-2-hikari/" target="blank">上一节</a>，我们介绍了Spring Boot在JDBC模块中自动化配置使用的默认数据源HikariCP。接下来这一节，我们将介绍另外一个被广泛应用的开源数据源：Druid。</p>
<p><a href="https://github.com/alibaba/druid" target="_blank" rel="noopener noreferrer">Druid</a>是由阿里巴巴数据库事业部出品的开源项目。它除了是一个高性能数据库连接池之外，更是一个自带监控的数据库连接池。虽然HikariCP已经很优秀，但是对于国内用户来说，可能对于Druid更为熟悉。所以，对于如何在Spring Boot中使用Druid是后端开发人员必须要掌握的基本技能。</p>
<h2> 配置Druid数据源</h2>
<p>这一节的实践我们将基于<a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">《JdbcTemplate访问MySQL数据库》</a>一文的代码基础上进行。所以，读者可以从文末的代码仓库中，检出<code>chapter3-1</code>目录来进行下面的实践学习。</p>
<p>下面我们就来开始对Spring Boot项目配置Druid数据源：</p>
<p><strong>第一步</strong>：在<code>pom.xml</code>中引入druid官方提供的Spring Boot Starter封装。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：在<code>application.properties</code>中配置数据库连接信息。</p>
<p>Druid的配置都以<code>spring.datasource.druid</code>作为前缀，所以根据之前的配置，稍作修改即可：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：配置Druid的连接池。</p>
<p>与Hikari一样，要用好一个数据源，就要对其连接池做好相应的配置，比如下面这样：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>关于Druid中各连接池配置的说明可查阅下面的表格：</p>
<table>
<thead>
<tr>
<th>配置</th>
<th>缺省值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td></td>
<td>配置这个属性的意义在于，如果存在多个数据源，监控的时候可以通过名字来区分开来。如果没有配置，将会生成一个名字，格式是："DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的，强行设置name会出错。<a href="http://blog.csdn.net/lanmo555/article/details/41248763" target="_blank" rel="noopener noreferrer">详情-点此处</a>。</td>
</tr>
<tr>
<td>url</td>
<td></td>
<td>连接数据库的url，不同数据库不一样。例如： mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto</td>
</tr>
<tr>
<td>username</td>
<td></td>
<td>连接数据库的用户名</td>
</tr>
<tr>
<td>password</td>
<td></td>
<td>连接数据库的密码。如果你不希望密码直接写在配置文件中，可以使用ConfigFilter。<a href="https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter" target="_blank" rel="noopener noreferrer">详细看这里</a></td>
</tr>
<tr>
<td>driverClassName</td>
<td>根据url自动识别</td>
<td>这一项可配可不配，如果不配置druid会根据url自动识别dbType，然后选择相应的driverClassName</td>
</tr>
<tr>
<td>initialSize</td>
<td>0</td>
<td>初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时</td>
</tr>
<tr>
<td>maxActive</td>
<td>8</td>
<td>最大连接池数量</td>
</tr>
<tr>
<td>maxIdle</td>
<td>8</td>
<td>已经不再使用，配置了也没效果</td>
</tr>
<tr>
<td>minIdle</td>
<td></td>
<td>最小连接池数量</td>
</tr>
<tr>
<td>maxWait</td>
<td></td>
<td>获取连接时最大等待时间，单位毫秒。配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，如果需要可以通过配置useUnfairLock属性为true使用非公平锁。</td>
</tr>
<tr>
<td>poolPreparedStatements</td>
<td>false</td>
<td>是否缓存preparedStatement，也就是PSCache。PSCache对支持游标的数据库性能提升巨大，比如说oracle。在mysql下建议关闭。</td>
</tr>
<tr>
<td>maxPoolPreparedStatementPerConnectionSize</td>
<td>-1</td>
<td>要启用PSCache，必须配置大于0，当大于0时，poolPreparedStatements自动触发修改为true。在Druid中，不会存在Oracle下PSCache占用内存过多的问题，可以把这个数值配置大一些，比如说100</td>
</tr>
<tr>
<td>validationQuery</td>
<td></td>
<td>用来检测连接是否有效的sql，要求是一个查询语句，常用select 'x'。如果validationQuery为null，testOnBorrow、testOnReturn、testWhileIdle都不会起作用。</td>
</tr>
<tr>
<td>validationQueryTimeout</td>
<td></td>
<td>单位：秒，检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法</td>
</tr>
<tr>
<td>testOnBorrow</td>
<td>true</td>
<td>申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。</td>
</tr>
<tr>
<td>testOnReturn</td>
<td>false</td>
<td>归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。</td>
</tr>
<tr>
<td>testWhileIdle</td>
<td>false</td>
<td>建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。</td>
</tr>
<tr>
<td>keepAlive</td>
<td>false （1.0.28）</td>
<td>连接池中的minIdle数量以内的连接，空闲时间超过minEvictableIdleTimeMillis，则会执行keepAlive操作。</td>
</tr>
<tr>
<td>timeBetweenEvictionRunsMillis</td>
<td>1分钟（1.0.14）</td>
<td>有两个含义： 1) Destroy线程会检测连接的间隔时间，如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据，详细看testWhileIdle属性的说明</td>
</tr>
<tr>
<td>numTestsPerEvictionRun</td>
<td>30分钟（1.0.14）</td>
<td>不再使用，一个DruidDataSource只支持一个EvictionRun</td>
</tr>
<tr>
<td>minEvictableIdleTimeMillis</td>
<td></td>
<td>连接保持空闲而不被驱逐的最小时间</td>
</tr>
<tr>
<td>connectionInitSqls</td>
<td></td>
<td>物理连接初始化的时候执行的sql</td>
</tr>
<tr>
<td>exceptionSorter</td>
<td>根据dbType自动识别</td>
<td>当数据库抛出一些不可恢复的异常时，抛弃连接</td>
</tr>
<tr>
<td>filters</td>
<td></td>
<td>属性类型是字符串，通过别名的方式配置扩展插件，常用的插件有： 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall</td>
</tr>
<tr>
<td>proxyFilters</td>
<td></td>
<td>类型是List&lt;com.alibaba.druid.filter.Filter&gt;，如果同时配置了filters和proxyFilters，是组合关系，并非替换关系</td>
</tr>
</tbody>
</table>
<p>到这一步，就已经完成了将Spring Boot的默认数据源HikariCP切换到Druid的所有操作。</p>
<h2> 配置Druid监控</h2>
<p>既然用了Druid，那么对于Druid的监控功能怎么能不用一下呢？下面就来再进一步做一些配置，来启用Druid的监控。</p>
<p><strong>第一步</strong>：在<code>pom.xml</code>中引入<code>spring-boot-starter-actuator</code>模块</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：在<code>application.properties</code>中添加Druid的监控配置。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上面的配置主要用于开启stat监控统计的界面以及监控内容的相关配置，具体释意如下：</p>
<ul>
<li><code>spring.datasource.druid.stat-view-servlet.url-pattern</code>：访问地址规则</li>
<li><code>spring.datasource.druid.stat-view-servlet.reset-enable</code>：是否允许清空统计数据</li>
<li><code>spring.datasource.druid.stat-view-servlet.login-username</code>：监控页面的登录账户</li>
<li><code>spring.datasource.druid.stat-view-servlet.login-password</code>：监控页面的登录密码</li>
</ul>
<p><strong>第三步</strong>：针对之前实现的<code>UserService</code>内容，我们创建一个Controller来通过接口去调用数据访问操作：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：完成上面所有配置之后，启动应用，访问Druid的监控页面<code>http://localhost:8080/druid/</code>，可以看到如下登录页面：</p>
<p><img src="https://static.didispace.com/images/pasted-311.png" alt=""></p>
<p>输入上面<code>spring.datasource.druid.stat-view-servlet.login-username</code>和<code>spring.datasource.druid.stat-view-servlet.login-password</code>配置的登录账户与密码，就能看到如下监控页面：</p>
<p><img src="https://static.didispace.com/images/pasted-310.png" alt=""></p>
<p>进入到这边时候，就可以看到对于应用端而言的各种监控数据了。这里讲解几个最为常用的监控页面：</p>
<p><strong>数据源</strong>：这里可以看到之前我们配置的数据库连接池信息以及当前使用情况的各种指标。</p>
<p><img src="https://static.didispace.com/images/pasted-312.png" alt=""></p>
<p><strong>SQL监控</strong>：该数据源中执行的SQL语句极其统计数据。在这个页面上，我们可以很方便的看到当前这个Spring Boot都执行过哪些SQL，这些SQL的执行频率和执行效率也都可以清晰的看到。如果你这里没看到什么数据？别忘了我们之前创建了一个Controller，用这些接口可以触发UserService对数据库的操作。所以，这里我们可以通过调用接口的方式去触发一些操作，这样SQL监控页面就会产生一些数据：</p>
<p><img src="https://static.didispace.com/images/pasted-313.png" alt=""></p>
<p>图中监控项上，执行时间、读取行数、更新行数都通过区间分布的方式表示，将耗时分布成8个区间：</p>
<ul>
<li>0 - 1 耗时0到1毫秒的次数</li>
<li>1 - 10 耗时1到10毫秒的次数</li>
<li>10 - 100 耗时10到100毫秒的次数</li>
<li>100 - 1,000 耗时100到1000毫秒的次数</li>
<li>1,000 - 10,000 耗时1到10秒的次数</li>
<li>10,000 - 100,000 耗时10到100秒的次数</li>
<li>100,000 - 1,000,000 耗时100到1000秒的次数</li>
<li>1,000,000 - 耗时1000秒以上的次数</li>
</ul>
<p>记录耗时区间的发生次数，通过区分分布，可以很方便看出SQL运行的极好、普通和极差的分布。 耗时区分分布提供了“执行+RS时分布”，是将执行时间+ResultSet持有时间合并监控，这个能方便诊断返回行数过多的查询。</p>
<p><strong>SQL防火墙</strong>：该页面记录了与SQL监控不同维度的监控数据，更多用于对表访问维度、SQL防御维度的统计。</p>
<p><img src="https://static.didispace.com/images/pasted-314.png" alt=""></p>
<p>该功能数据记录的统计需要在<code>spring.datasource.druid.filters</code>中增加<code>wall</code>属性才会进行记录统计，比如这样：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>注意</strong>：这里的所有监控信息是对这个应用实例的数据源而言的，而并不是数据库全局层面的，可以视为应用层的监控，不可能作为中间件层的监控。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-311.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用Spring Data JPA访问MySQL</title>
      <link>https://spring.didispace.com/spring-boot-2/4-4-spring-data-jpa.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-4-spring-data-jpa.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用Spring Data JPA访问MySQL</source>
      <description>Spring Boot 2.x基础教程：使用Spring Data JPA访问MySQL 在数据访问这章的第一篇文章《JdbcTemplate访问MySQL数据库》中，我们已经介绍了如何使用Spring Boot中最基本的jdbc模块来实现关系型数据库的数据读写操作。那么结合Web开发一章的内容，我们就可以利用JDBC模块与Web模块的功能，综合着使用来完成一个适用于很多简单应用场景的后端应用了。 然而当我们有一定的开发经验之后，不难发现，在实际开发过程中，对数据库的操作大多可以归结为：“增删改查”。就最为普遍的单表操作而言，除了表和字段不同外，语句几乎都是类似的，开发人员需要写大量类似而枯燥的语句来完成业务逻辑。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用Spring Data JPA访问MySQL</h1>
<p>在数据访问这章的第一篇文章<a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">《JdbcTemplate访问MySQL数据库》</a>中，我们已经介绍了如何使用Spring Boot中最基本的jdbc模块来实现关系型数据库的数据读写操作。那么结合Web开发一章的内容，我们就可以利用JDBC模块与Web模块的功能，综合着使用来完成一个适用于很多简单应用场景的后端应用了。</p>
<p>然而当我们有一定的开发经验之后，不难发现，在实际开发过程中，对数据库的操作大多可以归结为：“增删改查”。就最为普遍的单表操作而言，除了表和字段不同外，语句几乎都是类似的，开发人员需要写大量类似而枯燥的语句来完成业务逻辑。</p>
<p>为了解决这些大量枯燥的数据操作语句，诞生了非常多的优秀框架，比如：Hibernate。通过整合Hibernate，我们能够以操作Java实体的方式来完成对数据的操作，通过框架的帮助，对Java实体的变更最终将自动地映射到数据库表中。</p>
<p>在Hibernate的帮助下，Java实体映射到数据库表数据完成之后，再进一步解决抽象各个Java实体基本的“增删改查”操作，我们通常会以泛型的方式封装一个模板Dao来进行抽象简化，但是这样依然不是很方便，我们需要针对每个实体编写一个继承自泛型模板Dao的接口，再编写该接口的实现。虽然一些基础的数据访问已经可以得到很好的复用，但是在代码结构上针对每个实体都会有一堆Dao的接口和实现。</p>
<p>由于模板Dao的实现，使得这些具体实体的Dao层已经变的非常“薄”，有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理，并且往往这样的实现类可能会出现在很多实体上。Spring Data JPA的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式。比如，下面的例子：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>我们只需要通过编写一个继承自<code>JpaRepository</code>的接口就能完成数据访问，下面以一个具体实例来体验Spring Data JPA给我们带来的强大功能。</p>
<h2> 使用步骤</h2>
<p>由于Spring Data JPA依赖于Hibernate。如果您对Hibernate有一定了解，下面内容可以毫不费力的看懂并上手使用它。如果您还是Hibernate新手，您可以先按如下方式入门，再建议回头学习一下Hibernate以帮助这部分的理解和进一步使用。</p>
<h4> 工程配置</h4>
<p>在<code>pom.xml</code>中添加相关依赖，加入以下内容：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在<code>application.xml</code>中配置：数据库连接信息（如使用嵌入式数据库则不需要）、自动创建表结构的设置，例如使用mysql的情况如下：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>spring.jpa.properties.hibernate.hbm2ddl.auto</code>是hibernate的配置属性，其主要作用是：自动创建、更新、验证数据库表结构。该参数的几种配置如下：</p>
<ul>
<li><code>create</code>：每次加载hibernate时都会删除上一次的生成的表，然后根据你的model类再重新来生成新表，哪怕两次没有任何改变也要这样执行，这就是导致数据库表数据丢失的一个重要原因。</li>
<li><code>create-drop</code>：每次加载hibernate时根据model类生成表，但是sessionFactory一关闭,表就自动删除。</li>
<li><code>update</code>：最常用的属性，第一次加载hibernate时根据model类会自动建立起表的结构（前提是先建立好数据库），以后加载hibernate时根据model类自动更新表结构，即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后，表结构是不会被马上建立起来的，是要等应用第一次运行起来后才会。</li>
<li><code>validate</code>：每次加载hibernate时，验证创建数据库表结构，只会和数据库中的表进行比较，不会创建新表，但是会插入新值。</li>
</ul>
<p>至此已经完成基础配置，如果您有在Spring下整合使用过它的话，相信你已经感受到Spring Boot的便利之处：JPA的传统配置在<code>persistence.xml</code>文件中，但是这里我们不需要。当然，最好在构建项目时候按照之前提过的<a href="https://blog.didispace.com/spring-boot-learning-21-1-2/" target="_blank" rel="noopener noreferrer">最佳实践的工程结构</a>来组织，这样以确保各种配置都能被框架扫描到。</p>
<h4> 创建实体</h4>
<p>创建一个User实体，包含id（主键）、name（姓名）、age（年龄）属性，通过ORM框架其会被映射到数据库表中，由于配置了<code>hibernate.hbm2ddl.auto</code>，在应用启动的时候框架会自动去数据库中创建对应的表。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>@Entity</code>注解标识了User类是一个持久化的实体</li>
<li><code>@Data</code>和<code>@NoArgsConstructor</code>是Lombok中的注解。用来自动生成各参数的Set、Get函数以及不带参数的构造函数。如果您对Lombok还不了解，可以看看这篇文章：<a href="https://blog.didispace.com/java-lombok-how-to-use/" target="_blank" rel="noopener noreferrer">Java开发神器Lombok的使用与原理</a></li>
<li><code>@Id</code>和<code>@GeneratedValue</code>用来标识User对应对应数据库表中的主键</li>
</ul>
<p><strong>注意</strong>：除了这些注解之外，还有很多用来精细化配置映射关系的注解，这里不做具体介绍。后续会出专门一篇来介绍常用注解。读者也可以自行阅读Hibernate的文档来学习这些注解的详细使用方法。</p>
<h4> 创建数据访问接口</h4>
<p>下面针对User实体创建对应的<code>Repository</code>接口实现对该实体的数据访问，如下代码：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在Spring Data JPA中，只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类，直接减少了我们的文件清单。</p>
<p>下面对上面的<code>UserRepository</code>做一些解释，该接口继承自<code>JpaRepository</code>，通过查看<code>JpaRepository</code>接口的<a href="http://docs.spring.io/spring-data/data-jpa/docs/current/api/" target="_blank" rel="noopener noreferrer">API文档</a>，可以看到该接口本身已经实现了创建（save）、更新（save）、删除（delete）、查询（findAll、findOne）等基本操作的函数，因此对于这些基础操作的数据访问就不需要开发者再自己定义。</p>
<p>在我们实际开发中，<code>JpaRepository</code>接口定义的接口往往还不够或者性能不够优化，我们需要进一步实现更复杂一些的查询或操作。由于本文重点在Spring Boot中整合spring-data-jpa，在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能，后续再单独开篇讲一下spring-data-jpa中的常见使用。</p>
<p>在上例中，我们可以看到下面两个函数：</p>
<ul>
<li><code>User findByName(String name)</code></li>
<li><code>User findByNameAndAge(String name, Integer age)</code></li>
</ul>
<p>它们分别实现了按name查询User实体和按name和age查询User实体，可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性：<strong>通过解析方法名创建查询</strong>。</p>
<p>除了通过解析方法名来创建查询外，它也提供通过使用@Query 注解来创建查询，您只需要编写JPQL语句，并通过类似“:name”来映射@Param指定的参数，就像例子中的第三个findUser函数一样。</p>
<p><strong>Spring Data JPA的能力远不止本文提到的这些，由于本文主要以整合介绍为主，对于Spring Data JPA的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开，这里先挖个坑，后续再补文章填坑，如您对这些感兴趣可以关注我博客或简书，同样欢迎大家留言交流想法。</strong></p>
<h4> 单元测试</h4>
<p>在完成了上面的数据访问接口之后，按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍，主要通过数据操作和查询来反复验证操作的正确性。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 拓展阅读：关于Spring Data</h2>
<p>Spring Data JPA在Spring家族中实际上是一个二级项目，它隶属于<a href="https://spring.io/projects/spring-data" target="_blank" rel="noopener noreferrer">Spring Data</a>这个顶级项目。读者可以看一下关于这个项目的介绍，它除了涵盖对关系型数据库的抽象之外，其实还有很多对其他数据存储中间件的实现，比如我们常用的Redis、MongoDB、Elasticsearch等。</p>
<p>如果再找几个项目看一下它们的简单示例，你会发现：不论你是要访问什么数据存储产品，它们的编码方式几乎都是一样的！这就是Spring Data这个项目充满魅力的地方！通过对数据访问操作的抽象来屏蔽细节，用不同子项目的方式去实现细节。让开发者只需要学会使用Spring Data，就能方便快捷的学会对各种数据存储的操作。所以，对于Spring Data，我是强烈推荐Java开发者们可以学、甚至读一下源码的重要框架。虽然，目前来说很多大型互联网公司并不会选择它（性能考量居多，能真正用好它的人不多）作为主要的开发框架，但是其背后的抽象思想是非常值得我们学习的。并且，在做一些非高并发项目的时候，这简直就是一个快捷开发神器，它可以帮助我们少写非常多的代码！</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-4</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用MyBatis的XML配置方式</title>
      <link>https://spring.didispace.com/spring-boot-2/4-5-mybatis-xml.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-5-mybatis-xml.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用MyBatis的XML配置方式</source>
      <description>Spring Boot 2.x基础教程：使用MyBatis的XML配置方式 上一篇我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问。但是上一篇中使用了注解方式来实现，而对于很多MyBatis老用户还是习惯于XML的开发方式，所以这篇，我们就来看看如何使用XML的方式来进行开发。 动手试试 本篇将不具体介绍整合MyBatis的基础内容，读者可以阅读上一篇：Spring Boot 2.x基础教程：使用MyBatis访问MySQL来了解该部分内容。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用MyBatis的XML配置方式</h1>
<p><a href="/spring-boot-2/4-5-mybatis/" target="blank">上一篇</a>我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问。但是上一篇中使用了注解方式来实现，而对于很多MyBatis老用户还是习惯于XML的开发方式，所以这篇，我们就来看看如何使用XML的方式来进行开发。</p>
<h2> 动手试试</h2>
<p>本篇将不具体介绍整合MyBatis的基础内容，读者可以阅读<a href="/spring-boot-2/4-5-mybatis/" target="blank">上一篇：Spring Boot 2.x基础教程：使用MyBatis访问MySQL</a>来了解该部分内容。</p>
<p>下面的实操部分将基于上一篇的例子之后进行，基础工程可通过文末仓库中的<code>chapter3-5</code>目录获取。</p>
<p><strong>第一步</strong>：在应用主类中增加mapper的扫描包配置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：在第一步中指定的Mapper包下创建User表的Mapper定义：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：在配置文件中通过<code>mybatis.mapper-locations</code>参数指定xml配置的位置：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>第四步</strong>：在第三步中指定的xml配置目录下创建User表的mapper配置：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>到这里从注解方式的MyBatis使用方式就改为了XML的配置方式了，为了验证是否运行正常，可以通过下面的单元测试来尝试对数据库的写和读操作：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果您在尝试没有成功，建议通过文末仓库查看完成代码，对比是否有所遗漏与疏忽。</p>
<p><strong>更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-6</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用MyBatis访问MySQL</title>
      <link>https://spring.didispace.com/spring-boot-2/4-5-mybatis.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-5-mybatis.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用MyBatis访问MySQL</source>
      <description>Spring Boot 2.x基础教程：使用MyBatis访问MySQL 之前我们已经介绍了两种在Spring Boot中访问关系型数据库的方式： 使用spring-boot-starter-jdbc 使用spring-boot-starter-data-jpa</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用MyBatis访问MySQL</h1>
<p>之前我们已经介绍了两种在Spring Boot中访问关系型数据库的方式：</p>
<ul>
<li><a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">使用spring-boot-starter-jdbc</a></li>
<li><a href="/spring-boot-2/4-4-spring-data-jpa/" target="blank">使用spring-boot-starter-data-jpa</a></li>
</ul>
<p>虽然Spring Data JPA在国外广泛流行，但是在国内还是MyBatis的天下。所以，今天这篇我们将具体说说如何在Spring Boot中整合MyBatis完成关系型数据库的增删改查操作。</p>
<h2> 整合MyBatis</h2>
<p><strong>第一步</strong>：新建Spring Boot项目，在<code>pom.xml</code>中引入MyBatis的Starter以及MySQL Connector依赖，具体如下：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>关于<code>mybatis-spring-boot-starter</code>的版本需要注意：</p>
<ul>
<li><code>2.1.x</code>版本适用于：MyBatis 3.5+、Java 8+、Spring Boot 2.1+</li>
<li><code>2.0.x</code>版本适用于：MyBatis 3.5+、Java 8+、Spring Boot 2.0/2.1</li>
<li><code>1.3.x</code>版本适用于：MyBatis 3.4+、Java 6+、Spring Boot 1.5</li>
</ul>
<p>其中，目前还在维护的是<code>2.1.x</code>版本和<code>1.3.x</code>版本。</p>
<p><strong>第二步</strong>：同之前介绍的使用jdbc模块和jpa模块连接数据库一样，在<code>application.properties</code>中配置mysql的连接配置</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当然，也可以不用默认数据源，如果要使用Druid作为数据库连接池的话，可以参见<a href="/spring-boot-2/4-3-druid">《Spring Boot 2.x：使用国产数据库连接池Druid》</a>一文。</p>
<p><strong>第三步</strong>：Mysql中创建一张用来测试的表，比如：User表，其中包含id(BIGINT)、age(INT)、name(VARCHAR)字段。</p>
<p>具体创建命令如下：</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：创建User表的映射对象User：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第五步</strong>：创建User表的操作接口：UserMapper。在接口中定义两个数据操作，一个插入，一个查询，用于后续单元测试验证。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第六步</strong>：创建Spring Boot主类</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第七步</strong>：创建单元测试。具体测试逻辑如下：</p>
<ul>
<li>插入一条name=AAA，age=20的记录，然后根据name=AAA查询，并判断age是否为20</li>
<li>测试结束回滚数据，保证测试单元每次运行的数据环境独立</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 注解配置说明</h2>
<p>下面通过几种不同传参方式来实现前文中实现的插入操作，来学习一下MyBatis中常用的一些注解。</p>
<h3> 使用@Param</h3>
<p>在之前的整合示例中我们已经使用了这种最简单的传参方式，如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>这种方式很好理解，<code>@Param</code>中定义的<code>name</code>对应了SQL中的<code>#{name}</code>，<code>age</code>对应了SQL中的<code>#{age}</code>。</p>
<h3> 使用Map</h3>
<p>如下代码，通过<code>Map&lt;String, Object&gt;</code>对象来作为传递参数的容器：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>对于Insert语句中需要的参数，我们只需要在map中填入同名的内容即可，具体如下面代码所示：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 使用对象</h3>
<p>除了Map对象，我们也可直接使用普通的Java对象来作为查询条件的传参，比如我们可以直接使用User对象:</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>这样语句中的<code>#{name}</code>、<code>#{age}</code>就分别对应了User对象中的<code>name</code>和<code>age</code>属性。</p>
<h3> 增删改查</h3>
<p>MyBatis针对不同的数据库操作分别提供了不同的注解来进行配置，在之前的示例中演示了<code>@Insert</code>，下面针对User表做一组最基本的增删改查作为示例：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在完成了一套增删改查后，不妨我们试试下面的单元测试来验证上面操作的正确性：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3> 返回结果绑定</h3>
<p>对于增、删、改操作相对变化较小。而对于“查”操作，我们往往需要进行多表关联，汇总计算等操作，那么对于查询的结果往往就不再是简单的实体对象了，往往需要返回一个与数据库实体不同的包装类，那么对于这类情况，就可以通过<code>@Results</code>和<code>@Result</code>注解来进行绑定，具体如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在上面代码中，@Result中的property属性对应User对象中的成员名，column对应SELECT出的字段名。在该配置中故意没有查出id属性，只对User对应中的name和age对象做了映射配置，这样可以通过下面的单元测试来验证查出的id为null，而其他属性不为null：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>本文主要介绍几种最为常用的方式，更多其他注解的使用可<a href="http://www.mybatis.org/mybatis-3/zh/java-api.html" target="_blank" rel="noopener noreferrer">参见文档</a>，下一篇我们将介绍如何使用XML配置SQL的传统使用方式。</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-5</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置</title>
      <link>https://spring.didispace.com/spring-boot-2/4-6-multi-jdbctemplate.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-6-multi-jdbctemplate.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置</source>
      <description>Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置 在本系列之前的教程中，我们已经介绍了如何使用目前最常用的三种数据访问方式： JdbcTemplate Spring Data JPA MyBatis</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置</h1>
<p>在本系列之前的教程中，我们已经介绍了如何使用目前最常用的三种数据访问方式：</p>
<ul>
<li><a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">JdbcTemplate</a></li>
<li><a href="/spring-boot-2/4-4-spring-data-jpa/" target="blank">Spring Data JPA</a></li>
<li><a href="/spring-boot-2/4-5-mybatis/" target="blank">MyBatis</a></li>
</ul>
<p>下面我们将分三篇来介绍在这三种数据访问方式之下，当我们需要多个数据源的时候，该如何使用的配置说明。</p>
<h2> 添加多数据源的配置</h2>
<p>先在Spring Boot的配置文件<code>application.properties</code>中设置两个你要链接的数据库配置，比如这样：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ol>
<li>多数据源配置的时候，与单数据源不同点在于<code>spring.datasource</code>之后多设置一个数据源名称<code>primary</code>和<code>secondary</code>来区分不同的数据源配置，这个前缀将在后续初始化数据源的时候用到。</li>
<li>数据源连接配置2.x和1.x的配置项是有区别的：2.x使用<code>spring.datasource.secondary.jdbc-url</code>，而1.x版本使用<code>spring.datasource.secondary.url</code>。如果你在配置的时候发生了这个报错<code>java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.</code>，那么就是这个配置项的问题。</li>
</ol>
<h2> 初始化数据源与JdbcTemplate</h2>
<p>完成多数据源的配置信息之后，就来创建个配置类来加载这些配置信息，初始化数据源，以及初始化每个数据源要用的JdbcTemplate。你只需要在你的Spring Boot应用下添加下面的这个配置类即可完成！</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ol>
<li>前两个Bean是数据源的创建，通过<code>@ConfigurationProperties</code>可以知道这两个数据源分别加载了<code>spring.datasource.primary.*</code>和<code>spring.datasource.secondary.*</code>的配置。</li>
<li><code>@Primary</code>注解指定了主数据源，就是当我们不特别指定哪个数据源的时候，就会使用这个Bean</li>
<li>后两个Bean是每个数据源对应的<code>JdbcTemplate</code>。可以看到这两个<code>JdbcTemplate</code>创建的时候，分别注入了<code>primaryDataSource</code>数据源和<code>secondaryDataSource</code>数据源</li>
</ol>
<h2> 测试一下</h2>
<p>完成了上面之后，我们就可以写个测试类来尝试一下上面的多数据源配置是否正确了，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ol>
<li>可能这里你会问，有两个JdbcTemplate，为什么不用<code>@Qualifier</code>指定？这里顺带说个小知识点，当我们不指定的时候，会采用参数的名字来查找Bean，存在的话就注入。</li>
<li>这两个JdbcTemplate创建的时候，我们也没指定名字，它们是如何匹配上的？这里也是一个小知识点，当我们创建Bean的时候，默认会使用方法名称来作为Bean的名称，所以这里就对应上了。读者不妨回头看看两个名称是不是一致的？</li>
</ol>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-7</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：MyBatis的多数据源配置</title>
      <link>https://spring.didispace.com/spring-boot-2/4-6-multi-mybatis.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-6-multi-mybatis.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：MyBatis的多数据源配置</source>
      <description>Spring Boot 2.x基础教程：MyBatis的多数据源配置 前两天，我们已经介绍了关于JdbcTemplate的多数据源配置以及Spring Data JPA的多数据源配置，接下来具体说说使用MyBatis时候的多数据源场景该如何配置。 添加多数据源的配置</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：MyBatis的多数据源配置</h1>
<p>前两天，我们已经介绍了关于<a href="/spring-boot-2/4-6-multi-jdbctemplate/" target="blank">JdbcTemplate的多数据源配置</a>以及<a href="/spring-boot-2/4-6-multi-spring-data-jpa/" target="blank">Spring Data JPA的多数据源配置</a>，接下来具体说说使用MyBatis时候的多数据源场景该如何配置。</p>
<h2> 添加多数据源的配置</h2>
<p>先在Spring Boot的配置文件application.properties中设置两个你要链接的数据库配置，比如这样：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ol>
<li>多数据源配置的时候，与单数据源不同点在于spring.datasource之后多设置一个数据源名称primary和secondary来区分不同的数据源配置，这个前缀将在后续初始化数据源的时候用到。</li>
<li>数据源连接配置2.x和1.x的配置项是有区别的：2.x使用spring.datasource.secondary.jdbc-url，而1.x版本使用spring.datasource.secondary.url。如果你在配置的时候发生了这个报错java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.，那么就是这个配置项的问题。</li>
<li>可以看到，不论使用哪一种数据访问框架，对于数据源的配置都是一样的。</li>
</ol>
<h2> 初始化数据源与MyBatis配置</h2>
<p>完成多数据源的配置信息之后，就来创建个配置类来加载这些配置信息，初始化数据源，以及初始化每个数据源要用的MyBatis配置。</p>
<p>这里我们继续将数据源与框架配置做拆分处理：</p>
<ol>
<li>单独建一个多数据源的配置类，比如下面这样：</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到内容跟JdbcTemplate、Spring Data JPA的时候是一模一样的。通过<code>@ConfigurationProperties</code>可以知道这两个数据源分别加载了<code>spring.datasource.primary.*</code>和<code>spring.datasource.secondary.*</code>的配置。<code>@Primary</code>注解指定了主数据源，就是当我们不特别指定哪个数据源的时候，就会使用这个Bean真正差异部分在下面的JPA配置上。</p>
<ol start="2">
<li>分别创建两个数据源的MyBatis配置。</li>
</ol>
<p>Primary数据源的JPA配置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Secondary数据源的JPA配置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ol>
<li>配置类上使用<code>@MapperScan</code>注解来指定当前数据源下定义的Entity和Mapper的包路径；另外需要指定sqlSessionFactory和sqlSessionTemplate，这两个具体实现在该配置类中类中初始化。</li>
<li>配置类的构造函数中，通过<code>@Qualifier</code>注解来指定具体要用哪个数据源，其名字对应在<code>DataSourceConfiguration</code>配置类中的数据源定义的函数名。</li>
<li>配置类中定义SqlSessionFactory和SqlSessionTemplate的实现，注意具体使用的数据源正确（如果使用这里的演示代码，只要第二步没问题就不需要修改）。</li>
</ol>
<p>上一篇介绍JPA的时候，因为之前介绍JPA的使用时候，说过实体和Repository定义的方法，所以省略了 User 和 Repository的定义代码，但是还是有读者问怎么没有这个，其实都有说明，仓库代码里也都是有的。未避免再问这样的问题，所以这里就贴一下吧。</p>
<p>根据上面Primary数据源的定义，在<code>com.didispace.chapter39.p</code>包下，定义Primary数据源要用的实体和数据访问对象，比如下面这样：</p>
<div class="language-Java line-numbers-mode" data-ext="Java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>根据上面Secondary数据源的定义，在<code>com.didispace.chapter39.s</code>包下，定义Secondary数据源要用的实体和数据访问对象，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试验证</h2>
<p>完成了上面之后，我们就可以写个测试类来尝试一下上面的多数据源配置是否正确了，先来设计一下验证思路：</p>
<ol>
<li>往Primary数据源插入一条数据</li>
<li>从Primary数据源查询刚才插入的数据，配置正确就可以查询到</li>
<li>从Secondary数据源查询刚才插入的数据，配置正确应该是查询不到的</li>
<li>往Secondary数据源插入一条数据</li>
<li>从Primary数据源查询刚才插入的数据，配置正确应该是查询不到的</li>
<li>从Secondary数据源查询刚才插入的数据，配置正确就可以查询到</li>
</ol>
<p>具体实现如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-9</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置</title>
      <link>https://spring.didispace.com/spring-boot-2/4-6-multi-spring-data-jpa.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-6-multi-spring-data-jpa.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置</source>
      <description>Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置 上一篇我们介绍了在使用JdbcTemplate来做数据访问时候的多数据源配置实现。接下来我们继续学习如何在使用Spring Data JPA的时候，完成多数据源的配置和使用。 添加多数据源的配置 先在Spring Boot的配置文件application.properties中设置两个你要链接的数据库配置，比如这样：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置</h1>
<p><a href="/spring-boot-2/4-6-multi-jdbctemplate/" target="blank">上一篇</a>我们介绍了在使用JdbcTemplate来做数据访问时候的多数据源配置实现。接下来我们继续学习如何在使用Spring Data JPA的时候，完成多数据源的配置和使用。</p>
<h2> 添加多数据源的配置</h2>
<p>先在Spring Boot的配置文件<code>application.properties</code>中设置两个你要链接的数据库配置，比如这样：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里除了JPA自身相关的配置之外，与JdbcTemplate配置时候的数据源配置完全是一致的</p>
<p><strong>说明与注意</strong>：</p>
<ol>
<li>多数据源配置的时候，与单数据源不同点在于<code>spring.datasource</code>之后多设置一个数据源名称<code>primary</code>和<code>secondary</code>来区分不同的数据源配置，这个前缀将在后续初始化数据源的时候用到。</li>
<li>数据源连接配置2.x和1.x的配置项是有区别的：2.x使用<code>spring.datasource.secondary.jdbc-url</code>，而1.x版本使用<code>spring.datasource.secondary.url</code>。如果你在配置的时候发生了这个报错<code>java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.</code>，那么就是这个配置项的问题。</li>
</ol>
<h2> 初始化数据源与JPA配置</h2>
<p>完成多数据源的配置信息之后，就来创建个配置类来加载这些配置信息，初始化数据源，以及初始化每个数据源要用的JPA配置。</p>
<p>由于JPA的配置要比JdbcTemplate的复杂很多，所以我们将配置拆分一下来处理：</p>
<ol>
<li>单独建一个多数据源的配置类，比如下面这样：</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到内容跟JdbcTemplate时候是一模一样的。通过<code>@ConfigurationProperties</code>可以知道这两个数据源分别加载了<code>spring.datasource.primary.*</code>和<code>spring.datasource.secondary.*</code>的配置。<code>@Primary</code>注解指定了主数据源，就是当我们不特别指定哪个数据源的时候，就会使用这个Bean真正差异部分在下面的JPA配置上。</p>
<ol start="2">
<li>分别创建两个数据源的JPA配置。</li>
</ol>
<p>Primary数据源的JPA配置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Secondary数据源的JPA配置：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ul>
<li>在使用JPA的时候，需要为不同的数据源创建不同的package来存放对应的Entity和Repository，以便于配置类的分区扫描</li>
<li>类名上的注解<code>@EnableJpaRepositories</code>中指定Repository的所在位置</li>
<li><code>LocalContainerEntityManagerFactoryBean</code>创建的时候，指定Entity所在的位置</li>
<li>其他主要注意在互相注入时候，不同数据源不同配置的命名，基本就没有什么大问题了</li>
</ul>
<h2> 测试一下</h2>
<p>完成了上面之后，我们就可以写个测试类来尝试一下上面的多数据源配置是否正确了，比如下面这样：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>说明与注意</strong>：</p>
<ul>
<li>测试验证的逻辑很简单，就是通过不同的Repository往不同的数据源插入数据，然后查询一下总数是否是对的</li>
<li>这里省略了Entity和Repository的细节，读者可以在下方代码示例中下载完整例子对照查看</li>
</ul>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-8</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：事务管理入门</title>
      <link>https://spring.didispace.com/spring-boot-2/4-7-transactional.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-7-transactional.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：事务管理入门</source>
      <description>Spring Boot 2.x基础教程：事务管理入门 什么是事务？ 我们在开发企业应用时，通常业务人员的一个操作实际上是对数据库读写的多步操作的结合。由于数据操作在顺序执行的过程中，任何一步操作都有可能发生异常，异常会导致后续操作无法完成，此时由于业务逻辑并未正确的完成，之前成功操作的数据并不可靠，如果要让这个业务正确的执行下去，通常有实现方式： 记录失败的位置，问题修复之后，从上一次执行失败的位置开始继续执行后面要做的业务逻辑 在执行失败的时候，回退本次执行的所有过程，让操作恢复到原始状态，带问题修复之后，重新执行原来的业务逻辑</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：事务管理入门</h1>
<h2> 什么是事务？</h2>
<p>我们在开发企业应用时，通常业务人员的一个操作实际上是对数据库读写的多步操作的结合。由于数据操作在顺序执行的过程中，任何一步操作都有可能发生异常，异常会导致后续操作无法完成，此时由于业务逻辑并未正确的完成，之前成功操作的数据并不可靠，如果要让这个业务正确的执行下去，通常有实现方式：</p>
<ol>
<li>记录失败的位置，问题修复之后，从上一次执行失败的位置开始继续执行后面要做的业务逻辑</li>
<li>在执行失败的时候，回退本次执行的所有过程，让操作恢复到原始状态，带问题修复之后，重新执行原来的业务逻辑</li>
</ol>
<p>事务就是针对上述方式2的实现。事务，一般是指要做的或所做的事情，就是上面所说的业务人员的一个操作（比如电商系统中，一个创建订单的操作包含了创建订单、商品库存的扣减两个基本操作。如果创建订单成功，库存扣减失败，那么就会出现商品超卖的问题，所以最基本的最发就是需要为这两个操作用事务包括起来，保证这两个操作要么都成功，要么都失败）。</p>
<p>这样的场景在实际开发过程中非常多，所以今天就来一起学习一下Spring Boot中的事务管理如何使用！</p>
<h2> 快速入门</h2>
<p>在Spring Boot中，当我们使用了<code>spring-boot-starter-jdbc</code>或<code>spring-boot-starter-data-jpa</code>依赖的时候，框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外配置就可以用<code>@Transactional</code>注解进行事务的使用。</p>
<p>我们以之前实现的<a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">《使用Spring Data JPA访问MySQL》</a>的示例作为基础工程进行事务的使用学习。在该样例工程中（若对该数据访问方式不了解，可先阅读该前文），我们引入了spring-data-jpa，并创建了User实体以及对User的数据访问对象UserRepository，在单元测试类中实现了使用UserRepository进行数据读写的单元测试用例，如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，在这个单元测试用例中，使用UserRepository对象连续创建了10个User实体到数据库中，下面我们人为的来制造一些异常，看看会发生什么情况。</p>
<p>通过<code>@Max(50)</code>来为User的age设置最大值为50，这样通过创建时User实体的age属性超过50的时候就可以触发异常产生。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>执行测试用例，可以看到控制台中抛出了如下异常，关于age字段的错误：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>此时查数据库中的User表：</p>
<p><img src="https://static.didispace.com/images/pasted-329.png" alt=""></p>
<p>可以看到，测试用例执行到一半之后因为异常中断了，前5条数据正确插入而后5条数据没有成功插入，如果这10条数据需要全部成功或者全部失败，那么这时候就可以使用事务来实现，做法非常简单，我们只需要在test函数上添加<code>@Transactional</code>注解即可。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再来执行该测试用例，可以看到控制台中输出了回滚日志（Rolled back transaction for test context），</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再看数据库中，User表就没有AAA到EEE的用户数据了，成功实现了自动回滚。</p>
<p>这里主要通过单元测试演示了如何使用<code>@Transactional</code>注解来声明一个函数需要被事务管理，通常我们单元测试为了保证每个测试之间的数据独立，会使用<code>@Rollback</code>注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时，我们通常在service层接口中使用<code>@Transactional</code>来对各个业务逻辑进行事务管理的配置，例如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 事务详解</h2>
<p>上面的例子中我们使用了默认的事务配置，可以满足一些基本的事务需求，但是当我们项目较大较复杂时（比如，有多个数据源等），这时候需要在声明事务时，指定不同的事务管理器。对于不同数据源的事务管理配置可以见<a href="/spring-boot-2/4-6-multi-spring-data-jpa/" target="blank">《Spring Data JPA的多数据源配置》</a>中的设置。在声明事务时，只需要通过value属性指定配置的事务管理器名即可，例如：<code>@Transactional(value="transactionManagerPrimary")</code>。</p>
<p>除了指定不同的事务管理器之后，还能对事务进行隔离级别和传播行为的控制，下面分别详细解释：</p>
<h3> 隔离级别</h3>
<p>隔离级别是指若干个并发的事务之间的隔离程度，与我们开发时候主要相关的场景包括：脏读取、重复读、幻读。</p>
<p>我们可以看<code>org.springframework.transaction.annotation.Isolation</code>枚举类中定义了五个表示隔离级别的值：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>DEFAULT</code>：这是默认值，表示使用底层数据库的默认隔离级别。对大部分数据库而言，通常这值就是：<code>READ_COMMITTED</code>。</li>
<li><code>READ_UNCOMMITTED</code>：该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读，因此很少使用该隔离级别。</li>
<li><code>READ_COMMITTED</code>：该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。</li>
<li><code>REPEATABLE_READ</code>：该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询，这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。</li>
<li><code>SERIALIZABLE</code>：所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。</li>
</ul>
<p>指定方法：通过使用<code>isolation</code>属性设置，例如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 传播行为</h3>
<p>所谓事务的传播行为是指，如果在开始当前事务之前，一个事务上下文已经存在，此时有若干选项可以指定一个事务性方法的执行行为。</p>
<p>我们可以看<code>org.springframework.transaction.annotation.Propagation</code>枚举类中定义了6个表示传播行为的枚举值：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li><code>REQUIRED</code>：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。</li>
<li><code>SUPPORTS</code>：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。</li>
<li><code>MANDATORY</code>：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。</li>
<li><code>REQUIRES_NEW</code>：创建一个新的事务，如果当前存在事务，则把当前事务挂起。</li>
<li><code>NOT_SUPPORTED</code>：以非事务方式运行，如果当前存在事务，则把当前事务挂起。</li>
<li><code>NEVER</code>：以非事务方式运行，如果当前存在事务，则抛出异常。</li>
<li><code>NESTED</code>：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于<code>REQUIRED</code>。</li>
</ul>
<p>指定方法：通过使用<code>propagation</code>属性设置，例如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-10</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p>如果您觉得本文不错，欢迎Star支持，您的关注是我坚持的动力！</p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-329.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用Flyway管理数据库版本</title>
      <link>https://spring.didispace.com/spring-boot-2/4-8-flyway.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-8-flyway.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用Flyway管理数据库版本</source>
      <description>Spring Boot 2.x基础教程：使用Flyway管理数据库版本 之前已经介绍了很多在Spring Boot中使用MySQL的案例，包含了Spring Boot最原始的JdbcTemplate、Spring Data JPA以及我们国内最常用的MyBatis。同时，对于一些复杂场景比如：更换Druid数据源，或是多数据源的情况也都做了介绍。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用Flyway管理数据库版本</h1>
<p>之前已经介绍了很多在Spring Boot中使用MySQL的案例，包含了Spring Boot最原始的<a href="/spring-boot-2/4-1-jdbctemplate/" target="blank">JdbcTemplate</a>、<a href="/spring-boot-2/4-4-spring-data-jpa/" target="blank">Spring Data JPA</a>以及我们国内最常用的<a href="/spring-boot-2/4-5-mybatis/" target="blank">MyBatis</a>。同时，对于一些复杂场景比如：更换Druid数据源，或是多数据源的情况也都做了介绍。</p>
<p>不论我们使用哪一个具体实现框架，都离不开对数据库表结构的管理。而这一类管理一直都存在一个问题：由于数据库表元数据存储于数据库中，而我们的访问逻辑都存在于Git或其他代码仓库中。Git已经帮助我们完成了代码的多版本管理，那么数据库中的表该如何做好版本控制呢？</p>
<p>今天我们就来介绍在Spring Boot中使用Flyway来管理数据库版本的方法。</p>
<h2> Flyway简介</h2>
<p>Flyway是一个简单开源数据库版本控制器（约定大于配置），主要提供migrate、clean、info、validate、baseline、repair等命令。它支持SQL（PL/SQL、T-SQL）方式和Java方式，支持命令行客户端等，还提供一系列的插件支持（Maven、Gradle、SBT、ANT等）。</p>
<p>官方网站：https://flywaydb.org/</p>
<p>本文对于Flyway的自身功能不做过多的介绍，读者可以通过阅读官方文档或利用搜索引擎获得更多资料。下面我们具体说说在Spring Boot应用中的应用，如何使用Flyway来创建数据库以及结构不一致的检查。</p>
<h2> 动手试试</h2>
<p>下面我们先预设一个开发目标：</p>
<ol>
<li>假设我们需要开发一个用户管理系统，那么我们势必要设计一张用户表，并实现对用户表的增删改查操作。</li>
<li>在任务1的功能完成之后，我们又有一个新需求，需要对用户表增加了一个字段，看看如何实现对数据库表结构的更改。</li>
</ol>
<p><strong>目标 1 的实现</strong></p>
<p>第一步：创建一个基础的Spring Boot项目，并在<code>pom.xml</code>中加入Flyway、MySQL连接和数据访问相关的必要依赖（这里选用<code>spring-boot-starter-jdbc</code>作为例子）</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二步：按Flyway的规范创建版本化的SQL脚本。</p>
<ul>
<li>在工程的<code>src/main/resources</code>目录下创建<code>db</code>目录，在<code>db</code>目录下再创建<code>migration</code>目录</li>
<li>在<code>migration</code>目录下创建版本化的SQL脚本<code>V1__Base_version.sql</code></li>
</ul>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>注意：如果你不想将SQL脚本放到其他目录，可以用<code>spring.flyway.locations</code>参数来配置。这里不同于1.x版本的配置项<code>flyway.locations</code></p>
</blockquote>
<p>第三步：根据User表的结构，编写对应的实体定义</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第四步：编写用户操作接口和实现</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>这里主要介绍Flyway的应用，所以采用这种比较简单的编写方式，实际项目应用中，还是推荐MyBatis的具体操作实现。</p>
</blockquote>
<p>第五步：编写测试用例</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>注意由于Spring Boot 2.4应用的junit版本与之前Spring Boot 1.x版本中的不同，因此单元测试的编写略有区别，有兴趣的读者可以分别查看之前介绍文章和这篇文章中的单元测试的区别，这里就不细说了。</p>
</blockquote>
<p>第六步：运行上面编写的单元测试，验证一下效果。</p>
<p>不出意外，单元测试运行ok的话</p>
<p><img src="https://static.didispace.com/images/pasted-383.png" alt=""></p>
<p>连上数据库看看。此时应该多出了这两张表：</p>
<p><img src="https://static.didispace.com/images/pasted-382.png" alt=""></p>
<ul>
<li><code>user</code>表就是我们维护在SQL脚本中要创建的表</li>
<li><code>flyway_schema_history</code>表是flyway的管理表，用来记录在这个数据库上跑过的脚本，以及每个脚本的检查依据。这样每次应用启动的时候，就可以知道哪个脚本需要运行，或者哪个脚本发生了变动，运行基础可能不对，造成数据结构的混乱而阻止运行。</li>
</ul>
<p><strong>目标 2 的实现</strong></p>
<p>有了上面的基础之后，我们来说说后续要做表结构的表变动该怎么操作，这也是之前读者出现问题最多的情况，所以在2.x版本教程中特地讲一讲。</p>
<p>首先，大家在开始使用Flyway之后，对于数据库表接口的变更就要关闭这几个途径：</p>
<ol>
<li>直接通过工具登录数据去修改表结构</li>
<li>已经发布的sql脚本不允许修改</li>
</ol>
<p>正确的表结构调整途径：在flyway脚本配置路径下编写新的脚本，启动程序来执行变更。这样可以获得几个很大的好处：</p>
<ol>
<li>脚本受Git版本管理控制，可以方便的找到过去的历史</li>
<li>脚本在程序启动的时候先加载，再提供接口服务，一起完成部署步骤</li>
<li>所有表结构的历史变迁，在管理目录中根据版本号就能很好的追溯</li>
</ol>
<p>下面根据一个实际需求来具体操作下。假设我们现在想对User表增加一个字段：address，用来存储用户的通讯地址，那么我们就需要这样操作实现。</p>
<p>第一步：创建脚本文件<code>V1_1__alter_table_user.sql</code>，并写入增加<code>address</code>列的语句</p>
<div class="language-sql line-numbers-mode" data-ext="sql"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><blockquote>
<p>对于脚本文件名的基本规则是：<code>版本号__描述.sql</code>。当然如果你有更细致的要求，那么可以做更细致的文件名规划，具体细节读者可以查阅文末参考资料中的官方文档获取。</p>
</blockquote>
<p>第二步：再次执行单元测试，在控制台中可以看到如下日志：</p>
<div class="language-log line-numbers-mode" data-ext="log"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>再查看一下数据中国的内容：</p>
<p><img src="https://static.didispace.com/images/pasted-384.png" alt="User表中已经有了Address列"></p>
<p><img src="https://static.didispace.com/images/pasted-385.png" alt="Flyway管理表中已经有新脚本的加载记录"></p>
<p>如果你还没有体会到引入Flyway对给我们的表结构带来的好处的话，不妨也留言分享下你们的管理方式吧！</p>
<p>本系列教程<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-11</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
<h2> 参考资料</h2>
<ul>
<li><a href="https://blog.didispace.com/spring-boot-flyway-db-version/" target="_blank" rel="noopener noreferrer">Spring Boot中使用Flyway来管理数据库版本</a></li>
<li><a href="https://flywaydb.org/documentation/concepts/migrations.html" target="_blank" rel="noopener noreferrer">Flyway官方文档</a></li>
</ul>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-383.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用JTA实现多数据源的事务管理</title>
      <link>https://spring.didispace.com/spring-boot-2/4-9-jta.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/4-9-jta.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用JTA实现多数据源的事务管理</source>
      <description>Spring Boot 2.x基础教程：使用JTA实现多数据源的事务管理 在一个Spring Boot项目中，连接多个数据源还是比较常见的。之前也介绍了如何在几种常用框架的场景下配置多数据源，具体可见： Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置 Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置 Spring Boot 2.x基础教程：MyBatis的多数据源配置</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用JTA实现多数据源的事务管理</h1>
<p>在一个Spring Boot项目中，连接多个数据源还是比较常见的。之前也介绍了如何在几种常用框架的场景下配置多数据源，具体可见：</p>
<ul>
<li><a href="/spring-boot-2/4-6-multi-jdbctemplate/" target="blank">Spring Boot 2.x基础教程：JdbcTemplate的多数据源配置</a></li>
<li><a href="/spring-boot-2/4-6-multi-spring-data-jpa/" target="blank">Spring Boot 2.x基础教程：Spring Data JPA的多数据源配置</a></li>
<li><a href="/spring-boot-2/4-6-multi-mybatis">Spring Boot 2.x基础教程：MyBatis的多数据源配置</a></li>
</ul>
<p>当我们采用多数据源的时候，同时也会出现一个这样的特殊场景：我们希望对A数据源的更新和B数据源的更新具备事务性。这样的例子很常见，比如：在订单库中创建一条订单记录，同时还需要在商品库中扣减商品库存。如果库存扣减失败，那么我们希望订单创建也能够回滚。</p>
<p>如果这两条数据在一个数据库中，那么通过之前介绍的<a href="/spring-boot-2/4-7-transactional/" target="blank">事务管理</a>就能轻松解决了。但是，当这两个操作位于不同的数据库中，那么就无法实现了。</p>
<p>本文就来介绍一种解决这类问题的方法：JTA事务。</p>
<h2> 什么是JTA</h2>
<p>JTA，全称：Java Transaction API。JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者，而一个JDBC事务则被限定在一个单一的数据库连接。所以，当我们在同时操作多个数据库的时候，使用JTA事务就可以弥补JDBC事务的不足。</p>
<p>在Spring Boot 2.x中，整合了这两个JTA的实现：</p>
<ul>
<li>Atomikos：可以通过引入<code>spring-boot-starter-jta-atomikos</code>依赖来使用</li>
<li>Bitronix：可以通过引入<code>spring-boot-starter-jta-bitronix</code>依赖来使用</li>
</ul>
<p>由于Bitronix自Spring Boot 2.3.0开始不推荐使用，所以在下面的动手环节中，我们将使用Atomikos作为例子来介绍JTA的使用。</p>
<h2> 动手试试</h2>
<p>下面我们就来实操一下，如何在Spring Boot中使用JTA来实现多数据源下的事务管理。</p>
<p><strong>准备工作</strong></p>
<ol>
<li>
<p>这里我们将使用最基础的JdbcTemplate来实现数据访问，所以如果你还不会使用JdbcTemplate配置多数据源，建议先看一下<a href="/spring-boot-2/4-6-multi-jdbctemplate/" target="blank">JdbcTemplate的多数据源配置</a>。</p>
</li>
<li>
<p>场景设定：</p>
</li>
</ol>
<ul>
<li>假设我们有两个库，分别为：test1和test2</li>
<li>这两个库中都有一张User表，我们希望这两张表中的数据是一致的</li>
<li>假设这两张表中都已经有一条数据：name=aaa，age=30；因为这两张表中数据是一致的，所以要update的时候，就必须两个库中的User表更新时候，要么都成功，要么都失败。</li>
</ul>
<p><img src="https://static.didispace.com/images/pasted-427.png" alt=""></p>
<p><strong>操作详细</strong></p>
<ol>
<li>在<code>pom.xml</code>中加入JTA的实现Atomikos的Starter</li>
</ol>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="2">
<li>在<code>application.properties</code>配置文件中配置两个test1和test2数据源</li>
</ol>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="3">
<li>创建多数据源配置类</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>注意，这里除了家在的配置不同之外，<code>DataSource</code>也采用了<code>AtomikosDataSourceBean</code>注意与之前配置多数据源使用的配置和实现类的区别。</p>
<ol start="4">
<li>创建一个Service实现，模拟两种不同的情况。</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里tx函数，是两句update操作，一般都会成功；而tx2函数中，我们人为的制造了一个异常，这个异常是在test1库中的数据更新后才产生的，这样就可以测试一下test1更新成功，之后是否还能在JTA的帮助下实现回滚。</p>
<ol start="5">
<li>创建测试类，编写测试用例</li>
</ol>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里有两个测试用例：</p>
<ul>
<li>test1：因为没有故意制造的异常，不出意外两个库的update都会成功，所以根据name=aaa去把两个数据查出来，看age是否都被更新到了30。</li>
<li>test2：tx2函数会把test1中name=aaa的用户age更新为40，然后抛出异常，JTA事务生效的话，会把age回滚回30，所以这里的检查也是两个库的aaa用户的age应该都为30，这样就意味着JTA事务生效，保证了test1和test2两个库中的User表数据更新一致，没有制造出脏数据。</li>
</ul>
<h2> 测试验证</h2>
<p>将上面编写的单元测试运行起来:</p>
<p><img src="https://static.didispace.com/images/pasted-429.png" alt=""></p>
<p>观察一下启动阶段的日志，可以看到这些Atomikos初始化日志输出：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>同时，我们在<code>transaction-logs</code>目录下，还能找到关于事务的日志信息：</p>
<p><img src="https://static.didispace.com/images/pasted-428.png" alt=""></p>
<div class="language-json line-numbers-mode" data-ext="json"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>本系列教程<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter3-12</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-427.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：进程内缓存的使用与Cache注解详解</title>
      <link>https://spring.didispace.com/spring-boot-2/5-1-caching.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/5-1-caching.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：进程内缓存的使用与Cache注解详解</source>
      <description>Spring Boot 2.x基础教程：进程内缓存的使用与Cache注解详解 随着时间的积累，应用的使用用户不断增加，数据规模也越来越大，往往数据库查询操作会成为影响用户使用体验的瓶颈，此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持，可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能，提高数据访问性能。 在Spring Boot中对于缓存的支持，提供了一系列的自动化配置，使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示，我们是如何给一个既有应用增加缓存功能的。 快速入门</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：进程内缓存的使用与Cache注解详解</h1>
<p>随着时间的积累，应用的使用用户不断增加，数据规模也越来越大，往往数据库查询操作会成为影响用户使用体验的瓶颈，此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持，可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能，提高数据访问性能。</p>
<p>在Spring Boot中对于缓存的支持，提供了一系列的自动化配置，使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示，我们是如何给一个既有应用增加缓存功能的。</p>
<h2> 快速入门</h2>
<p>下面我们将使用<a href="https://blog.didispace.com/spring-boot-learning-21-3-4/" target="_blank" rel="noopener noreferrer">使用Spring Data JPA访问MySQL</a>一文的案例为基础。这个案例中包含了使用Spring Data JPA访问User数据的操作，利用这个基础，我们为其添加缓存，来减少对数据库的IO，以达到访问加速的作用。如果您还不熟悉如何实现对MySQL的读写操作，那么建议先阅读前文，完成这个基础案例的编写。</p>
<p>先简单回顾下这个案例的基础内容：</p>
<p><strong>User实体的定义</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>User实体的数据访问实现</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>为了更好的理解缓存，我们先对该工程做一些简单的改造。</p>
<ul>
<li><code>application.properties</code>文件中新增<code>spring.jpa.show-sql=true</code>，开启hibernate对sql语句的打印。如果是1.x版本，使用<code>spring.jpa.properties.hibernate.show_sql=true</code>参数。</li>
<li>修改单元测试类，插入User表一条用户名为AAA，年龄为10的数据。并通过findByName函数完成两次查询，具体代码如下：</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在没有加入缓存之前，我们可以先执行一下这个案例，可以看到如下的日志：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>两次<code>findByName</code>查询都执行了两次SQL，都是对MySQL数据库的查询。</p>
<h2> 引入缓存</h2>
<p>第一步：在<code>pom.xml</code>中引入cache依赖，添加如下内容：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二步：在Spring Boot主类中增加<code>@EnableCaching</code>注解开启缓存功能，如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第三步：在数据访问接口中，增加缓存配置注解，如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第四步：再来执行以下单元测试，可以在控制台中输出了下面的内容</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>到这里，我们可以看到，在调用第二次<code>findByName</code>函数时，没有再执行select语句，也就直接减少了一次数据库的读取操作。</p>
<p>为了可以更好的观察，缓存的存储，我们可以在单元测试中注入<code>CacheManager</code>。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p>使用debug模式运行单元测试，观察<code>CacheManager</code>中的缓存集users以及其中的User对象的缓存加深理解。</p>
<p><img src="https://static.didispace.com/images/pasted-330.png" alt=""></p>
<p>可以看到，在第一次调用<code>findByName</code>函数之后，<code>CacheManager</code>将这个查询结果保存了下来，所以在第二次访问的时候，就能匹配上而不需要再访问数据库了。</p>
<h2> Cache配置注解详解</h2>
<p>回过头来我们再来看这里使用到的两个注解分别作了什么事情：</p>
<ul>
<li><code>@CacheConfig</code>：主要用于配置该类中会用到的一些共用的缓存配置。在这里<code>@CacheConfig(cacheNames = "users")</code>：配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中，我们也可以不使用该注解，直接通过<code>@Cacheable</code>自己配置缓存集的名字来定义。</li>
<li><code>@Cacheable</code>：配置了findByName函数的返回值将被加入缓存。同时在查询时，会先从缓存中获取，若不存在才再发起对数据库的访问。该注解主要有下面几个参数：
<ul>
<li><code>value</code>、<code>cacheNames</code>：两个等同的参数（<code>cacheNames</code>为Spring 4新增，作为<code>value</code>的别名），用于指定缓存存储的集合名。由于Spring 4中新增了<code>@CacheConfig</code>，因此在Spring 3中原本必须有的<code>value</code>属性，也成为非必需项了</li>
<li><code>key</code>：缓存对象存储在Map集合中的key值，非必需，缺省按照函数的所有参数组合作为key值，若自己配置需使用SpEL表达式，比如：<code>@Cacheable(key = "#p0")</code>：使用函数第一个参数作为缓存的key值，更多关于SpEL表达式的详细内容可参考<a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-spel-context" target="_blank" rel="noopener noreferrer">官方文档</a></li>
<li><code>condition</code>：缓存对象的条件，非必需，也需使用SpEL表达式，只有满足表达式条件的内容才会被缓存，比如：<code>@Cacheable(key = "#p0", condition = "#p0.length() &lt; 3")</code>，表示只有当第一个参数的长度小于3的时候才会被缓存，若做此配置上面的AAA用户就不会被缓存，读者可自行实验尝试。</li>
<li><code>unless</code>：另外一个缓存条件参数，非必需，需使用SpEL表达式。它不同于<code>condition</code>参数的地方在于它的判断时机，该条件是在函数被调用之后才做判断的，所以它可以通过对result进行判断。</li>
<li><code>keyGenerator</code>：用于指定key生成器，非必需。若需要指定一个自定义的key生成器，我们需要去实现<code>org.springframework.cache.interceptor.KeyGenerator</code>接口，并使用该参数来指定。需要注意的是：<strong>该参数与<code>key</code>是互斥的</strong></li>
<li><code>cacheManager</code>：用于指定使用哪个缓存管理器，非必需。只有当有多个时才需要使用</li>
<li><code>cacheResolver</code>：用于指定使用那个缓存解析器，非必需。需通过<code>org.springframework.cache.interceptor.CacheResolver</code>接口来实现自己的缓存解析器，并用该参数指定。</li>
</ul>
</li>
</ul>
<p>除了这里用到的两个注解之外，还有下面几个核心注解：</p>
<ul>
<li><code>@CachePut</code>：配置于函数上，能够根据参数定义条件来进行缓存，它与<code>@Cacheable</code>不同的是，它每次都会真是调用函数，所以主要用于数据新增和修改操作上。它的参数与<code>@Cacheable</code>类似，具体功能可参考上面对<code>@Cacheable</code>参数的解析</li>
<li><code>@CacheEvict</code>：配置于函数上，通常用在删除方法上，用来从缓存中移除相应数据。除了同<code>@Cacheable</code>一样的参数之外，它还有下面两个参数：
<ul>
<li><code>allEntries</code>：非必需，默认为false。当为true时，会移除所有数据</li>
<li><code>beforeInvocation</code>：非必需，默认为false，会在调用方法之后移除数据。当为true时，会在调用方法之前移除数据。</li>
</ul>
</li>
</ul>
<p>欢迎关注本系列教程：<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》</a></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter5-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-330.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：EhCache缓存的使用</title>
      <link>https://spring.didispace.com/spring-boot-2/5-2-encache.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/5-2-encache.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：EhCache缓存的使用</source>
      <description>Spring Boot 2.x基础教程：EhCache缓存的使用 上一篇我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问。可能大家会问，那我们在Spring Boot中到底使用了什么缓存呢？ 在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器（CacheManager），Spring Boot根据下面的顺序去侦测缓存提供者：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：EhCache缓存的使用</h1>
<p><a href="https://blog.didispace.com/spring-boot-learning-21-5-1" target="_blank" rel="noopener noreferrer">上一篇</a>我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问。可能大家会问，那我们在Spring Boot中到底使用了什么缓存呢？</p>
<p>在Spring Boot中通过<code>@EnableCaching</code>注解自动化配置合适的缓存管理器（CacheManager），Spring Boot根据下面的顺序去侦测缓存提供者：</p>
<ul>
<li>Generic</li>
<li>JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)</li>
<li>EhCache 2.x</li>
<li>Hazelcast</li>
<li>Infinispan</li>
<li>Couchbase</li>
<li>Redis</li>
<li>Caffeine</li>
<li>Simple</li>
</ul>
<p>除了按顺序侦测外，我们也可以通过配置属性spring.cache.type来强制指定。我们也可以通过debug调试查看<code>cacheManager</code>对象的实例来判断当前使用了什么缓存。在<a href="https://blog.didispace.com/spring-boot-learning-21-5-1" target="_blank" rel="noopener noreferrer">上一篇</a>中，我们也展示了如何去查看当前使用情况。</p>
<p>当我们不指定具体其他第三方实现的时候，Spring Boot的Cache模块会使用<code>ConcurrentHashMap</code>来存储。而实际生产使用的时候，因为我们可能需要更多其他特性，往往就会采用其他缓存框架，所以接下来我们会分几篇分别介绍几个常用优秀缓存的整合与使用。</p>
<h2> 使用EhCache</h2>
<p>本篇我们将介绍如何在Spring Boot中使用EhCache进程内缓存。这里我们将沿用<a href="https://blog.didispace.com/spring-boot-learning-21-5-1" target="_blank" rel="noopener noreferrer">上一篇</a>的案例结果来进行改造，以实现EhCache的使用。</p>
<p>先回顾下这个基础案例的三个部分：</p>
<p><strong>User实体的定义</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>User实体的数据访问实现（涵盖了缓存注解）</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>测试验证用例（涵盖了CacheManager的注入，可用来观察使用的缓存管理类）</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>接下来我们通过下面的几步操作，就可以轻松的把上面的缓存应用改成使用ehcache缓存管理。</p>
<p><strong>第一步</strong>：在<code>pom.xml</code>中引入ehcache依赖</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>在Spring Boot的parent管理下，不需要指定具体版本，会自动采用Spring Boot中指定的版本号。</p>
</blockquote>
<p><strong>第二步</strong>：在<code>src/main/resources</code>目录下创建：<code>ehcache.xml</code></p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>完成上面的配置之后，再通过debug模式运行单元测试，观察此时CacheManager已经是EhCacheManager实例，说明EhCache开启成功了。或者在测试用例中加一句CacheManager的输出，比如：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>执行测试输出可以得到：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到：</p>
<ol>
<li>第一行输出的CacheManager type为<code>org.springframework.cache.ehcache.EhCacheCacheManager</code>，而不是上一篇中的<code>ConcurrentHashMap</code>了。</li>
<li>第二次查询的时候，没有输出SQL语句，所以是走的缓存获取</li>
</ol>
<p>整合成功！欢迎关注本系列教程：<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》</a></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter5-2</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用EhCache缓存集群</title>
      <link>https://spring.didispace.com/spring-boot-2/5-3-encache-cluster.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/5-3-encache-cluster.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用EhCache缓存集群</source>
      <description>Spring Boot 2.x基础教程：使用EhCache缓存集群 上一篇我们介绍了在Spring Boot中整合EhCache的方法。既然用了ehcache，我们自然要说说它的一些高级功能，不然我们用默认的ConcurrentHashMap就好了。本篇不具体介绍EhCache缓存如何落文件、如何配置各种过期参数等常规细节配置，这部分内容留给读者自己学习，如果您不知道如何搞，可以看看这里的官方文档。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用EhCache缓存集群</h1>
<p><a href="https://blog.didispace.com/spring-boot-learning-21-5-2/" target="_blank" rel="noopener noreferrer">上一篇</a>我们介绍了在Spring Boot中整合EhCache的方法。既然用了ehcache，我们自然要说说它的一些高级功能，不然我们用默认的<code>ConcurrentHashMap</code>就好了。本篇不具体介绍EhCache缓存如何落文件、如何配置各种过期参数等常规细节配置，这部分内容留给读者自己学习，如果您不知道如何搞，可以看看这里的<a href="http://www.ehcache.org/" target="_blank" rel="noopener noreferrer">官方文档</a>。</p>
<p>那么我们今天具体讲什么呢？先思考一个场景，当我们使用了EhCache，在缓存过期之前可以有效的减少对数据库的访问，但是通常我们将应用部署在生产环境的时候，为了实现应用的高可用（有一台机器挂了，应用还需要可用），肯定是会部署多个不同的进程去运行的，那么这种情况下，当有数据更新的时候，每个进程中的缓存都是独立维护的，如果这些进程缓存同步机制，那么就存在因缓存没有更新，而一直都用已经失效的缓存返回给用户，这样的逻辑显然是会有问题的。所以，本文就来说说当使用EhCache的时候，如果来组建进程内缓存EnCache的集群以及配置配置他们的同步策略。</p>
<p><strong>由于下面是组建集群的过程，务必采用多机的方式调试，避免不必要的错误发生。</strong></p>
<h2> 动手试试</h2>
<p>本篇的实现将基于<a href="https://blog.didispace.com/spring-boot-learning-21-5-2/" target="_blank" rel="noopener noreferrer">上一篇</a>的基础工程来进行。先来回顾下上一篇中的程序要素：</p>
<p><strong>User实体的定义</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>User实体的数据访问实现（涵盖了缓存注解）</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>下面开始改造这个项目：</p>
<p><strong>第一步</strong>：为需要同步的缓存对象实现<code>Serializable</code>接口</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>注意：如果没有做这一步，后续缓存集群通过过程中，因为要传输User对象，会导致序列化与反序列化相关的异常</p>
</blockquote>
<p><strong>第二步</strong>：重新组织ehcache的配置文件。我们尝试手工组建集群的方式，不同实例在网络相关配置上会产生不同的配置信息，所以我们建立不同的配置文件给不同的实例使用。比如下面这样：</p>
<p>实例1，使用<code>ehcache-1.xml</code></p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>实例2，使用<code>ehcache-2.xml</code></p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>配置说明：</p>
<ul>
<li><code>cache</code>标签中定义名为users的缓存，这里我们增加了一个子标签定义<code>cacheEventListenerFactory</code>，这个标签主要用来定义缓存事件监听的处理策略，它有以下这些参数用来设置缓存的同步策略：
<ul>
<li>replicatePuts：当一个新元素增加到缓存中的时候是否要复制到其他的peers。默认是true。</li>
<li>replicateUpdates：当一个已经在缓存中存在的元素被覆盖时是否要进行复制。默认是true。</li>
<li>replicateRemovals：当元素移除的时候是否进行复制。默认是true。</li>
<li>replicateAsynchronously：复制方式是异步的指定为true时，还是同步的，指定为false时。默认是true。</li>
<li>replicatePutsViaCopy：当一个新增元素被拷贝到其他的cache中时是否进行复制指定为true时为复制，默认是true。</li>
<li>replicateUpdatesViaCopy：当一个元素被拷贝到其他的cache中时是否进行复制指定为true时为复制，默认是true。</li>
</ul>
</li>
<li>新增了一个<code>cacheManagerPeerProviderFactory</code>标签的配置，用来指定组建的集群信息和要同步的缓存信息，其中：
<ul>
<li>hostName：是当前实例的主机名</li>
<li>port：当前实例用来同步缓存的端口号</li>
<li>socketTimeoutMillis：同步缓存的Socket超时时间</li>
<li>peerDiscovery：集群节点的发现模式，有手工与自动两种，这里采用了手工指定的方式</li>
<li>rmiUrls：当peerDiscovery设置为manual的时候，用来指定需要同步的缓存节点，如果存在多个用<code>|</code>连接</li>
</ul>
</li>
</ul>
<p><strong>第三步</strong>：打包部署与启动。打包没啥大问题，主要缓存配置内容存在一定差异，所以在指定节点的模式下，需要单独拿出来，然后使用启动参数来控制读取不同的配置文件。比如这样：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：实现几个接口用来验证缓存的同步效果</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>验证逻辑：</p>
<ol>
<li>启动通过第三步说的命令参数，启动两个实例</li>
<li>调用实例1的<code>/create</code>接口，创建一条数据</li>
<li>调用实例1的<code>/find</code>接口，实例1缓存User，同时同步缓存信息给实例2，在实例1中会存在SQL查询语句</li>
<li>调用实例2的<code>/find</code>接口，由于缓存集群同步了User的信息，所以在实例2中的这次查询也不会出现SQL语句</li>
</ol>
<h2> 进一步思考</h2>
<p><a href="https://blog.didispace.com/spring-boot-learning-21-5-2/" target="_blank" rel="noopener noreferrer">上一篇</a>发布的时候，公众号上有网友留言问，数据更新之后怎么办？</p>
<p>其实当构建了缓存集群之后，就比较好办了。比如这里的例子，需要做两件事：</p>
<ol>
<li><code>save</code>操作增加<code>@CachePut</code>注解，让更新操作完成之后将结果再put到缓存中</li>
<li>保证缓存事件监听的replicateUpdates=true，这样数据在更新之后可以保证复制到其他节点</li>
</ol>
<p>这样就可以防止缓存的脏数据了，但是这种方法还并不是很好，因为缓存集群的同步依然需要时间，会存在短暂的不一致。同时进程内的缓存要在每个实例上都占用，如果大量存储的话始终不那么经济。所以，很多时候进程内缓存不会作为主要的缓存手段。下一篇将具体说说，另一个更重要的缓存使用！</p>
<p>欢迎关注本系列教程：<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》</a></p>
<h2> 参考资料</h2>
<ul>
<li><a href="https://www.cnblogs.com/hoojo/archive/2012/07/19/2599534.html" target="_blank" rel="noopener noreferrer">EhCache 分布式缓存/缓存集群</a></li>
<li><a href="https://blog.csdn.net/chenchaofuck1/article/details/51558995" target="_blank" rel="noopener noreferrer">Java RMI:rmi Connection refused to host: 127.0.0.1异常解决</a></li>
</ul>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter5-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用集中式缓存Redis</title>
      <link>https://spring.didispace.com/spring-boot-2/5-4-redis-cache.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/5-4-redis-cache.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用集中式缓存Redis</source>
      <description>Spring Boot 2.x基础教程：使用集中式缓存Redis 之前我们介绍了两种进程内缓存的用法，包括Spring Boot默认使用的ConcurrentMap缓存以及缓存框架EhCache。虽然EhCache已经能够适用很多应用场景，但是由于EhCache是进程内的缓存框架，在集群模式下时，各应用服务器之间的缓存都是独立的，因此在不同服务器的进程间会存在缓存不一致的情况。即使EhCache提供了集群环境下的缓存同步策略，但是同步依然是需要一定的时间，短暂的缓存不一致依然存在。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用集中式缓存Redis</h1>
<p>之前我们介绍了两种进程内缓存的用法，包括Spring Boot默认使用的<a href="https://blog.didispace.com/spring-boot-learning-21-5-1/" target="_blank" rel="noopener noreferrer">ConcurrentMap缓存</a>以及<a href="https://blog.didispace.com/spring-boot-learning-21-5-2/" target="_blank" rel="noopener noreferrer">缓存框架EhCache</a>。虽然EhCache已经能够适用很多应用场景，但是由于EhCache是进程内的缓存框架，在集群模式下时，各应用服务器之间的缓存都是独立的，因此在不同服务器的进程间会存在缓存不一致的情况。即使EhCache提供了集群环境下的缓存同步策略，但是同步依然是需要一定的时间，短暂的缓存不一致依然存在。</p>
<p>在一些要求高一致性（任何数据变化都能及时的被查询到）的系统和应用中，就不能再使用EhCache来解决了，这个时候使用集中式缓存就可以很好的解决缓存数据的一致性问题。接下来我们就来学习一下，如何在Spring Boot的缓存支持中使用Redis实现数据缓存。</p>
<h2> 动手试试</h2>
<p>本篇的实现将基于<a href="https://blog.didispace.com/spring-boot-learning-21-5-3/" target="_blank" rel="noopener noreferrer">上一篇</a>的基础工程来进行。先来回顾下上一篇中的程序要素：</p>
<p><strong>User实体的定义</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>User实体的数据访问实现（涵盖了缓存注解）</strong></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>下面开始改造这个项目：</p>
<p><strong>第一步</strong>：<code>pom.xml</code>中增加相关依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>在Spring Boot 1.x的早期版本中，该依赖的名称为<code>spring-boot-starter-redis</code>，所以在<a href="https://blog.didispace.com/springbootcache2/" target="_blank" rel="noopener noreferrer">Spring Boot 1.x基础教程</a>中与这里不同。</p>
</blockquote>
<p><strong>第二步</strong>：配置文件中增加配置信息，以本地运行为例，比如：</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>关于连接池的配置，注意几点：</p>
<ol>
<li>Redis的连接池配置在1.x版本中前缀为<code>spring.redis.pool</code>与Spring Boot 2.x有所不同。</li>
<li>在1.x版本中采用jedis作为连接池，而在2.x版本中采用了lettuce作为连接池</li>
<li>以上配置均为默认值，实际上生产需进一步根据部署情况与业务要求做适当修改.</li>
</ol>
</blockquote>
<p>再来试试单元测试：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>执行测试输出可以得到：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到：</p>
<ol>
<li>第一行输出的CacheManager type为<code>org.springframework.data.redis.cache.RedisCacheManager</code>，而不是上一篇中的<code>EhCacheCacheManager</code>了</li>
<li>第二次查询的时候，没有输出SQL语句，所以是走的缓存获取</li>
</ol>
<p>整合成功！</p>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<h2> 思考题</h2>
<p>既然EhCache等进程内缓存有一致性问题存在，而Redis性能好而且还能解决一致性问题，那么我们只要学会用Redis就好了咯，为什么还要学进程内缓存呢？先留下你的思考，下一篇我们一起讨论这个问题！欢迎关注本系列教程：<a href="/spring-boot-2/" target="blank">《Spring Boot 2.x基础教程》</a></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter5-4</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用Redis的发布订阅功能</title>
      <link>https://spring.didispace.com/spring-boot-2/5-5-redis-publisher-subscriber.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/5-5-redis-publisher-subscriber.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用Redis的发布订阅功能</source>
      <description>Spring Boot 2.x基础教程：使用Redis的发布订阅功能 通过前面一篇集中式缓存的使用教程，我们已经了解了Redis的核心功能：作为K、V存储的高性能缓存。 接下来我们会分几篇来继续讲讲Redis的一些其他强大用法！如果你对此感兴趣，一定要关注收藏我哦！ 发布订阅模式 如果你看过之前我写的关于MQ的相关文章，那么对于发布订阅功能应该不会陌生。如果没看过，那也不要紧，这里先做一个简单介绍，已经了解的可以跳过直接看下一节内容。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用Redis的发布订阅功能</h1>
<p>通过前面一篇<a href="/spring-boot-2/5-4-redis-cache/" target="blank">集中式缓存的使用教程</a>，我们已经了解了Redis的核心功能：作为K、V存储的高性能缓存。</p>
<p>接下来我们会分几篇来继续讲讲Redis的一些其他强大用法！如果你对此感兴趣，一定要关注收藏我哦！</p>
<h2> 发布订阅模式</h2>
<p>如果你看过之前我写的<a href="https://blog.didispace.com/spring-boot-rabbitmq/" target="_blank" rel="noopener noreferrer">关于MQ的相关文章</a>，那么对于发布订阅功能应该不会陌生。如果没看过，那也不要紧，这里先做一个简单介绍，已经了解的可以跳过直接看下一节内容。</p>
<p><strong>什么是发布订阅模式？</strong></p>
<p>在发布订阅模式中有个重要的角色，一个是发布者Publisher，另一个订阅者Subscriber。本质上来说，发布订阅模式就是一种生产者消费者模式，Publisher负责生产消息，而Subscriber则负责消费它所订阅的消息。这种模式被广泛的应用于软硬件的系统设计中。比如：配置中心的一个配置修改之后，就是通过发布订阅的方式传递给订阅这个配置的订阅者来实现自动刷新的。</p>
<p><strong>不就是观察者模式吗？</strong></p>
<p>看到这里，学过设计模式的同学可能很容易将它与设计模式中的观察者模式联系起来，你会觉得发布订阅模式中的两个概念与观察者模式中的两个概念似乎干的是一样的事情？所以：Publisher就是观察者模式中的Subject？Subscriber就是观察者模式中的Observer？</p>
<p><strong>重要区别在哪里？</strong></p>
<p>从这两种模式的角色任务来说确实是非常相似的，但从实现架构上来说有一个核心不同点！</p>
<p>我们通过下面的图示来理解，就很清晰了：</p>
<p><img src="https://static.didispace.com/images/pasted-515.png" alt="观察者模式"></p>
<p><img src="https://static.didispace.com/images/pasted-516.png" alt="发布订阅模式"></p>
<p>可以看到这里有一个非常大的区别就是：<strong>发布订阅模式在两个角色中间是一个中间角色来过渡的，发布者并不直接与订阅者产生交互</strong>。</p>
<p>回想一下生产者消费者模式，这个中间过渡区域对应的就是是缓冲区。因为这个缓冲区的存在，发布者与订阅者的工作就可以实现更大程度的解耦。发布者不会因为订阅者处理速度慢，而影响自己的发布任务，它只需要快速生产即可。而订阅者也不用太担心一时来不及处理，因为有缓冲区在，可以一点点排队来完成（也就是我们常说的“削峰填谷”效果）。</p>
<p>而我们所熟知的RabbitMQ、Kafka、RocketMQ这些中间件的本质其实就是实现发布订阅模式中的这个中间缓冲区。而Redis也提供了简单的发布订阅实现，当我们有一些简单需求的时候，也是可以一用的！如果你已经理解了这个概念，那么就进入下一节，一起来做个例子吧！</p>
<h2> 动手试一试</h2>
<p>下面的动手任务，我们将在Spring Boot应用中，通过接口的方式实现一个消息发布者的角色，然后再写一个Service来实现消息的订阅（把接口传过来的消息内容打印处理）。</p>
<p><strong>第一步</strong>：创建一个基础的Spring Boot应用，如果还不会<a href="https://blog.didispace.com/spring-boot-learning-21-1-1/" target="_blank" rel="noopener noreferrer">点这里</a></p>
<p><strong>第二步</strong>：<code>pom.xml</code>中加入必须的几个依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：创建一个接口，用来发送消息。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里为了简单实现，公用CHANNEL名称字段，我都写在了应用主类里。</p>
<p><strong>第四步</strong>：继续应用主类里实现消息订阅，打接收到的消息打印处理</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第六步</strong>：验证结果</p>
<ol>
<li>启动应用Spring Boot主类</li>
<li>通过curl或其他工具调用接口<code>curl localhost:8080/publish?message=hello</code></li>
<li>观察控制台，可以看到打印了收到的message参数</li>
</ol>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>好了，今天的内容到这里结束了。如果你对本系列教程《Spring Boot 2.x基础教程》感兴趣，可以<a href="/spring-boot-2/" target="blank">点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter5-5</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！更多本系列免费教程连载<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">「点击进入汇总目录」</a></strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-515.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用MongoDB</title>
      <link>https://spring.didispace.com/spring-boot-2/6-1-mongodb.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/6-1-mongodb.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用MongoDB</source>
      <description>Spring Boot 2.x基础教程：使用MongoDB 前段时间因为团队调整，大部分时间放在了团队上，这系列的更新又耽误了一下。但既然承诺持久更新，那就不会落下，今天开始继续更新这部分的内容！ 过了年，重申一下这个系列的目标：目前主要任务就是把Spring Boot 1.x部分没有升级的内容做完升级。我会将因为版本升级而产生的变化做一些说明，这样不论低版本的读者还是高版本的读者都能找到自己想要的部分。这也是这次做2.x版本升级的重要原因，尽量避免或减少有读者用着高版本参考我这边低版本的实现而出现问题，然后开始问候我家人的情况。 在完成上述所有的更新之后，接下来很重要的更新内容将会集中在关于Spring Boot的一些进阶内容，比如：要做什么扩展的时候，该从哪里着手等。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用MongoDB</h1>
<p>前段时间因为团队调整，大部分时间放在了团队上，这系列的更新又耽误了一下。但既然承诺持久更新，那就不会落下，今天开始继续更新这部分的内容！</p>
<p>过了年，重申一下这个系列的目标：目前主要任务就是把Spring Boot 1.x部分没有升级的内容做完升级。我会将因为版本升级而产生的变化做一些说明，这样不论低版本的读者还是高版本的读者都能找到自己想要的部分。这也是这次做2.x版本升级的重要原因，尽量避免或减少有读者用着高版本参考我这边低版本的实现而出现问题，然后开始问候我家人的情况。</p>
<p>在完成上述所有的更新之后，接下来很重要的更新内容将会集中在关于Spring Boot的一些进阶内容，比如：要做什么扩展的时候，该从哪里着手等。</p>
<p>如果是您是Spring Boot的使用者，那么一定要关注一下！后面的内容会越来越精彩！</p>
<p>下面回归今天的主题，如何在Spring Boot中使用MongoDB！</p>
<h2> MongoDB简介</h2>
<p>MongoDB是一个基于分布式文件存储的数据库，它是一个介于关系数据库和非关系数据库之间的产品，其主要目标是在键/值存储方式（提供了高性能和高度伸缩性）和传统的RDBMS系统（具有丰富的功能）之间架起一座桥梁，它集两者的优势于一身。</p>
<p>MongoDB支持的数据结构非常松散，是类似json的bson格式，因此可以存储比较复杂的数据类型，也因为他的存储格式也使得它所存储的数据在Nodejs程序应用中使用非常流畅。</p>
<p>既然称为NoSQL数据库，Mongo的查询语言非常强大，其语法有点类似于面向对象的查询语言，几乎可以实现类似关系数据库单表查询的绝大部分功能，而且还支持对数据建立索引。</p>
<p>但是，MongoDB也不是万能的，同MySQL等关系型数据库相比，它们在针对不同的数据类型和事务要求上都存在自己独特的优势。在数据存储的选择中，坚持多样化原则，选择更好更经济的方式，而不是自上而下的统一化。</p>
<p>较常见的，我们可以直接用MongoDB来存储键值对类型的数据，如：验证码、Session等；由于MongoDB的横向扩展能力，也可以用来存储数据规模会在未来变的非常巨大的数据，如：日志、评论等；由于MongoDB存储数据的弱类型，也可以用来存储一些多变json数据，如：与外系统交互时经常变化的JSON报文。而对于一些对数据有复杂的高事务性要求的操作，如：账户交易等就不适合使用MongoDB来存储。</p>
<p>MongoDB官网：https://www.mongodb.org/</p>
<h2> 动手试试</h2>
<p><strong>第一步</strong>：引入依赖</p>
<p>Spring Boot中可以通过在pom.xml中加入spring-boot-starter-data-mongodb引入对mongodb的访问支持依赖。它的实现依赖spring-data-mongodb。是的，您没有看错，又是spring-data的子项目，之前介绍过spring-data-jpa、spring-data-redis，对于mongodb的访问，spring-data也提供了强大的支持，下面就开始动手试试吧。</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：创建用户实体<code>User</code></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：实现用户实体<code>User</code>的数据访问对象<code>UserRepository</code></p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在Spring Data的抽象下，是不是同其他Spring Data子项目一样的简洁、好用、易学！</p>
<p><strong>第四步</strong>：编写单元测试</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>这里注意所使用的<code>Assertions</code>是Spring Boot 2.4之后整合的版本，之前的版本还是使用<code>Assert</code></p>
</blockquote>
<p><strong>第五步</strong>：参数配置</p>
<p>通过上面的例子，我们可以轻而易举的对MongoDB进行访问，但是实战中，应用服务器与MongoDB通常不会部署于同一台设备之上，这样就无法使用自动化的本地配置来进行使用。这个时候，我们也可以方便的配置来完成支持，只需要在<code>application.properties</code>中加入mongodb服务端的相关配置，具体示例如下：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p>在尝试此配置时，记得在mongo中对test库创建具备读写权限的用户（用户名为name，密码为pass），不同版本的用户创建语句不同，注意查看文档做好准备工作</p>
<p>若使用mongodb 2.x，也可以通过如下参数配置，该方式不支持mongodb 3.x。</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h2> 题外话</h2>
<p>MongoDB虽然在过去的一段时间受到不少的关注，但由于其在各方面都表现中庸，最近似乎越来越少听到或者看到关于MongoDB的大规模应用场景。就笔者所接触的很多以往的使用场景也都开始在使用ES来取代，以获得更好的性能表现。</p>
<p>所以，下次我们就来讲讲Spring Boot中如何使用ES吧！关注我，持续获得更多Spring Boot的技术干货！</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter6-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用LDAP来管理用户与组织数据</title>
      <link>https://spring.didispace.com/spring-boot-2/6-2-ldap.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/6-2-ldap.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用LDAP来管理用户与组织数据</source>
      <description>Spring Boot 2.x基础教程：使用LDAP来管理用户与组织数据 很多时候，我们在做公司系统或产品时，都需要自己创建用户管理体系，这对于开发人员来说并不是什么难事，但是当我们需要维护多个不同系统并且相同用户跨系统使用的情况下，如果每个系统维护自己的用户信息，那么此时用户信息的同步就会变的比较麻烦，对于用户自身来说也会非常困扰，很容易出现不同系统密码不一致啊等情况出现。 如果此时我们引入LDAP来集中存储用户的基本信息并提供统一的读写接口和校验机制，那么这样的问题就比较容易解决了。尤其在一些内部管理系统的开发和搭建时，往往我们的内部系统一开始并不全是自己开发的，还有很多第三方产品支持，比如：OA系统、财务系统等，如果自己开发一套用户管理系统，那么这些系统对接还得二次开发，成本很大。由于LDAP并不是什么新技术，大部分成熟软件都支持用LDAP来管理用户，所以时至今日，LDAP的应用依然可以经常看到。 下面我们就具体来看看，当使用Spring Boot开发的时候，如何来访问LDAP服务端。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用LDAP来管理用户与组织数据</h1>
<blockquote>
<p>很多时候，我们在做公司系统或产品时，都需要自己创建用户管理体系，这对于开发人员来说并不是什么难事，但是当我们需要维护多个不同系统并且相同用户跨系统使用的情况下，如果每个系统维护自己的用户信息，那么此时用户信息的同步就会变的比较麻烦，对于用户自身来说也会非常困扰，很容易出现不同系统密码不一致啊等情况出现。</p>
<p>如果此时我们引入LDAP来集中存储用户的基本信息并提供统一的读写接口和校验机制，那么这样的问题就比较容易解决了。尤其在一些内部管理系统的开发和搭建时，往往我们的内部系统一开始并不全是自己开发的，还有很多第三方产品支持，比如：OA系统、财务系统等，如果自己开发一套用户管理系统，那么这些系统对接还得二次开发，成本很大。由于LDAP并不是什么新技术，大部分成熟软件都支持用LDAP来管理用户，所以时至今日，LDAP的应用依然可以经常看到。</p>
<p>下面我们就具体来看看，当使用Spring Boot开发的时候，如何来访问LDAP服务端。</p>
</blockquote>
<h2> LDAP简介</h2>
<p>LDAP（轻量级目录访问协议，Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务。目录服务是一种特殊的数据库系统，其专门针对读取，浏览和搜索操作进行了特定的优化。目录一般用来包含描述性的，基于属性的信息并支持精细复杂的过滤能力。目录一般不支持通用数据库针对大量更新操作操作需要的复杂的事务管理或回卷策略。而目录服务的更新则一般都非常简单。这种目录可以存储包括个人信息、web链结、jpeg图像等各种信息。为了访问存储在目录中的信息，就需要使用运行在TCP/IP 之上的访问协议—LDAP。</p>
<p>LDAP目录中的信息是是按照树型结构组织，具体信息存储在条目(entry)的数据结构中。条目相当于关系数据库中表的记录；条目是具有区别名DN （Distinguished Name）的属性（Attribute），DN是用来引用条目的，DN相当于关系数据库表中的关键字（Primary Key）。属性由类型（Type）和一个或多个值（Values）组成，相当于关系数据库中的字段（Field）由字段名和数据类型组成，只是为了方便检索的需要，LDAP中的Type可以有多个Value，而不是关系数据库中为降低数据的冗余性要求实现的各个域必须是不相关的。LDAP中条目的组织一般按照地理位置和组织关系进行组织，非常的直观。LDAP把数据存放在文件中，为提高效率可以使用基于索引的文件数据库，而不是关系数据库。类型的一个例子就是mail，其值将是一个电子邮件地址。</p>
<p>LDAP的信息是以树型结构存储的，在树根一般定义国家(c=CN)或域名(dc=com)，在其下则往往定义一个或多个组织 (organization)(o=Acme)或组织单元(organizational units) (ou=People)。一个组织单元可能包含诸如所有雇员、大楼内的所有打印机等信息。此外，LDAP支持对条目能够和必须支持哪些属性进行控制，这是有一个特殊的称为对象类别(objectClass)的属性来实现的。该属性的值决定了该条目必须遵循的一些规则，其规定了该条目能够及至少应该包含哪些属性。例如：inetorgPerson对象类需要支持sn(surname)和cn(common name)属性，但也可以包含可选的如邮件，电话号码等属性。</p>
<p><strong>LDAP简称对应</strong></p>
<ul>
<li>o：organization（组织-公司）</li>
<li>ou：organization unit（组织单元-部门）</li>
<li>c：countryName（国家）</li>
<li>dc：domainComponent（域名）</li>
<li>sn：surname（姓氏）</li>
<li>cn：common name（常用名称）</li>
</ul>
<p><em>以上内容参考自：<a href="https://www.cnblogs.com/obpm/archive/2010/08/28/1811065.html" target="_blank" rel="noopener noreferrer">LDAP快速入门</a></em></p>
<h2> 入门示例</h2>
<p>在了解了LDAP的基础概念之后，我们通过一个简单例子进一步理解！</p>
<ul>
<li>创建一个基础的Spring Boot项目</li>
<li>在<code>pom.xml</code>中引入两个重要依赖</li>
</ul>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中，<code>spring-boot-starter-data-ldap</code>是Spring Boot封装的对LDAP自动化配置的实现，它是基于spring-data-ldap来对LDAP服务端进行具体操作的。</p>
<p>而<code>unboundid-ldapsdk</code>主要是为了在这里使用嵌入式的LDAP服务端来进行测试操作，所以<code>scope</code>设置为了test，实际应用中，我们通常会连接真实的、独立部署的LDAP服务器，所以不需要此项依赖。</p>
<ul>
<li>在<code>src/test/resources</code>目录下创建<code>ldap-server.ldif</code>文件，用来存储LDAP服务端的基础数据，以备后面的程序访问之用。</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里创建了一个基础用户，真实姓名为<code>zhaiyongchao</code>，常用名<code>didi</code>，在后面的程序中，我们会来读取这些信息。更多内容解释大家可以深入学习LDAP来理解，这里不做过多的讲解。</p>
<ul>
<li>在<code>application.properties</code>中添加嵌入式LDAP的配置</li>
</ul>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>使用spring-data-ldap的基础用法，定义LDAP中属性与我们Java中定义实体的关系映射以及对应的Repository</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过上面的定义之后，已经将Person对象与LDAP存储内容实现了映射，我们只需要使用<code>PersonRepository</code>就可以轻松的对LDAP内容实现读写。</p>
<ul>
<li>创建单元测试用例读取所有用户信息：</li>
</ul>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>启动该测试用例之后，我们可以看到控制台中输出了刚才维护在<code>ldap-server.ldif</code>中的用户信息：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><h3> 添加用户</h3>
<p>通过上面的入门示例，如果您能够独立完成，那么在Spring Boot中操作LDAP的基础目标已经完成了。</p>
<p>如果您足够了解Spring Data，其实不难想到，这个在其下的子项目必然也遵守Repsitory的抽象。所以，我们可以使用上面定义的<code>PersonRepository</code>来轻松实现操作，比如下面的代码就可以方便的往LDAP中添加用户：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果还想实现更多操作，您可以参考spring-data-ldap的文档来进行使用。</p>
<h3> 连接LDAP服务端</h3>
<p>在本文的例子中都采用了嵌入式的LDAP服务器，事实上这种方式也仅限于我们本地测试开发使用，真实环境下LDAP服务端必然是独立部署的。</p>
<p>在Spring Boot的封装下，我们只需要配置下面这些参数就能将上面的例子连接到远端的LDAP而不是嵌入式的LDAP。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>关注我，后面更新如何与Spring Security结合使用！</strong></p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>。学习过程中如遇困难，建议加入<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter2-8</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用时序数据库InfluxDB</title>
      <link>https://spring.didispace.com/spring-boot-2/6-3-influxdb.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/6-3-influxdb.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用时序数据库InfluxDB</source>
      <description>Spring Boot 2.x基础教程：使用时序数据库InfluxDB 除了最常用的关系数据库和缓存之外，之前我们已经介绍了在Spring Boot中如何配置和使用MongoDB、LDAP这些存储的案例。接下来，我们继续介绍另一种特殊的数据库：时序数据库InfluxDB在Spring Boot中的使用。 InfluxDB简介</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用时序数据库InfluxDB</h1>
<p>除了最常用的关系数据库和缓存之外，之前我们已经介绍了在Spring Boot中如何配置和使用<a href="/spring-boot-2/6-1-mongodb/" target="blank">MongoDB</a>、<a href="/spring-boot-2/6-2-ldap/" target="blank">LDAP</a>这些存储的案例。接下来，我们继续介绍另一种特殊的数据库：时序数据库InfluxDB在Spring Boot中的使用。</p>
<h2> InfluxDB简介</h2>
<p>什么是时序数据库？全称为时间序列数据库。时间序列数据库主要用于指处理带时间标签（按照时间的顺序变化，即时间序列化）的数据，带时间标签的数据也称为时间序列数据。
时间序列数据主要由电力行业、化工行业等各类型实时监测、检查与分析设备所采集、产生的数据，这些工业数据的典型特点是：产生频率快（每一个监测点一秒钟内可产生多条数据）、严重依赖于采集时间（每一条数据均要求对应唯一的时间）、测点多信息量大（常规的实时监测系统均有成千上万的监测点，监测点每秒钟都产生数据，每天产生几十GB的数据量）。虽然关系型数据库也可以存储基于时间序列的数据，但由于存储结构上的劣势，使得这些数据无法高效的实现高频存储和查询统计，因此就诞生了一种专门针对时间序列来做存储和优化的数据库，以满足更高的效率要求。 -- 参考：<a href="https://baike.baidu.com/item/%E6%97%B6%E5%BA%8F%E6%95%B0%E6%8D%AE%E5%BA%93/922671" target="_blank" rel="noopener noreferrer">百度百科：时序数据库</a></p>
<p>InfluxDB就是目前比较流行的开源时序数据库（官网地址：<a href="https://www.influxdata.com/" target="_blank" rel="noopener noreferrer">https://www.influxdata.com/</a>），我们比较常见的使用场景就是一些与时间相关的高频的数据记录和统计需要，比如：监控数据的存储和查询。</p>
<p>在进行下面的动手环节之前，先了解一下InfluxDB中的几个重要名词：</p>
<ul>
<li>database：数据库</li>
<li>measurement：类似于关系数据库中的table（表）</li>
<li>points：类似于关系数据库中的row（一行数据）</li>
</ul>
<p>其中，一个Point由三个部分组成：</p>
<ul>
<li>time：时间戳</li>
<li>fields：记录的值</li>
<li>tags：索引的属性</li>
</ul>
<h2> 动手试试</h2>
<p>在了解了什么是时序数据库以及InfluxDB一些基础概念之后，下面我们通过一个简单的定时上报监控数据的小案例，进一步理解InfluxDB的基础配置、数据组织和写入操作！</p>
<p><strong>第一步</strong>：创建一个基础的Spring Boot项目</p>
<p><strong>第二步</strong>：在<code>pom.xml</code>中引入influx的官方SDK</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>注意：这里因为Spring Boot 2.x版本的parent中有维护InfluxDB的SDK版本，所以不需要手工指明版本信息。如果使用的Spring Boot版本比较老，那么可能会缺少version信息，就需要手工写了。</p>
<p><strong>第三步</strong>：配置要连接的influxdb信息</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>三个属性分别代表：连接地址、用户名、密码。到这一步，基础配置就完成了。</p>
<p>注意：虽然没有spring data的支持，但spring boot 2.x版本中也实现了InfluxDB的自动化配置，所以只需要写好配置信息，就可以使用了。具体配置属性可以查看源码：<code>org.springframework.boot.autoconfigure.influx.InfluxDbProperties</code>。</p>
<p><strong>第四步</strong>：创建定时任务，模拟上报数据，并写入InfluxDB</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2> 测试验证</h2>
<p><strong>第一步</strong>：启动InfluxDB，并通过命令行准备好要使用的数据库，主要涉及的命令如下；</p>
<ul>
<li>进入InfluxDB：</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><ul>
<li>查询当前存在的数据库：</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><ul>
<li>创建数据库（注意数据库名称与上面Java代码中write的第一个参数一致）：</li>
</ul>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div></div></div><p><strong>第二步</strong>：启动Spring Boot应用，在定时任务的作用下，我们会看到类似下面的日志：</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：通过命令，查看一下InfluxDB中是否已经存在这些数据</p>
<div class="language-text line-numbers-mode" data-ext="text"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到，已经存在与日志中一样的数据了。</p>
<p>好了，今天的教程到这里结束了，记得自己动手试试哦！记得关注我，学习不迷路！后面我们还会再继续介绍，如何去展示这些时序数据！</p>
<p>本系列教程<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">《Spring Boot 2.x基础教程》点击直达！</a>，欢迎收藏与转发！如果学习过程中如遇困难？可以加入我们<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！</p>
<h2> 代码示例</h2>
<p>本文的完整工程可以查看下面仓库中的<code>chapter6-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用PostgreSQL数据库</title>
      <link>https://spring.didispace.com/spring-boot-2/6-4-postgresql.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/6-4-postgresql.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用PostgreSQL数据库</source>
      <description>Spring Boot 2.x基础教程：使用PostgreSQL数据库 在如今的关系型数据库中，有两个开源产品是你必须知道的。其中一个是MySQL，相信关注我的小伙伴们一定都不陌生，因为之前的Spring Boot关于关系型数据库的所有例子都是对MySQL来介绍的。而今天我们将介绍另外一个开源关系型数据库：PostgreSQL，以及在Spring Boot中如何使用。 PostgreSQL简介 在学习PostgreSQL的时候，我们总是会将其与MySQL放一起来比较：MySQL自称是最流行的开源数据库，而PostgreSQL则标榜自己是最先进的开源数据库，那么有多先进呢？下面就一起认识一下它！</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用PostgreSQL数据库</h1>
<p>在如今的关系型数据库中，有两个开源产品是你必须知道的。其中一个是MySQL，相信关注我的小伙伴们一定都不陌生，因为之前的Spring Boot关于关系型数据库的所有例子都是对MySQL来介绍的。而今天我们将介绍另外一个开源关系型数据库：<strong>PostgreSQL</strong>，以及在Spring Boot中如何使用。</p>
<h1> PostgreSQL简介</h1>
<p>在学习PostgreSQL的时候，我们总是会将其与MySQL放一起来比较：MySQL自称是最流行的开源数据库，而PostgreSQL则标榜自己是最先进的开源数据库，那么有多先进呢？下面就一起认识一下它！</p>
<p>PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统（ORDBMS），是以加州大学计算机系开发的POSTGRES，4.2版本为基础的对象关系型数据库管理系统。POSTGRES的许多领先概念只是在比较迟的时候才出现在商业网站数据库中。PostgreSQL支持大部分的SQL标准并且提供了很多其他现代特性，如复杂查询、外键、触发器、视图、事务完整性、多版本并发控制等。同样，PostgreSQL也可以用许多方法扩展，例如通过增加新的数据类型、函数、操作符、聚集函数、索引方法、过程语言等。另外，因为许可证的灵活，任何人都可以以任何目的免费使用、修改和分发PostgreSQL。</p>
<h1> PostgreSQL的优势</h1>
<p>既然跟MySQL一样，同为关系型数据库，那么什么时候用MySQL，什么时候用PostgreSQL自然是我们需要去了解的。所以下面简单介绍一下，PostgreSQL相比于MySQL来说，都有哪些优势，如果你有这些需求，那么选择PostgreSQL就优于MySQL，反之则还是选择MySQL更佳：</p>
<ul>
<li>支持存储一些特殊的数据类型，比如：array、json、jsonb</li>
<li>对地理信息的存储与处理有更好的支持，所以它可以成为一个空间数据库，更好的管理数据测量和几何拓扑分析</li>
<li>可以快速构建REST API，通过PostgREST可以方便的为任何PostgreSQL数据库提供RESTful API的服务</li>
<li>支持树状结构，可以更方便的处理具备此类特性的数据存储</li>
<li>外部数据源支持，可以把MySQL、Oracle、CSV、Hadoop等当成自己数据库中的表来进行查询</li>
<li>对索引的支持更强，PostgreSQL支持 B-树、哈希、R-树和 Gist 索引。而MySQL取决于存储引擎。MyISAM：BTREE，InnoDB：BTREE。</li>
<li>事务隔离更好，MySQL 的事务隔离级别repeatable read并不能阻止常见的并发更新，得加锁才可以，但悲观锁会影响性能，手动实现乐观锁又复杂。而 PostgreSQL 的列里有隐藏的乐观锁 version 字段，默认的 repeatable read 级别就能保证并发更新的正确性，并且又有乐观锁的性能。</li>
<li>时间精度更高，可以精确到秒以下</li>
<li>字符支持更好，MySQL里需要utf8mb4才能显示emoji，PostgreSQL没这个坑</li>
<li>存储方式支持更大的数据量，PostgreSQL主表采用堆表存放，MySQL采用索引组织表，能够支持比MySQL更大的数据量。</li>
<li>序列支持更好，MySQL不支持多个表从同一个序列中取id，而PostgreSQL可以</li>
<li>增加列更简单，MySQL表增加列，基本上是重建表和索引，会花很长时间。PostgreSQL表增加列，只是在数据字典中增加表定义，不会重建表。</li>
</ul>
<p>这里仅列举了开发者视角关注的一些优势，还有一些其他优势读者可查看<a href="https://www.biaodianfu.com/mysql-vs-postgresql.html" target="_blank" rel="noopener noreferrer">这篇文章</a>，获得更详细的解读。</p>
<h1> 下载与安装</h1>
<p>读者可以通过下面的链接获取PostgreSQL各版本的安装程序，这里不对安装过程做详细描述了，根据安装程序的指引相信大家都能完成安装（一路next，设置访问密码和端口即可）。</p>
<blockquote>
<p>下载地址：https://www.enterprisedb.com/downloads/postgres-postgresql-downloads</p>
</blockquote>
<p><strong>注意</strong>：因为14是今天刚发布的版本，为避免Spring Boot的兼容问题，还是选用之前的13.4版本来完成下面的实验。</p>
<p>安装完成后，打开pgAdmin。因为自带了界面化的管理工具，所以如果你用过mysql等任何关系型数据库的话，基本不用怎么学，就可以上手使用了。</p>
<p><img src="https://static.didispace.com/images/pasted-607.png" alt="PostgreSQL pgAdmin"></p>
<h1> Spring Boot中如何使用</h1>
<p>在安装好了PostgreSQL之后，下面我们尝试一下在Spring Boot中使用PostgreSQL数据库。</p>
<p><strong>第一步</strong>：创建一个基础的Spring Boot项目（如果您还不会，可以参考这篇文章：<a href="https://blog.didispace.com/spring-boot-learning-21-1-1/" target="_blank" rel="noopener noreferrer">快速入门</a>）</p>
<p><strong>第二步</strong>：在<code>pom.xml</code>中引入访问PostgreSQL需要的两个重要依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里<code>postgresql</code>是必须的，<code>spring-boot-starter-data-jpa</code>的还可以替换成其他的数据访问封装框架，比如：MyBatis等，具体根据你使用习惯来替换依赖即可。因为已经是更上层的封装，所以基本使用与之前用MySQL是类似的，所以你也可以参考<a href="/spring-boot-2/4-4-spring-data-jpa/" target="blank">之前MySQL的文章</a>进行配置，但数据源部分需要根据下面的部分配置。</p>
<p><strong>第三步</strong>：在配置文件中为PostgreSQL数据库配置数据源、以及JPA的必要配置。</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：创建用户信息实体，映射<code>user_info</code>表（最后完成可在pgAdmin中查看）</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第五步</strong>：创建用户信息实体的增删改查</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第六步</strong>：创建单元测试，尝试一下增删改查操作。</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>把单元测试跑起来：</p>
<p><img src="https://static.didispace.com/images/pasted-609.png" alt="单元测试"></p>
<p>一切顺利的话，因为这里用的是create策略，所以表还在，打开pgAdmin，可以看到user_info表自动创建出来了，里面的数据也可以查到，看看跟单元测试的逻辑是否符合。</p>
<p><img src="https://static.didispace.com/images/pasted-608.png" alt="PostgreSQL pgAdmin"></p>
<h1> 思考一下</h1>
<p>如果您之前有读过本系列教程中关于MySQL的10多篇使用案例，再看这篇使用PostgreSQL的案例，是不是感觉差别非常小？其实真正变动的部分主要是两个地方：</p>
<ol>
<li>数据库驱动的依赖</li>
<li>数据源的配置信息</li>
</ol>
<p>而对于更为上层的数据操作，其实并没有太大的变化，尤其是当使用Spring Data JPA的时候，这就是抽象的魅力所在！你体会到了吗？</p>
<p>好了，今天的学习就到这里！如果您在学习过程中遇到困难？可以加入我们超高质量的<a href="https://www.didispace.com/jiaqun.html" target="_blank" rel="noopener noreferrer">Spring技术交流群</a>，参与交流与讨论，更好的学习与进步！更多<a href="https://www.didispace.com/spring-boot-2/" target="_blank" rel="noopener noreferrer">Spring Boot教程可以点击直达！</a>，欢迎收藏与转发支持！</p>
<h1> 代码示例</h1>
<p>本文的完整工程可以查看下面仓库中<code>2.x</code>目录下的<code>chapter6-4</code>工程：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
<h1> 参考资料：</h1>
<ul>
<li>https://baike.baidu.com/item/PostgreSQL</li>
<li>https://www.biaodianfu.com/mysql-vs-postgresql.html</li>
</ul>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-607.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用 Thymeleaf开发Web页面</title>
      <link>https://spring.didispace.com/spring-boot-2/7-1-thymeleaf.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/7-1-thymeleaf.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用 Thymeleaf开发Web页面</source>
      <description>Spring Boot 2.x基础教程：使用 Thymeleaf开发Web页面 通过本系列教程的前几章内容（API开发、数据访问）。我们已经具备完成一个涵盖数据存储、提供HTTP接口的完整后端服务了。依托这些技能，我们已经可以配合前端开发人员，一起来完成一些前后端分离的Web项目，或是一些小程序、或者是App之类的应用开发。 对于Web项目来说，前后端分离模式是目前最为流行的，主要得益于前端框架的完善以及前后端分离方案的日渐成熟。这样的实现模式帮助Web类产品的开发团队更好的拆分任务，以及让开发人员更加聚焦在某一端的开发技术之上。所以，在本教程中，优先介绍了如何开发API，而不是开发Web页面。但是，传统模式的Web页面在一个项目中就可以管理，如果开发人员技能本身就可覆盖全栈，那直接采用传统模板引擎方式开发，也是个不错的选择。尤其对于一些老团队，对模板引擎非常熟悉，可以减少非常多的学习成本，直接上手Spring Boot来开发Web应用。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用 Thymeleaf开发Web页面</h1>
<p>通过<a href="/spring-boot-2/" target="blank">本系列教程</a>的前几章内容（API开发、数据访问）。我们已经具备完成一个涵盖数据存储、提供HTTP接口的完整后端服务了。依托这些技能，我们已经可以配合前端开发人员，一起来完成一些前后端分离的Web项目，或是一些小程序、或者是App之类的应用开发。</p>
<p>对于Web项目来说，前后端分离模式是目前最为流行的，主要得益于前端框架的完善以及前后端分离方案的日渐成熟。这样的实现模式帮助Web类产品的开发团队更好的拆分任务，以及让开发人员更加聚焦在某一端的开发技术之上。所以，在本教程中，优先介绍了如何开发API，而不是开发Web页面。但是，传统模式的Web页面在一个项目中就可以管理，如果开发人员技能本身就可覆盖全栈，那直接采用传统模板引擎方式开发，也是个不错的选择。尤其对于一些老团队，对模板引擎非常熟悉，可以减少非常多的学习成本，直接上手Spring Boot来开发Web应用。</p>
<p>接下来，我们就来具体讲讲在Spring Boot应用中，如何使用Thymeleaf模板引擎开发Web页面类的应用。</p>
<h2> 静态资源访问</h2>
<p>在我们开发Web应用的时候，需要引用大量的js、css、图片等静态资源。Spring Boot默认提供静态资源目录位置需置于classpath下，目录名需符合如下规则：</p>
<ul>
<li>/static</li>
<li>/public</li>
<li>/resources</li>
<li>/META-INF/resources</li>
</ul>
<p>举例：我们可以在<code>src/main/resources/</code>目录下创建<code>static</code>，在该位置放置一个图片文件。启动程序后，尝试访问<code>http://localhost:8080/D.jpg</code>。如能显示图片，配置成功。</p>
<h2> 渲染Web页面</h2>
<p>在之前的示例中，我们都是通过<code>@RestController</code>来处理请求，所以返回的内容为json对象。那么如果需要渲染html页面的时候，要如何实现呢？</p>
<h3> 模板引擎</h3>
<p>在动态HTML实现上Spring Boot依然可以完美胜任，并且提供了多种模板引擎的默认配置支持，所以在推荐的模板引擎下，我们可以很快的上手开发动态网站。</p>
<p>Spring Boot提供了自动化配置模块的模板引擎主要有以下几种：</p>
<ul>
<li>Thymeleaf</li>
<li>FreeMarker</li>
<li>Groovy</li>
</ul>
<p>当你使用上述模板引擎中的任何一个，它们默认的模板配置路径为：<code>src/main/resources/templates</code>。当然也可以修改这个路径，具体如何修改，可在后续各模板引擎的配置属性中查询并修改。</p>
<p><strong>补充</strong>：Spring Boot从一开始就建议使用模板引擎，避免使用JSP。同时，随着Spring Boot版本的迭代，逐步的淘汰了一些较为古老的模板引擎。</p>
<h3> Thymeleaf</h3>
<p>Thymeleaf是本文我们将具体介绍使用的模板引擎。它是一个XML/XHTML/HTML5模板引擎，可用于Web与非Web环境中的应用开发。它是一个开源的Java库，基于Apache License 2.0许可，由Daniel Fernández创建，该作者还是Java加密库Jasypt的作者。</p>
<p>Thymeleaf提供了一个用于整合Spring MVC的可选模块，在应用开发中，你可以使用Thymeleaf来完全代替JSP或其他模板引擎，如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式，因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码，开发者只需将标签属性添加到模板中即可。接下来，这些标签属性就会在DOM（文档对象模型）上执行预先制定好的逻辑。</p>
<p>示例模板：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到Thymeleaf主要以属性的方式加入到html标签中，浏览器在解析html时，当检查到没有的属性时候会忽略，所以Thymeleaf的模板可以通过浏览器直接打开展现，这样非常有利于前后端的分离。</p>
<h2> 动手试一下</h2>
<p><strong>第一步</strong>：新建一个Spring Boot应用，在<code>pom.xml</code>中加入所需的模板引擎模块，比如使用<code>thymeleaf</code>的话，只需要引入下面依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第二步</strong>：创建一个Spring MVC的传统Controller，用来处理根路径的请求，将解决渲染到index页面上，具体实现如下</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>简要说明：</p>
<ul>
<li>在渲染到index页面的时候，通过ModelMap，往页面中增加一个<code>host</code>参数，其值为<code>https://blog.didispace.com</code></li>
<li><code>return</code>的值index代表了要使用的模板页面名称，默认情况下，它将对应到<code>src/main/resources/templates/</code>目录下的<code>index.html</code>模板页面</li>
</ul>
<p><strong>第三步</strong>：根据上一步要映射的模板，去模板路径<code>src/main/resources/templates</code>下新建模板文件<code>index.html</code>，内容如下：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在该页面的body中，包含了一个带有Thymeleaf属性的h1标签，该便签内容将绑定<code>host</code>参数的值。</p>
<p>由于Thymeleaf通过属性绑定的特性。该模板页面同其他模板引擎不同，直接通过浏览器打开html页面，它是可以正常运作的，将会直接展现Hello World标题。这有利于开发页面的时候可以在非启动环境下验证应前端样式的正确性。</p>
<p>如果启动程序后，访问<code>http://localhost:8080/</code>，上面页面就会展示Controller中host的值：<code>https://blog.didispace.com</code>，做到了不破坏HTML自身内容的数据逻辑分离。</p>
<p>更多Thymeleaf的页面语法，可以访问Thymeleaf的官方文档来深入学习使用。</p>
<h2> Thymeleaf的配置参数</h2>
<p>如有需要修改默认配置的时候，只需复制下面要修改的属性到<code>application.properties</code>中，并修改成需要的值：</p>
<div class="language-bash line-numbers-mode" data-ext="sh"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>举几个我们常用的配置内容：</p>
<p><strong>Q：不想每次修改页面都重启</strong></p>
<p>A：修改<code>spring.thymeleaf.cache</code>参数，设置为<code>false</code></p>
<p><strong>Q：不想使用template目录存放模板文件</strong></p>
<p>A：修改<code>spring.thymeleaf.prefix</code>参数，设置为你想放置模板文件的目录</p>
<p><strong>Q：不想使用index作为模板文件的扩展名</strong></p>
<p>A：修改<code>spring.thymeleaf.suffix</code>参数，设置为你想用的扩展名</p>
<p><strong>Q：HTML5的严格校验很烦人</strong></p>
<p>A：修改<code>spring.thymeleaf.mode</code>参数，设置为<code>LEGACYHTML5</code></p>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-1</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：使用ECharts绘制各种华丽的数据图表</title>
      <link>https://spring.didispace.com/spring-boot-2/7-2-echarts.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/7-2-echarts.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：使用ECharts绘制各种华丽的数据图表</source>
      <description>Spring Boot 2.x基础教程：使用ECharts绘制各种华丽的数据图表 上一节我们介绍了如何在Spring Boot中使用模板引擎Thymeleaf开发Web应用的基础。接下来，我们介绍一下后端开发经常会遇到的一个场景：可视化图表。 通常，这类需求在客户端应用中不太会用到，但是在后端的各种统计分析模块会经常碰到。比如：通过折线图、柱状图、雷达图等可视化形式，更直观的展现和分析经营状况或系统运行情况。这里我们将引入的数据可视化组件库 ECharts来帮助我们完成这样的任务。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：使用ECharts绘制各种华丽的数据图表</h1>
<p><a href="https://blog.didispace.com/spring-boot-learning-21-4-1/" target="_blank" rel="noopener noreferrer">上一节</a>我们介绍了如何在Spring Boot中使用模板引擎Thymeleaf开发Web应用的基础。接下来，我们介绍一下后端开发经常会遇到的一个场景：可视化图表。</p>
<p>通常，这类需求在客户端应用中不太会用到，但是在后端的各种统计分析模块会经常碰到。比如：通过折线图、柱状图、雷达图等可视化形式，更直观的展现和分析经营状况或系统运行情况。这里我们将引入的数据可视化组件库 ECharts来帮助我们完成这样的任务。</p>
<h2> ECharts简介</h2>
<p>ECharts是百度开源的一个前端组件。它是一个使用 JavaScript 实现的开源可视化库，可以流畅的运行在 PC 和移动设备上，兼容当前绝大部分浏览器（IE8/9/10/11，Chrome，Firefox，Safari等），底层依赖矢量图形库 ZRender，提供直观，交互丰富，可高度个性化定制的数据可视化图表。</p>
<p>它提供了常规的折线图、柱状图、散点图、饼图、K线图，用于统计的盒形图，用于地理数据可视化的地图、热力图、线图，用于关系数据可视化的关系图、treemap、旭日图，多维数据可视化的平行坐标，还有用于 BI 的漏斗图，仪表盘，并且支持图与图之间的混搭。</p>
<p><img src="https://static.didispace.com/images/pasted-315.png" alt=""></p>
<p>除了已经内置的包含了丰富功能的图表，ECharts 还提供了自定义系列，只需要传入一个renderItem函数，就可以从数据映射到任何你想要的图形，更棒的是这些都还能和已有的交互组件结合使用而不需要操心其它事情。</p>
<p>你可以在下载界面下载包含所有图表的构建文件，如果只是需要其中一两个图表，又嫌包含所有图表的构建文件太大，也可以在在线构建中选择需要的图表类型后自定义构建。</p>
<ul>
<li>官方网站：https://www.echartsjs.com/zh/index.html</li>
<li>案例页面：https://www.echartsjs.com/examples/zh/index.html</li>
</ul>
<h2> 动手试一试</h2>
<p><strong>第一步</strong>：创建一个基础Spring Boot应用，或者拿上一节的工程chapter4-1（仓库地址见文末）来进行加工。</p>
<p><strong>第二步</strong>：在<code>pom.xml</code>中引入Web应用需要的<code>web模块</code>和模板引擎<code>thymeleaf模块</code>，比如用Thymeleaf的时候：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第三步</strong>：编写一个Controller，将<code>/</code>路径的请求，映射到<code>index.html</code>页面</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：在<code>resources/templates</code>目录下创建<code>index.html</code>页面，具体内容如下：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在页面内容中主要包含三部分：</p>
<ul>
<li><code>&lt;head&gt;</code>中通过<code>&lt;script&gt;</code>标签引入ECharts的组件JS，这里使用了bootcss的免费公共cdn。如果用于自己生产环境，不建议使用这类免费CDN的JS或者CSS等静态资源。可以从官网下载所需的静态内容，放到Spring Boot的静态资源位置（如果不知道在哪，可见<a href="https://blog.didispace.com/spring-boot-learning-21-4-1/" target="_blank" rel="noopener noreferrer">上一篇</a>），或是放到自己公司的静态资源管理的服务器上，实现动静分离。</li>
<li><code>&lt;body&gt;</code>中定义了一个id为main的<code>&lt;div&gt;</code>标签，这个标签后续将用来渲染EChart组件</li>
<li>最后的一段<code>&lt;script&gt;</code>内容则是具体的EChart图标的展现初始化和配置。具体配置内容可见代码中的注释信息。</li>
</ul>
<p><strong>第五步</strong>：启动应用，访问<code>localhost:8080</code>，如果上面操作均无差错，那我们就会得到类似下面的折线图：</p>
<p><img src="https://static.didispace.com/images/pasted-317.png" alt=""></p>
<p>关于ECharts图表的调试，官方还提供了一个在线工具，有兴趣的读者可以<a href="https://www.echartsjs.com/examples/zh/editor.html?c=line-simple" target="_blank" rel="noopener noreferrer">点击这里</a>尝试一下。</p>
<p><img src="https://static.didispace.com/images/pasted-316.png" alt=""></p>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-2</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-315.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：实现文件上传</title>
      <link>https://spring.didispace.com/spring-boot-2/7-3-fileupload.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/7-3-fileupload.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：实现文件上传</source>
      <description>Spring Boot 2.x基础教程：实现文件上传 文件上传的功能实现是我们做Web应用时候最为常见的应用场景，比如：实现头像的上传，Excel文件数据的导入等功能，都需要我们先实现文件的上传，然后再做图片的裁剪，excel数据的解析入库等后续操作。 今天通过这篇文章，我们就来一起学习一下如何在Spring Boot中实现文件的上传。 动手试试 第一步：创建一个基础的Spring Boot项目，如果还不会的话就先看看这篇《快速入门》。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：实现文件上传</h1>
<p>文件上传的功能实现是我们做Web应用时候最为常见的应用场景，比如：实现头像的上传，Excel文件数据的导入等功能，都需要我们先实现文件的上传，然后再做图片的裁剪，excel数据的解析入库等后续操作。</p>
<p>今天通过这篇文章，我们就来一起学习一下如何在Spring Boot中实现文件的上传。</p>
<h2> 动手试试</h2>
<p><strong>第一步</strong>：创建一个基础的Spring Boot项目，如果还不会的话就先看看这篇<a href="https://blog.didispace.com/spring-boot-learning-21-1-1/" target="_blank" rel="noopener noreferrer">《快速入门》</a>。</p>
<p><strong>第二步</strong>：在<code>pom.xml</code>中引入模版引擎依赖：</p>
<div class="language-xml line-numbers-mode" data-ext="xml"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>你也可以选择其他你熟悉的模版引擎，比如：Freemarker。</p>
<p><strong>第三步</strong>：在<code>resources</code>目录下，创建新目录<code>templates</code>；在<code>templates</code>目录下再创建一个文件上传的页面<code>upload.html</code>，内容如下：</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><strong>第四步</strong>：创建文件上传的处理控制器，命名为UploadController</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>其中包含这几个重要元素：</p>
<ol>
<li>成员变量<code>path</code>，通过<code>@Value</code>注入配置文件中的<code>file.upload.path</code>属性。这个配置用来定义文件上传后要保存的目录位置。</li>
<li>GET请求，路径<code>/</code>，用于显示<code>upload.html</code>这个文件上传页面。</li>
<li>POST请求。路径<code>/upload</code>，用于处理上传的文件，即：保存到<code>file.upload.path</code>配置的路径下面。</li>
</ol>
<blockquote>
<p>注意：这里主要演示文件上传的主要流程，真实应用还有更多内容要考虑，比如：文件上传后的文件名处理（防止重名）、分布式情况下文件上传后如何共享访问等。更高级的最后，我们后续文章继续讲。</p>
</blockquote>
<p><strong>第五步</strong>：编辑<code>application.properties</code>配置文件</p>
<div class="language-properties line-numbers-mode" data-ext="properties"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>前两个参数用于限制了上传请求和上传文件的大小，而<code>file.upload.path</code>是上面我们自己定义的用来保存上传文件的路径。</p>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<h2> 测试验证</h2>
<p><strong>第一步</strong>：启动Spring Boot应用，访问<code>http://localhost:8080</code>，可以看到如下的文件上传页面。</p>
<p><img src="https://static.didispace.com/images/pasted-378.png" alt=""></p>
<p><strong>第二步</strong>：选择一个不大于2MB的文件，点击“提交”按钮，完成上传。</p>
<p>如果上传成功，将显示类似下面的页面：</p>
<p><img src="https://static.didispace.com/images/pasted-379.png" alt=""></p>
<p>你可以根据打印的文件路径去查看文件是否真的上传了。</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-378.png" type="image/png"/>
    </item>
    <item>
      <title>Spring Boot 2.x基础教程：多个文件的上传</title>
      <link>https://spring.didispace.com/spring-boot-2/7-4-multi-fileupload.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/7-4-multi-fileupload.html</guid>
      <source url="https://spring.didispace.com/rss.xml">Spring Boot 2.x基础教程：多个文件的上传</source>
      <description>Spring Boot 2.x基础教程：多个文件的上传 昨天，我们介绍了如何在Spring Boot中实现文件的上传。有读者问：那么如果有多个文件要同时上传呢？这就马上奉上，当碰到多个文件要同时上传的处理方法。 动手试试 本文的动手环节将基于Spring Boot中实现文件的上传一文的例子之上，所以读者可以拿上一篇的例子作为基础来进行改造，以体会这之间的区别，下面也主要讲解核心区别的地方。</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> Spring Boot 2.x基础教程：多个文件的上传</h1>
<p>昨天，我们介绍了如何在<a href="/spring-boot-2/7-3-fileupload/" target="blank">Spring Boot中实现文件的上传</a>。有读者问：那么如果有多个文件要同时上传呢？这就马上奉上，当碰到多个文件要同时上传的处理方法。</p>
<h2> 动手试试</h2>
<p>本文的动手环节将基于<a href="/spring-boot-2/7-3-fileupload/" target="blank">Spring Boot中实现文件的上传</a>一文的例子之上，所以读者可以拿上一篇的例子作为基础来进行改造，以体会这之间的区别，下面也主要讲解核心区别的地方。</p>
<p><strong>第一步</strong>：修改文件上传页面的上传表单</p>
<div class="language-html line-numbers-mode" data-ext="html"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到这里多增加一个input文件输入框，同时文件输入框的名称修改为了files，因为是多个文件，所以用了复数。注意：这几个输入框的name是一样的，这样才能在后端处理文件的时候组织到一个数组中。</p>
<p><strong>第二步</strong>：修改后端处理接口</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>几个重要改动：</p>
<ol>
<li><code>MultipartFile</code>使用数组，参数名称files对应html页面中input的name，一定要对应。</li>
<li>后续处理文件的主体（for循环内）跟之前的一样，就是对<code>MultipartFile</code>数组通过循环遍历的方式对每个文件进行存储，然后拼接结果返回信息。</li>
</ol>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<h2> 测试验证</h2>
<p><strong>第一步</strong>：启动Spring Boot应用，访问<code>http://localhost:8080</code>，可以看到如下的文件上传页面。</p>
<p><img src="https://static.didispace.com/images/pasted-380.png" alt=""></p>
<p><strong>第二步</strong>：选择2个不大于2MB的文件，点击“提交”按钮，完成上传。</p>
<p>如果上传成功，将显示类似下面的页面：</p>
<p><img src="https://static.didispace.com/images/pasted-381.png" alt=""></p>
<p>你可以根据打印的文件路径去查看文件是否真的上传了。</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-4</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
      <enclosure url="https://static.didispace.com/images/pasted-380.png" type="image/png"/>
    </item>
    <item>
      <title>文件上传的单元测试怎么写？</title>
      <link>https://spring.didispace.com/spring-boot-2/7-5-fileupload-ut.html</link>
      <guid>https://spring.didispace.com/spring-boot-2/7-5-fileupload-ut.html</guid>
      <source url="https://spring.didispace.com/rss.xml">文件上传的单元测试怎么写？</source>
      <description>文件上传的单元测试怎么写？ 早上有个群友问了一个不错的问题：文件上传的单元测试怎么写？后面也针对后端开发要不要学一下单元测试的话题聊了聊，个人是非常建议后端开发能够学一下单元测试的。所以，今天特地拿出来写一篇说说，并不是因为这有多难写，而是作为出色的后端开发人员，单元测试如果你能考虑周到，那么从代码结构，程序质量上都会有很大的提升。而实际开发过程中，很少有开发人员会特别关注这个方面。 言归正传，下面我们具体说说当碰到需要上传文件的接口，我们要如何写单元测试！ 先来回忆一下，普通接口的单元测试我们是如何写的？看看我们入门例子中的单元测试：</description>
      <category>Spring Boot</category>
      <pubDate>Tue, 02 Feb 2021 18:00:20 GMT</pubDate>
      <content:encoded><![CDATA[<h1> 文件上传的单元测试怎么写？</h1>
<p>早上有个群友问了一个不错的问题：文件上传的单元测试怎么写？后面也针对后端开发要不要学一下单元测试的话题聊了聊，个人是非常建议后端开发能够学一下单元测试的。所以，今天特地拿出来写一篇说说，并不是因为这有多难写，而是作为出色的后端开发人员，单元测试如果你能考虑周到，那么从代码结构，程序质量上都会有很大的提升。而实际开发过程中，很少有开发人员会特别关注这个方面。</p>
<p>言归正传，下面我们具体说说当碰到需要上传文件的接口，我们要如何写单元测试！</p>
<p>先来回忆一下，普通接口的单元测试我们是如何写的？看看我们入门例子中的单元测试：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里我们所用到的核心是MockMvc工具，通过模拟http请求的提交并指定相关的期望返回来完成。</p>
<p>对于文件上传接口，本质上还是http请求的处理，所以MockMvc依然逃不掉，就是上传内容发生了改变，我们只需要去找一下文件上传的模拟对象是哪个，就可以轻松完成这个任务。</p>
<p>具体写法如下：</p>
<div class="language-java line-numbers-mode" data-ext="java"><div class="line-numbers" aria-hidden="true"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>可以看到MockMvc的测试主体是不变的，无非就是请求类型和请求内容发生了改变。</p>
<p><strong>更多本系列免费教程连载<a href="/spring-boot-2/" target="blank">「点击进入汇总目录」</a></strong></p>
<p>今天的这篇挺水，但是否习惯为自己的代码编写单元测试以及能否写好单元测试，是很能看出开发者编码水平的。</p>
<p>所以，我是非常推荐大家能够在编写业务实现的时候，一并为自己的实现写写单元测试，看看是不是单元测试好写，如果不好写，往往就是代码结构设计不太理想导致。水平高点的，甚至可以先定义好接口，并写好单元测试，再去写实现（传说中的测试驱动开发）。</p>
<p>今天的小水分享到这里结束，有更多想法可来公众号、星球或社群交流！</p>
<h2> 代码示例</h2>
<p>本文的相关例子可以查看下面仓库中的<code>chapter4-3</code>目录：</p>
<ul>
<li>Github：<a href="https://github.com/dyc87112/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://github.com/dyc87112/SpringBoot-Learning/</a></li>
<li>Gitee：<a href="https://gitee.com/didispace/SpringBoot-Learning/tree/master/2.x" target="_blank" rel="noopener noreferrer">https://gitee.com/didispace/SpringBoot-Learning/</a></li>
</ul>
<p><strong>如果您觉得本文不错，欢迎<code>Star</code>支持，您的关注是我坚持的动力！</strong></p>
]]></content:encoded>
    </item>
  </channel>
</rss>