Spring-Boot

在 Kubernetes 上实现 Spring Boot SSL 热重载

本文将带你了解如何为在 Kubernetes 上运行的 Spring Boot 应用配置 SSL 证书的热重载。我们将使用 Spring Boot 3.1 和 3.2 版本中引入的两个功能。第一个功能允许我们利用 SSL Bundle 在服务器端和客户端配置和使用自定义 SSL 配置。第二个功能使得在 Spring Boot 应用的嵌入式 Web 服务器中轻松进行 SSL 证书和密钥的热重载。 为了在 Kubernetes 上生成 SSL 证书,我们将使用 cert-manager。“cert-manager” 可以在指定期限后轮换证书,并将其保存为 Kubernetes Secret。之前的文章中介绍了如何在 Secret 更新时自动重启 Pod 的类似方案。我们使用 Stakater Reloader 工具在新版本的 Secret 上自动重启 pod。不过,这次我们使用 Spring Boot 新特性来避免重新启动应用(Pod)。 源码 你也可以克隆我的源代码,亲自尝试一下。首先克隆我的 GitHub 仓库。然后切换到 ssl 目录。你会发现两个 Spring Boot 应用:secure-callme-bundle 和 secure-caller-bundle。之后,你只需按照说明操作即可。 工作原理 在介绍技术细节之前,首先来了解我们的应用架构和面临的挑战。我们需要设计一个解决方案,在 Kubernetes 上运行的服务之间实现 SSL/TLS 通信。这个解决方案必须考虑到证书重载的情况。此外,服务器和客户端必须同时进行重载,以避免通信中出现错误。在服务器端,使用嵌入式 Tomcat 服务器。在客户端应用中,使用 Spring RestTemplate 对象。

Spring Boot 3.2.3 发布

⚠️ 注意 此版本升级到 Hibernate 6.4.4.Final。虽然它包含了许多有价值的错误修复,但在原生镜像(Native Image)中无法正常工作。如果你正在使用 GraalVM,则应在 pom.xml 中使用 hibernate.version 属性将 Hibernate 暂时降级到 6.4.2.Final。 🐞 Bug 修复 如果路径中包含空格,无法解析嵌套的 JAR URL #39675 当使用较长的镜像名称且标记包含非法字符时,镜像构建会运行很长时间 #39638 Banner 打印不遵守设置的字符集 #39621 “micrometer.observations.” 配置属性应为 “management.observations.” #39600 配置类解析过程中的元数据读取使用默认资源加载器,而不是应用的资源加载器 #39598 当将一些 Gson 属性(包括 spring.gson.disable-html-escaping)设置为 false 时,它们的行为不正确 #39524 当配置属性绑定使用转换器从属性值创建一个 Map 时,属性占位符不会被解析 #39515 Gradle 插件允许使用 Gradle 7.4,但文档和测试的最低版本是 7.5 #39513 WebFlux 自动配置应仅在启用虚拟线程时配置 blocking executor #39469 TestcontainersPropertySource 断言有错别字 #39449 缺少参数时,Webflux actuator 端点的响应为 500 #39444 使用 non-shaded Pulsar 客户端和配置身份验证参数时出现 NoSuchMethod 错误 #39389 Jetty GracefulShutdown 会写入 System.

Spring Boot 使用 Redis 实现可靠的消息队列

在应用中把 Redis 当成消息队列来使用已经屡见不鲜了。我想主要原因是当代应用十有八九都会用到 Redis,因此不用再引入其他消息队列系统。而且 Redis 提供了好几种实现消息队列的方法,用起来也简单。 使用 Redis 实现消息队列的几种方式 Redis 提供了多种方式来实现消息队列。 Pub/Sub 订阅发布模式,发布者把消息发布到某个 Channel,该 Channel 的所有订阅者都会收到消息。但是这种方式最大的问题是 发布出去的消息,如果没有被监听消费,或者消费过程中宕机,那么消息就会永远丢失。适合用于临时通知之类的场景,对于需要保证数据不丢失的场景不能使用这种方式。 List List 是 Redis 提供的一种数据类型,底层是链表,可以用来实现队列、栈。 Stream Stream 是一个由 Redis 5 引入的,功能完善的消息队列。想必也是 Redis 官方团队看到太多人拿 Redis 当消息队列使,于是干脆就在 Redis 上设计出一个类似于 Kafka 的消息队列。 Steam 支持消费组消费,一条消息只能被消费组中的其中一个消费者消费。支持 消息确认、支持 回溯消费 还支持把未 ACK(确认)的消息转移给其他消费者进行重新消费,在进行转移的时候还会累计消息的转移次数,当次数达到一定阈值还没消费成功,就可以放入死信队列。 这也是 Redis 种最复杂的一种数据类型。如果你真的到了需要使用 Redis Steam 作为消息队列的地步,那不如直接使用 RabbitMQ 等更加成熟且稳定的消息队列系统。 使用 List 实现可靠的消息队列 目前来说,这是用得最多的一种方式,适用于大多数简单的消息队列应用场景。List 类型有很多指令,但是作为消息队列来说用到的只有几个个: LPUSH key element [element ...] 把元素插入到 List 的首部,如果 List 不存在,会自动创建。 BRPOPLPUSH source destination timeout 移除并且返回 List (source)尾部的最后一个元素,并且同时会把这个元素插入到另一个 List (destination)的首部。

