当前位置:首页 > 技术 > 正文内容

Java 22 核心特性解析与 Spring Boot 3.3 集成实践

访客 技术 2026年9月25日 13

环境配置与项目构建

Java 22 引入了多项关键的最终发布特性以及极具潜力的预览功能。为了充分利用这些新特性,特别是结合 GraalVM 提前编译(AOT)生成原生镜像的能力,我们需要配置合适的开发环境。GraalVM 原生镜像能够显著降低内存占用并实现毫秒级启动,而 Spring Boot 3.3 对此提供了更完善的底层支持。

推荐使用 SDKMAN 来管理 JDK 版本。通过以下命令安装并设置 GraalVM 社区版作为默认环境:


sdk install java 22-graalce
sdk default java 22-graalce

在安装完成后,通过 java --version 和 native-image --version 验证环境配置是否生效。

接下来,使用 Spring Initializr 初始化一个基于 Spring Boot 3.3.0-SNAPSHOT 的 Maven 项目,并引入 Spring Web、JDBC API、H2 Database 以及 GraalVM Native Support 依赖。为了启用 Java 22 的预览特性并支持原生镜像编译,需要对 pom.xml 进行如下深度配置:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0-SNAPSHOT</version>
        <relativePath/>
    </parent>
    <groupId>com.tech.deepdive</groupId>
    <artifactId>java22-springboot33-demo</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>22</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.graalvm.sdk</groupId>
            <artifactId>graal-sdk</artifactId>
            <version>23.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.graalvm.nativeimage</groupId>
            <artifactId>svm</artifactId>
            <version>23.1.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <enablePreview>true</enablePreview>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>0.10.1</version>
                <configuration>
                    <buildArgs>
                        <buildArg>--features=com.tech.deepdive.ForeignAccessFeature</buildArg>
                        <buildArg>--enable-native-access=ALL-UNNAMED</buildArg>
                        <buildArg>-H:+ForeignAPISupport</buildArg>
                        <buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
                        <buildArg>--enable-preview</buildArg>
                    </buildArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <jvmArguments>--enable-preview</jvmArguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Project Panama 与原生代码互操作

Project Panama 的 Foreign Function & Memory API 彻底改变了 Java 与原生代码交互的方式,取代了繁琐且易错的 JNI。通过 java.lang.foreign 包,开发者可以直接在 Java 中分配内存、调用 C 标准库函数,而无需编写任何 C/C++ 胶水代码。

以下示例展示了如何通过 Panama API 查找并调用 C 标准库中的 printf 函数:


package com.tech.deepdive;

import org.springframework.stereotype.Component;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;

@Component
class NativeInteropDemo implements FeatureExecutor {

    static final FunctionDescriptor PRINTF_DESCRIPTOR = 
            FunctionDescriptor.of(JAVA_INT, ADDRESS);

    private final SymbolLookup symbolLookup;

    NativeInteropDemo(SymbolLookup symbolLookup) {
        this.symbolLookup = symbolLookup;
    }

    @Override
    public void execute() throws Exception {
        var nativeLinker = Linker.nativeLinker();
        var methodHandle = this.symbolLookup.find("printf")
            .map(addr -> nativeLinker.downcallHandle(addr, PRINTF_DESCRIPTOR))
            .orElseThrow();
            
        try (var arena = Arena.ofConfined()) {
            var nativeString = arena.allocateFrom("Hello from Project Panama!");
            methodHandle.invoke(nativeString);
        }
    }
}

为了支持上述调用,我们需要在 Spring 容器中配置一个复合的 SymbolLookup:


@Bean
SymbolLookup compositeSymbolLookup() {
    var loaderLookup = SymbolLookup.loaderLookup();
    var defaultLookup = Linker.nativeLinker().defaultLookup();
    return name -> loaderLookup.find(name).or(() -> defaultLookup.find(name));
}

在构建 GraalVM 原生镜像时,由于 AOT 编译需要预先知道运行时的动态行为,我们必须通过实现 GraalVM 的 Feature 接口来注册外部函数访问权限:


package com.tech.deepdive;

import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;
import static com.tech.deepdive.NativeInteropDemo.PRINTF_DESCRIPTOR;

public class ForeignAccessFeature implements Feature {
    @Override
    public void duringSetup(DuringSetupAccess access) {
        RuntimeForeignAccess.registerForDowncall(PRINTF_DESCRIPTOR);
    }
}

配合 pom.xml 中 native-maven-plugin 的 --features 参数,编译出的原生镜像不仅包含了完整的 Spring 上下文和 H2 数据库,还能在数十毫秒内启动并成功执行原生 C 函数调用。

虚拟线程与并发模型演进

虚拟线程(Virtual Threads)是 Java 21 引入并在 Java 22 中持续优化的核心特性,旨在解决 I/O 密集型任务中的线程阻塞问题。在 Spring Boot 3.2 及以上版本中,只需在配置文件中添加 spring.threads.virtual.enabled=true,即可将传统的平台线程无缝替换为虚拟线程,从而大幅提升系统的并发吞吐量和资源利用率。

