1. 引言
Spring Boot 3.1.x 停止维护了,而 3.3.x 作为最新发布的版本,带来了许多新特性和改进。本篇文章将详细介绍这些新特性,并通过样例代码加以解释,帮助开发者更好地掌握和应用这些新功能。
Spring Boot 3.3现已正式发布!此版本包含大量更新,包括多项新功能。我们决定进行一些挑选,并查看最重要的变化,其中包括对类数据共享 (CDS)的支持,以加快应用程序启动速度。
每个版本的生命周期只有 一年。
2. 最低环境要求的变革
Spring Boot | JDK | Maven |
---|---|---|
3.3.x | 17 ~ 22 | 3.6.3+ |
3.2.x | 17 ~ 21 | 3.6.3+ |
3.1.x | 17 ~ 21 | 3.6.3+ |
3.0.x | 17 ~ 21 | 3.5+ |
2.7.x | 8 ~ 20 | 3.5+ |
- Spring Boot 3.0.0 开始将最低支持 Java 17 这一变化意味着开发者需要适应新的 Java 环境;Java 17引入了众多新特性,如密封类和记录类等,为开发者提供了更强大和灵活的编程能力。
- 而 Spring Boot 3.3.x 则更进一步,开始支持 Java 22。新版本在性能、安全性和功能方面又有了显著的提升。
- 从 Java 17 到 Java 22 的更新趋势可以看出,Java语言在不断演进,以满足日益复杂的应用需求和更高的性能要求。对于开发者而言,紧跟这一趋势是至关重要的。
3. CDS 支持
3.1 什么是CDS
简单一句话:它可以缩短启动时间并减少 Spring Boot 应用程序的内存消耗。
类数据共享 (CDS) 是一项 JVM 功能,CDS全称为 Class Data Sharing 即类数据共享。其主要作用是在多个 JVM之间实现类文件的共享,从而显著减少应用程序启动时 JVM 进行类加载所耗费的时间,同时有效降低内存的占用。
我们可以将类数据共享(CDS)视为 Spring Boot 3.3 中最重要的新功能,与CRaC类似,CDS都是JVM提供的功能,但是其减少启动时间和内存占用的机制不同。
3.2 CDS 工作原理
CDS 会生成一个共享类归档文件,通常文件后缀为 .jsa 这个文件包含了经过预处理的类元数据。当 JVM启动时,它能够直接从该共享归档文件中加载这些类元数据,而无需在启动时重新去加载和解析类信息。通过这种方式,大大缩短了启动所需的时间,并且因为类数据的共享,减少了内存的消耗。
要为应用程序创建 CDS 存档,您的 JDK 必须具有基础映像。为了最大限度地减少为 JVM 创建基础 CDS 存档的工作量,您可以使用带有 Liberica JDK 和系统 CDS 的准备好的容器映像。这样,您无需使用特殊命令来生成 CDS 存档。要将 Liberica JDK 容器与 CDS 一起使用,请访问我们的 Docker Hub 存储库并选择带有cds标签的映像。
如果缩短应用程序启动时间对您来说至关重要,那么 CDS 可以被视为标准 JVM 与检查点协调恢复 (CRaC)和 GraalVM Native Image 等创新解决方案之间的中介。无需更改应用程序代码即可使用 CDS,但您可能需要调整运行时设置并考虑几个重要方面(例如,类路径和 JVM 版本必须与构建存档时使用的相同)。
3.3 CDS的使用
简而言之,类数据共享(CDS)包括两个主要步骤:
3.3.1 创建 CDS 存档
从 JDK 12 开始,JDK 核心库类的默认 CDS 存档与 Oracle JDK 二进制文件一起预先打包,位于server文件夹 ( /server/classes.jsa) 中。但是,我们需要为 Java 应用程序类(在本例中为 Spring Boot 应用程序)创建 CDS 存档。Spring框架提供了一个挂钩点来方便创建存档,而Spring Boot使我们可以轻松地为我们的应用程序创建存档文件 ( .jsa)。您可以使用以下命令创建 CDS 存档:
java -Djarmode=tools -jar my-app.jar extract --目标应用程序
cd应用程序
java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar my-app.jar
3.3.2 使用CDS 存档
现在 CDS 存档文件已准备就绪,并且当 JVM 启动时,共享存档(用于 JDK 核心库和我们的 Spring Boot 应用程序)都会进行内存映射,以允许在多个 JVM 进程之间共享这些类的只读 JVM 元数据。
由于访问共享存档比加载类更快,因此它将减少启动时间!!!
要使用 CDS 存档,我们需要在启动应用程序时添加一个额外的参数,并确保使用标志启用了 CDS 存档-Xshare:
java -XX:SharedArchiveFile=application.jsa -jar target/myapp-1.0.0.jar
在启动日志中若出现相关信息,则表示启用 CDS 成功。
如果您想了解有关CDS 的更多信息以及它如何帮助减少 Java 应用程序的启动时间和内存占用。我强烈建议您阅读 Oracle 团队的以下指南:
Java虚拟机CDS功能
4. 支持虚拟线程
4.1 虚拟线程概念
虚拟线程是一种轻量级的线程实现方式,具有以下显著的特点和优势:
- 成本低廉:相较于传统线程,创建和销毁虚拟线程的开销极小,这使得在应用中可以创建大量的虚拟线程而不会造成资源过度消耗。
- 高效利用资源:能够充分利用硬件资源,特别是在高并发场景下,大幅提升资源的利用率。
- 灵活调度:其调度完全由 JDK 中的调度器控制,用户可以更灵活地管理线程的执行。
- 减少阻塞影响:当虚拟线程执行阻塞操作时,不会阻塞操作系统线程,从而有效提高了系统的整体性能和吞吐量。
4.2 虚拟线程与平台线程区别
主要区别在于虚拟线程在运行周期中不依赖于操作系统线程。虚拟线程与硬件分离,因此称为“虚拟”。此外,JVM 提供的抽象层实现了这种分离。
4.3 虚拟线程使用
首先,我们需要根据我们的环境配置我们的应用程序。
4.3.1 至少使用 Spring Boot 3.2 和 Java 21 的虚拟线程
从 Spring Boot 3.2 开始,如果我们使用 Java 21,启用虚拟线程非常容易。我们将spring.threads.virtual.enabled属性设置为true,就可以了:
spring.threads.virtual.enabled=true
理论上,我们不需要做任何其他事情。但是,从普通线程切换到虚拟线程可能会给旧版应用程序带来无法预料的后果。因此,我们必须彻底测试我们的应用程序。
4.3.2 验证虚拟线程是否正在运行
让我们测试一下 Spring Boot 应用程序是否使用虚拟线程来处理 Web 请求调用。为此,我们需要构建一个返回所需信息的简单控制器:
@RestController
@RequestMapping("/thread")
public class ThreadController {
@GetMapping("/name")
public String getThreadName() {
return Thread.currentThread().toString();
}
}
Thread对象的 toString() 方法返回我们需要的所有信息:线程 ID、线程名称、线程组和优先级。让我们使用curl请求访问此端点:
$ curl -s http://localhost:8080/thread/name
$ VirtualThread[#171]/runnable@ForkJoinPool-1-worker-4
我们可以看到,响应明确表明我们正在使用虚拟线程来处理此 Web 请求。换句话说,Thread.currentThread ()调用返回VirtualThread类的一个实例。现在让我们通过一个简单但有效的负载测试来查看虚拟线程的有效性。
4.4 虚拟线程和标准线程的性能比较
为了比较性能,我们将使用JMeter运行负载测试。值得注意的是,这不是完整的性能比较,而是一个起点,我们可以从中构建更多具有不同参数的测试。
在这个特定的场景中,我们将调用RestController中的一个端点,简单地让执行休眠一秒钟,模拟一个复杂的异步任务:
@RestController
@RequestMapping("/load")
public class LoadTestController {
private static final Logger LOG = LoggerFactory.getLogger(LoadTestController.class);
@GetMapping
public void doSomething() throws InterruptedException {
LOG.info("hey, I'm doing something");
Thread.sleep(1000);
}
}
使用**@ConditionalOnProperty** 注释,我们可以在虚拟线程和标准线程之间切换。
JMeter 测试仅包含一个线程组,模拟1000 个并发用户访问/load 端点100秒:
在这种情况下,采用此新功能带来的性能提升是显而易见的。让我们比较一下不同实现的“响应时间图”。如下图,这是标准线程的响应图。我们可以看到,完成调用所需的时间很快就达到了 5000 毫秒:
发生这种情况是因为平台线程是一种有限的资源。当所有调度线程和池化线程都处于繁忙状态时,Spring App 只能保留请求等待,直到有一个线程空闲。
好啦,想必大家都很期待,接下来,让我们看看最期待的虚拟线程会发生什么:
生成的图表显示响应在 1000 毫秒内稳定下来。因此,虚拟线程在请求后立即创建和使用,因为从资源角度来看它们非常便宜。在本例中,我们比较了 Spring 默认固定标准线程池(默认大小为 200)和 Spring 默认无界虚拟线程池的使用情况。
这种性能提升仅在像我们的玩具应用程序这样的简单场景中才有可能。事实上,对于 CPU 密集型操作,虚拟线程并不合适,因为此类任务需要最少的阻塞。
4.5 虚拟线程结论
我们了解了如何在基于 Springboot 3.2及以上版本 的应用程序中启用虚拟线程。
- 首先,我们了解了如何根据应用程序使用的 JDK 启用虚拟线程。
- 其次,我们创建了一个 REST 控制器来返回线程名称。最后,我们使用 JMeter 来确认虚拟线程与标准线程相比使用的资源更少。我们还了解了这如何简化更多请求的处理。
5. 其他一些重要的改进
5.1 可观察性的增强
Spring Boot 3.3 在可观察性方面有显著的增强。新增了对 Micrometer 的 @SpanTag 注解的支持,使开发者能够为自定义跨度添加标签,从而更精细地追踪和监控应用的执行流程。
spring.pulsar.listener.observation-enabled 和 spring.pulsar.template.observations-enabled 属性的默认值从 true 更改为 false 使配置更加明确和可控。
实现了一个新的基于 JDK HttpClient 的 Zipkin 发送器,并且 Brave 更新至 6.0, Zipkin 更新至 3.0 为监控和追踪提供了更强大的功能和更好的性能。
5.2 Spring Security 的增强
Spring Security 增强了对 JwtAuthenticationConverter 的自动配置。如果设置了特定的属性,如 spring.security.oauth2.resourceserver.jwt.authority-prefix 、 spring.security.oauth2.resourceserver.jwt.principal-claim-name 、 spring.security.oauth2.resourceserver.jwt.authorities-claim-name ,JwtAuthenticationConverter 会自动进行配置,简化了安全认证的设置流程,提高了应用的安全性和配置的便捷性。
6. 小结
Spring Boot 3.3 这个版本的变化挺大的,带来了一系列令人瞩目的新特性,为开发者带来了诸多优势,值得让我们赶紧切换以尝试。更多详细内容请参考 SpringBoot 官方文档。