Spring Boot 3.2.2 发布

Spring Boot 3.2.2 版本的详细更新内容如下: 🐞 Bug 修复 SSLBundle 实现未提供有用的 toString() 结果 #39167 JarEntry.getComment() 从 NestedJarFile 实例返回不正确的结果 #39166 在 server.ssl 属性中混合 PEM 和 JKS 类型的证书不起作用 #39158 仅仅在 classpath 上引入 AspectJ 和 Micrometer 并不足以启用对 Micrometer observation 注解的支持 #39128 当映射到 / 时,无法访问未使用选择器(Selector)操作的 Actuator 端点。#39122 由于缺少 Authentication Manager,使用 WebFlux、Security 和 Actuator 的 Spring Boot 3.2 应用可能无法启动 #39096 management.observations.http.server.requests.name 不再生效 #39083 spring.rabbitmq.listener.stream.auto-startup 属性不起作用 #39078 PatternParseException 的日志信息中的错误标记位置不对 #39075 server.jetty.max-connections 配置无效 #39052 依赖于初始的 CharSequence 到 String 转换的 @ConfigurationPropertiesBinding 转换器不再起作用 #39051 在新的加载器实现中无法解析 Manifest attributes #38996 来自日志系统初始化的 Throwable 可能导致应用在启动时默默地失败 #38963 使用 Jetty 时,IO 操作和延迟调度的空闲超时不能设置为小于 30000ms #38960 当 jar 放在 WSL 网络驱动器上时,spring-boot-maven-plugin repackage uber jar 执行失败 #38956 Oracle OJDBC BOM 版本被标记为不适合生产使用 #38943 使用 jOOQ 且未设置 spring.

在 Spring Boot 中通过 @Value 绑定属性到枚举(不分区大小写)

1、概览 Spring 提供了自动配置功能,可以用它来绑定组件、配置 Bean 以及从属性源中设置值。 当我们不想硬编码值,而希望使用 properties 文件或系统环境提供值时,@Value 注解就非常有用。 本文将带你了解如何通过 Spring 自动配置将属性值映射到 Enum 实例,不区分大小写。 2、Converter<F,T> Spring 使用 Converter 将 @Value 注解中的 String 值映射到所需的类型。一个专用的 BeanPostProcessor 会遍历所有组件,并检查它们是否需要额外的配置或注入。然后,找到一个合适的 Converter,并将源 Converter 中的数据绑定目标。Spring 提供了一个内置的 String 到 Enum 类型的 Converter,让我们来详细了解一下。 2.1、LenientToEnumConverter 顾名思义,该 Converter 在转换过程中可以自由解释数据。最初,它假定提供的数值是正确的: @Override public E convert(T source) { String value = source.toString().trim(); if (value.isEmpty()) { return null; } try { return (E) Enum.valueOf(this.enumType, value); } catch (Exception ex) { return findEnum(value); } } 不过,如果无法将源映射到 Enum,它就会尝试另一种方法。它会获取 Enum 和 value 的规范名称:

在 Spring Boot 中使用 Java Record

本文将带你了解如何在 Spring Boot 应用中利用 Java Record 来提高其效率和可读性。 Java Record 是什么? Java Record 是一种专为保存不可变数据而设计的类。它们自动提供 equals()、hashCode() 和 toString() 等方法的实现,大大减少了模板代码。因此,它们非常适合在 Spring Boot 应用中创建数据传输对象(DTO)、实体和其他模型类。 Java Record 示例 举一个简单的例子,用 Java Record 来表示一个 Person。 public record Person(String name, int age) {} 在本例中,Person 是一个 Record,包含两个字段:name 和 age。Java 自动为该 Record 提供了以下内容: 一个 public 构造函数:Person(String name, int age) 属性的 public Getter 方法:name() 和 age() 实现了 equals()、hashCode() 和 toString() 方法 使用 Getter 方法 Record 的主要特点之一是提供了用于访问字段的隐式 Getter 方法。这些方法以字段本身命名。 创建 Person 实例并使用其 Getter 方法:

Spring Boot 中的 @ConfigurationProperties 注解