与虚拟线程协同设计的还有结构化并发(Structured Concurrency)和作用域值(Scoped Values),这两者在 Java 22 中仍处于预览阶段。结构化并发提供了一种更安全的并发任务编排模型,而作用域值则作为 ThreadLocal 的高效替代方案,特别适合在数百万个虚拟线程的上下文中传递不可变数据。

语言语法糖与开发体验优化

Java 22 引入了多项旨在减少样板代码的预览特性,显著提升了日常开发体验。

未命名的变量和模式:在 Lambda 表达式或模式匹配中,当某些参数未被使用时,现在可以使用下划线 _ 来代替显式声明,使代码意图更加清晰。例如在使用 Spring 的 JdbcClient 映射数据库结果集时:


@Component
class UnnamedVariablesDemo implements FeatureExecutor {
    private final JdbcClient jdbcClient;

    UnnamedVariablesDemo(DataSource dataSource) {
        this.jdbcClient = JdbcClient.create(dataSource);
    }

    record UserProfile(int id, String username) {}

    @Override
    public void execute() throws Exception {
        var profiles = this.jdbcClient.sql("SELECT id, username FROM user_profiles")
            .query((rs, _) -> new UserProfile(rs.getInt("id"), rs.getString("username")))
            .list();
        System.out.println("Loaded profiles: " + profiles.size());
    }
}

超类之前的语句:过去,Java 强制要求 super() 必须是子类构造函数的第一条语句,这导致在调用父类构造函数前需要进行复杂的参数计算时,不得不提取额外的静态辅助方法。Java 22 的预览特性允许在 super() 之前执行普通语句,从而简化了构造函数的逻辑。

隐式声明的类和实例 main 方法:对于简单的脚本或入门示例,Java 22 允许省略类声明、public static void 修饰符以及 String[] 参数,直接编写 void main() { ... },极大降低了初学者的认知负担。

Stream API 扩展:Gatherers

Java 22 引入了 Gatherer API(预览特性),为 Stream API 提供了底层扩展能力。过去,当内置的 Stream 操作无法满足需求时,开发者往往被迫中断流处理,将数据收集到集合中再进行复杂操作。Gatherer 允许开发者自定义有状态的流转换操作,而无需破坏流的惰性求值特性。

以下示例实现了一个自定义的 Gatherer,用于计算整数流的累积和,并将其与并发映射操作组合:


package com.tech.deepdive;

import org.springframework.stereotype.Component;
import java.util.stream.*;

@Component
class CustomGathererDemo implements FeatureExecutor {

    private static Gatherer<Integer, int[], Integer> cumulativeSum() {
        return Gatherer.ofSequential(
            () -> new int[]{0},
            Gatherer.Integrator.ofGreedy((state, element, downstream) -> {
                state[0] += element;
                return downstream.push(state[0]);
            })
        );
    }

    @Override
    public void execute() throws Exception {
        var results = Stream.of(1, 2, 3, 4, 5)
            .gather(cumulativeSum())
            .map(Object::toString)
            .toList();
            
        System.out.println("Cumulative sums: " + results);
    }
}

Class-File API 与字节码解析

Java 22 新增了 java.lang.classfile API(预览特性),为框架开发者提供了一种标准、高效的方式来解析和生成 .class 字节码文件。这取代了对 ASM、ByteBuddy 等第三方库的依赖,使得在运行时或 AOT 阶段分析类结构变得更加便捷。

以下示例展示了如何读取类路径下的字节码资源,并提取其中的方法和字段信息:


package com.tech.deepdive;

import org.springframework.stereotype.Component;
import org.springframework.core.io.ClassPathResource;
import java.lang.classfile.*;

@Component
class BytecodeParserDemo implements FeatureExecutor {

    private final byte[] targetClassBytes;

    BytecodeParserDemo() throws Exception {
        var resource = new ClassPathResource("/com/tech/deepdive/TargetService.class");
        this.targetClassBytes = resource.getContentAsByteArray();
    }

    @Override
    public void execute() throws Exception {
        var classModel = ClassFile.of().parse(this.targetClassBytes);
        
        for (var element : classModel) {
            if (element instanceof MethodModel method) {
                System.out.println("Parsed method: " + method.methodName().stringValue());
            } else if (element instanceof FieldModel field) {
                System.out.println("Parsed field: " + field.fieldName().stringValue());
            }
        }
    }
}

字符串模板

字符串模板(String Templates)为 Java 带来了期待已久的字符串插值功能(预览特性)。通过 STR 模板处理器,开发者可以直接在字符串字面量中嵌入变量和表达式,不仅提高了代码的可读性,还避免了繁琐的字符串拼接操作。该机制的设计具备高度的可扩展性,允许开发者自定义模板处理器以实现特定的格式化或安全校验逻辑。


package com.tech.deepdive;

import org.springframework.stereotype.Component;

@Component
class StringInterpolationDemo implements FeatureExecutor {

    @Override
    public void execute() throws Exception {
        var framework = "Spring Boot";
        var releaseYear = 2024;
        
        var message = STR."""
            Integrating \{framework} with Java 22.
            Target release year: \{releaseYear}.
            """;
            
        System.out.println(message);
    }
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。