1、简介 Spring Boot 有许多有用的功能,包括外部化配置和轻松访问 Properties 文件中定义的属性。上一篇文章 介绍了实现这一点的各种方法。 本文将带你了解 Spring Boot 中的 @ConfigurationProperties 注解。 2、项目设置 按照惯例,在 pom.xml 中将 spring-boot-starter-parent 添加为 <parent>: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> <relativePath/> </parent> 为了验证文件中定义的属性,还需要一个 JSR-380 实现,hibernate-validator 就是其中之一,它由 spring-boot-starter-validation 依赖提供。 把它也添加到 pom.xml 中: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 更多详细信息可参阅 Hibernate Validator 入门。 3、示例属性 官方文档建议将配置属性隔离到单独的 POJO 中。 如下: @Configuration @ConfigurationProperties(prefix = "mail") public class ConfigProperties { private String hostName; private int port; private String from; //Get、Set 方法 } 通过 @Configuration 注解,Spring 会在 Application Context 中创建一个 Spring Bean。

Spring 和 Spring Boot 中的属性配置

1、概览 本文将带你了解如何通过 Java 配置和 @PropertySource 在 Spring 中设置和使用 Properties,以及 Properties 在 Spring Boot 中的工作原理。 2、通过注解注册 Properties 文件 Spring 3.1 引入了新的 @PropertySource 注解,作为一种方便的机制,用于将属性源添加到环境中。 可以将此注解与 @Configuration 注解结合使用: @Configuration @PropertySource("classpath:foo.properties") public class PropertiesWithJavaConfig { //... } 另一种非常有用的注册新 Properties 文件的方法是使用占位符,它允许在运行时动态选择不同的文件: @PropertySource({ "classpath:persistence-${envTarget:mysql}.properties" }) ... 2.1、定义多个属性位置 @PropertySource 注解是可重复的(Java 8 的可重复注解特性)。因此,如果使用的是 Java 8 或更高版本,就可以使用此注解来定义多个属性位置: @PropertySource("classpath:foo.properties") @PropertySource("classpath:bar.properties") public class PropertiesWithJavaConfig { //... } 当然,也可以使用 @PropertySources 注解并指定一个 @PropertySource 数组。这不仅适用于 Java 8 或更高版本,也适用于任何受支持的 Java 版本: @PropertySources({ @PropertySource("classpath:foo.properties"), @PropertySource("classpath:bar.properties") }) public class PropertiesWithJavaConfig { //.

Spring Boot 中的测试

1、概览 本文将带你了解如何使用 Spring Boot 中的框架支持来编写测试,包括可以独立运行的单元测试,以及在执行测试之前加载 Spring Application Context 的集成测试。 2、项目设置 本文中的示例项目是一个 “雇员管理 API”,提供了对 Employee 资源的一些操作。是一个典型的MVC三层架构,从 Controller 到 Service 最后到持久层。 3、Maven 依赖 首先,添加测试依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <version>2.5.0</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency> spring-boot-starter-test 是主要的依赖,它包含了测试所需的大部分依赖。 H2 DB 是内存数据库,非常方便用于测试。 3.1、JUnit 4 从 Spring Boot 2.4 开始,JUnit 5 的 Vintage 引擎已从 spring-boot-starter-test 中移除。如果仍想使用 JUnit 4 编写测试,则需要添加以下 Maven 依赖: <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency> 4、使用 @SpringBootTest 进行集成测试 顾名思义,集成测试的重点是集成应用的不同层。这也意味着不涉及模拟(Mock)。

手动写入响应后对 @ResponseBody 注解的影响

问题回溯 2023 年 Q2 某日运营反馈一个问题,商品系统商家中心某批量工具模板无法下载,导致功能无法使用(因为模板是动态变化的) 商家中心报错如下(JSON串): {"code":-1,"msg":"失败"} 负责的同事看到失败后立即与我展开讨论(因为不是关键业务,所以不需要回滚,修复即可),我们发现新功能模板下载的代码与之前的代码有所不同,恰好之前的功能又可以正常运行,所以同事对现有代码进行改造然后预发布测试完成后再次上线。 其他业务代码: /** * 模板下载 */ @RequestMapping("/doBatchWareSetAd") public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); } 上线的结果是:仍然无法使用。 其实也正常:因为两种代码在预发布都可以正常运行,在线上出错只可能是因为其他原因,只不过我们不了解底层原理,害怕它 “可能” 有问题罢了,最终查询得到的结论是权限系统管理员在线上环境没有给我们配置相应的文件,导致请求为空,导致请求失败。 探索 @ResponseBody 与主动写入响应流的关系 我们都知道 @ResponseBody 注解可以帮助我们把返回对象转化为 JSON,方便展示和交互。 那它到底是如何工作的呢,请看下面的讲解: 代码案例 1 @RequestMapping("/test1") @ResponseBody public Map<String, String> test1(HttpServletResponse response) { Map<String, String> map = new HashMap<>(); map.put("1", "1"); return map; } // 响应 JSON报文 Debug 代码,发现其核心处理类为:RequestResponseBodyMethodProcessor。 方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 会处理其相关返回值。 真正的核心处理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters。 关键 DEBUG 记录如下图所示: