首页 > 编程语言 >当Java 22遇到 SpringBoot 3.3.0!

当Java 22遇到 SpringBoot 3.3.0!

时间:2024-03-23 23:46:41浏览次数:38  
标签:Java SpringBoot 22 java 3.3 org import GraalVM

工程 | JOSH LONG | 0条评论

Java 22发布快乐!

Java 22 是一个重大的进步,是一个值得升级版本。有一些重大的最终发布功能,如 Project Panama及一系列更优秀的预览功能。我不可能覆盖它们全部,但我确实想谈谈我最喜爱的一些。我们将会涉及到许多功能。如果你想在家里跟着做,代码在这

我爱Java 22,当然,我也爱 GraalVM,它们都在发布了新版本!Java 当然是我们最喜爱的运行时和语言,而 GraalVM 是一个高性能的 JDK 发行版,它支持更多语言并允许提前编译(它们被称为 GraalVM native images)。GraalVM 包含了 Java 22 新版的所有好东西,还有一些额外的工具,所以我总是推荐下载那个版本。我特别感兴趣的是 GraalVM native image 的能力。生成的二进制文件几乎可以立即启动,并且与它们的 JRE 相比,消耗的 RAM 明显少。GraalVM 不是新事物,但值得记住的是,Spring Boot 有一个很棒的引擎,支持将你的 Spring Boot 应用程序转化为 GraalVM native images。

1 安装

我正在使用一个出色的 Java 包管理器 SDKMAN。我还在运行带有 macOS 的 Apple Silicon 芯片。所以,这个事实和我喜欢并鼓励使用 GraalVM 的事实稍后会有些重要,所以不要忘了。将会有测试!

sdk install java 22-graalce

我还会设置它为你的默认选择:

sdk default java 22-graalce

在继续之前,打开一个新的 shell,然后通过运行 javac --versionjava --version,和 native-image --version 来验证一切是否正常。

如果你是在遥远的未来阅读这篇文章的(我们已经有飞行汽车了吗?)而且有 50-graalce,那么就尽情安装那个版本!版本越新越好!

2 你总得从某处开始...

在这一点上,我想要开始构建了!所以,我去了互联网上第二喜欢的地方,Spring Initializr - start.spring.io - 并生成了一个新的项目,使用以下规格:

  • 我选择了 3.3.0-snapshot 版本的 Spring Boot。3.3 还没有正式发行,但应该在短短几个月内就会。与此同时,不断前进!这个版本对 Java 22 有更好的支持。
  • 我选择了 Maven 作为构建工具。
  • 我添加了 GraalVM Native Support 支持,H2 Database,和 JDBC API 支持。

我在我的 IDE 中打开了项目,像这样:idea pom.xml。现在我需要配置一些 Maven 插件以支持 Java 22 和一些我们将在本文中看到的预览功能。这是我的完整配置的 pom.xml。它有点密集,所以我会在代码结束后来介绍一下。

COPY<?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/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>0.10.1</version>
                <configuration>
                    <buildArgs>
                        <buildArg> --features=com.example.demo.DemoFeature</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.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <enablePreview>true</enablePreview>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <compilerArguments> --enable-preview </compilerArguments>
                    <jvmArguments> --enable-preview</jvmArguments>
                </configuration>
            </plugin>
            <plugin>
            <groupId>io.spring.javaformat</groupId>
            <artifactId>spring-javaformat-maven-plugin</artifactId>
            <version>0.0.41</version>
            <executions>
                <execution>
                    <phase>validate</phase>
                    <inherited>true</inherited>
                    <goals>
                        <goal>validate</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        </plugins>
    </build>
    <repositories>
    <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>
</project>

我知道,我知道!很多!但实际上并不是这样。这个 pom.xml 几乎和我从 Spring Initializr 获取的一模一样。主要改变:

  • 重新定义 maven-surefire-pluginmaven-compiler-plugin 支持预览功能。
  • 添加 spring-javaformat-maven-plugin 用来支持格式化我的源代码。
  • 添加两个新依赖项:org.graalvm.sdk:graal-sdk:23.1.2org.graalvm.nativeimage:svm:23.1.2,都是专门为后面我们将需要的 GraalVM Feature 实现创建的
  • native-maven-pluginspring-boot-maven-plugin<configuration> 部分添加了配置节

非常快就到了,Spring Boot 3.3 将会正式发布并支持 Java 22,所以可能这个构建文件的一半会消失。(真是春天的清理!)

3 编程快速说明

LanguageDemonstrationRunner ,一个功能性接口,声明可抛 Throwable

package com.example.demo;

@FunctionalInterface
interface LanguageDemonstrationRunner {

    void run() throws Throwable;

}

我还有一个 ApplicationRunner,反过来,它注入了我所有的功能接口实现,然后调用它们的 run 方法,捕获并处理 Throwable

    // ...	
    @Bean
    ApplicationRunner demo(Map<String, LanguageDemonstrationRunner> demos) {
        return _ -> demos.forEach((_, demo) -> {
            try {
                demo.run();
            } //
            catch (Throwable e) {
                throw new RuntimeException(e);
            }
        });
    }
    // ...

好的,既然我们已经讲过了,那就开始吧!

4 再见,JNI!

此版本终于等待了已久的 Project Panama 的发布。我最期待的三个特性之一,另外两个特性是:

  • 虚拟线程
  • GraalVM native images

它们至少已经成为现实六个月了。Project Panama 是让我们能够利用长期以来被拒之门外的 C 和 C++ 代码的星系。回想起来,如果它支持 ELF,我想象。例如 Rust 和 Go 程序可以编译成与 C 兼容的二进制文件,所以我想象(但没有尝试过)这意味着与这些语言的互操作也足够容易。在本节中,当我提到“原生代码”时,我指的是以某种方式编译的二进制文件,它们可以像 C 库那样被调用。

从历史上看,Java 一直是孤立的。对于 Java 开发人员来说,重新使用原生 C 和 C++ 代码并不容易。这是有道理的。原生、特定于操作系统的代码只会破坏 Java 的“一次编写,到处运行”的承诺。它一直是有点禁忌的。但我不明白为什么会这样。公平地说,尽管缺乏易用的原生代码互操作功能,我们也做得不错。几乎任何你想要做的事情,可能都有一个纯 Java 解决方案存在,它可以在 Java 运行的任何地方运行。它运行得很好,直到它不再运行。Java 在这里错过了关键的机会。想象一下:

  • 如果 Kubernetes 是用 Java 构建的?
  • 如果当前的 AI 革命是由 Java 驱动的?

这两个概念会不可思议,当 Numpy、Scipy 和 Kubernetes 最初创建时,但是今天?今天,他们发布了 Panama 项目。

Panama 项目引入了一种容易连接原生代码的方法。支持两个级别。你可以以相当低级的方式操纵内存,并将数据在原生代码中来回传递。我说“来回”,但我可能应该说“向下和向上”到原生代码。Panama 项目支持“向下调用”,即从 Java 调用原生代码,以及“向上调用”,即从原生代码调用 Java。你可以调用函数、分配和释放内存、读取和更新 struct 中的字段等等。

让我们来看一个简单的例子。代码使用新的 java.lang.foreign.* API 查找一个叫做 printf 的符号(基本上就是 System.out.print()),分配内存(有点像 malloc)缓冲区,然后将该缓冲区传递给 printf 函数。

package com.example.demo;

import org.springframework.stereotype.Component;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.util.Objects;

import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;

@Component
class ManualFfi implements LanguageDemonstrationRunner {

    // 这是包私有的,因为我们稍后会需要它
    static final FunctionDescriptor PRINTF_FUNCTION_DESCRIPTOR =
            FunctionDescriptor.of(JAVA_INT, ADDRESS);

    private final SymbolLookup symbolLookup;

    // SymbolLookup 是 Panama API,但我有一个我正在注入的实现
    ManualFfi(SymbolLookup symbolLookup) {
        this.symbolLookup = symbolLookup;
    }

    @Override
    public void run() throws Throwable {
        var symbolName = "printf";
        var nativeLinker = Linker.nativeLinker();
        var methodHandle = this.symbolLookup.find(symbolName)
            .map(symbolSegment -> nativeLinker.downcallHandle(symbolSegment, PRINTF_FUNCTION_DESCRIPTOR))
            .orElse(null);
        try (var arena = Arena.ofConfined()) {
            var cString = arena.allocateFrom("hello, Panama!");
            Objects.requireNonNull(methodHandle).invoke(cString);
        }
    }

}

这是我提出的 SymbolLookup 的定义。它是一种复合体,尝试一个 SymbolLookup,如果第一个失败,则尝试另一个。

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

运行这个,你会看到它打印出 hello, Panama!.

您可能想知道为什么我没有选择更有趣的例子。事实证明,在所有os中你既能理所当然地享有,在计算机上也能感知到自己做了些什么的东西几乎没有。IO 似乎是我能想到的所有东西,而且控制台 IO 更容易理解。

但 GraalVM 原生镜像咋样呢?它并不支持你可能想做的每件事。至少目前,它不在苹果芯片运行,只在 x86 芯片。我开发了这个例子,并设置了 GitHub 操作在 x86 Linux 环境中查看结果。对于我们这些不使用英特尔芯片的 Mac 开发者来说,这有点遗憾,但我们大多数人不是将产品部署到苹果设备上,我们是部署到 Linux 和 x86 上,所以这不是一个破坏协议的事情。

还有一些其他限制。如GraalVM 原生映像仅支持我们复合中的第一个 SymbolLookup, loaderLookup。如果那个不起作用,那么它们都将不起作用。

GraalVM 想要知道你在运行时会做的一些动态事情,包括外部函数调用。你需要提前告诉它。对于其他需要此类信息的大多数事情,如反射、序列化、资源加载等,你需要编写 .json 配置文件(或让 Spring 的 AOT 引擎为你编写)。这个特性是如此新,以至于你必须走下几个抽象层次并编写一个 GraalVM Feature 类。Feature 有回调方法,在 GraalVM 的本地编译生命周期中被调用。你将告诉 GraalVM 我们最终会在运行时调用的原生函数的签名,即形态。这是 Feature。只有一行价值。

package com.example.demo;

import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;

import static com.example.demo.ManualFfi.PRINTF_FUNCTION_DESCRIPTOR;

public class DemoFeature implements Feature {

    @Override
    public void duringSetup(DuringSetupAccess access) {
        // 这是唯一重要的一行。注意:我们正在分享
        // 我们稍早从 ManualFfi bean 中的 PRINTF_FUNCTION_DESCRIPTOR。
        RuntimeForeignAccess.registerForDowncall(PRINTF_FUNCTION_DESCRIPTOR);
    }

}

然后我们需要连接所有的特性,通过将 --features 属性传递给 GraalVM 原生图像 Maven 插件配置来告知 GraalVM。我们还需要解锁外部 API 支持和解锁实验性事物。(我不知道为什么在 GraalVM 原生镜像中这是实验性的,而在 Java 22 本身中它不再是实验性的)。还需要告诉 GraalVM 允许所有未命名类型的原生访问。所以,总的来说,这是最终的 Maven 插件配置。

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>0.10.1</version>
    <configuration>
        <buildArgs>
            <buildArg>--features=com.example.demo.DemoFeature</buildArg>
            <buildArg>--enable-native-access=ALL-UNNAMED</buildArg>
            <buildArg>-H:+ForeignAPISupport</buildArg>
            <buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
            <buildArg>--enable-preview</buildArg>
        </buildArgs>
    </configuration>
</plugin>

这是一个了不起的结果。我将这个示例中的代码编译成一个在 GitHub Actions 运行中的 GraalVM 原生图像然后执行它。应用程式,我提醒您 - 具有 Spring JDBC 支持、完整和嵌入式 SQL 99 兼容的 Java 数据库叫做 H2,以及类路径上的所有内容 - 在 0.031 秒(31 毫秒,或 31 千分之一秒)内执行,占用数十兆字节的 RAM,并从 GraalVM 原生镜像调用原生 C 代码!

我真的很高兴,大家。我已经等这一天很久了。

但这确实感觉有点低级。归根到底,你在使用一个 Java API 来以编程方式创建和维护原生代码中的结构。这有点像使用 JDBC 中的 SQL。JDBC 允许你在 Java 中操纵 SQL 数据库记录,但你不是在 Java 中编写 SQL 并在 Java 中编译它并在 SQL 中执行它。存在一个抽象增量;你将字符串发送到 SQL 引擎,然后以 ResultSet 对象的形式获取回来的记录。Panama 中的低级 API 也是如此。它起作用,但你没有调用原生代码,你正在查找符号和操纵内存。

所以,他们发布了一个与之分离但相关的工具叫做 jextract。你可以指向一个 C 头文件,如 stdio.hprintf 函数定义在其中,它会生成模仿底层 C 代码调用签名的 Java 代码。我没有在这个示例中使用它,因为生成的 Java 代码最终与底层平台绑定。我指它去 stdio.h 并获得了很多 macOS 特定的定义。我可以隐藏所有这些在运行时检查操作系统的后面,然后动态加载特定的实现,但是,嗯,这篇博客已经太长了。如果你想看怎么运行 jextract,这是我用的可以在 macOS 和 Linux 上工作的 bash 脚本。你的里程可能会有所不同。

#!/usr/bin/env bash
LINUX=https://download.java.net/java/early_access/jextract/22/3/openjdk-22-jextract+3-13_linux-x64_bin.tar.gz
MACOS=https://download.java.net/java/early_access/jextract/22/3/openjdk-22-jextract+3-13_macos-x64_bin.tar.gz

OS=$(uname)

DL=""
STDIO=""

if [ "$OS" = "Darwin" ]; then
    DL="$MACOS"
    STDIO=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h
elif [ "$OS" = "Linux" ]; then
    DL=$LINUX
    STDIO=/usr/include/stdio.h
else
    echo "Are you running on Windows? This might work inside the Windows Subsystem for Linux, but I haven't tried it yet.."
fi

LOCAL_TGZ=tmp/jextract.tgz
REMOTE_TGZ=$DL
JEXTRACT_HOME=jextract-22

mkdir -p "$(

 dirname  $LOCAL_TGZ )"
wget -O $LOCAL_TGZ $REMOTE_TGZ
tar -zxf "$LOCAL_TGZ" -C .
export PATH=$PATH:$JEXTRACT_HOME/bin

jextract  --output src/main/java  -t com.example.stdio $STDIO

想想看,我们拥有简单的外部函数互操作性、提供惊人扩展性的虚拟线程,以及静态链接的、快如闪电、内存高效、自足的 GraalVM 原生图像二进制文件。再次告诉我,为何你要开始一个新的 Go 项目?

标签:Java,SpringBoot,22,java,3.3,org,import,GraalVM
From: https://www.cnblogs.com/JavaEdge/p/18091934

相关文章

  • 基于springboot的班级综合测评管理系统的设计与实现
    目录背景技术简介系统简介界面预览背景随着电子技术的广泛渗透和迅猛发展,网络化的管理平台得到了大规模的应用。众多的公共机构和商业组织都在积极推进管理流程的电子化转型,班级的综合评价管理系统亦是如此,从传统的手工操作转变为更加电子化、信息化和系统化的管理模式......
  • 一文弄懂Javascript中的深拷贝和浅拷贝
    目录一文弄懂Javascript深拷贝与浅拷贝1Javascript数据存储规则2浅拷贝3部分深拷贝3.1Object.assign3.2slice()3.3concat()3.4拓展运算符4完全深拷贝4.1_.cloneDeep()4.2结构化拷贝4.3json.stringify()4.4循环递归4.5jQuery.extend()5总结一文弄懂J......
  • 按功能划分的常用Java库
    日志打印使用slf4j作为日志门面API,常用的日志实现库为log4j和logback。<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version></dependency><!--使用logback作为底层日志实现框架,需要在......
  • 基于SpringBoot+Vue医疗管理系统设计和实现(源码+LW+部署讲解)
    博主介绍:✌全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌主要内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs......
  • 基于SpringBoot+Vue新闻管理系统设计和实现(源码+LW+部署讲解)
    博主介绍:✌全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌主要内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs......
  • Java中的继承、重载与重写:概念、区别与实例演示(day7)
    Java的继承、重载以及重写java的继承:java的重载java的重写:在这个例子中:Animal类是父类,它有一个makeSound方法和两个重载的eat方法。Dog类是子类,它继承了Animal类,并重写了makeSound方法,使其具有不同的行为。此外,Dog类还重载了eat方法,但这次重载的方法......
  • 线路查询||基于Java+Spring Boot+MySQL的公交线路查询系统设计与实现(源码+数据库+文
    目录一、前言二、技术介绍三、系统实现四、论文参考五、核心代码六、其他案例七、源码获取作者介绍:✌️大厂全栈码农|毕设实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。✌️作者博客:曾几何时​​​​​​​......
  • 基于ssm药品管理系统(java毕业设计源码)
    收藏关注不迷路1.项目介绍本药品管理系统以Mysql数据库作为数据存储的核心,并依托SSM框架进行开发,确保系统架构的稳固与高效。Tomcat服务器作为运行环境,为系统提供了稳定可靠的运行平台。在开发过程中,我们选择了ECLIPSE作为开发平台,利用其强大的功能和灵活的扩展性,极大地......
  • 卡码java基础课 | 17.判断集合成员
    学习内容:set的概念和特点set的基本操作,比如创建、插入、删除、查找HashSet的常用方法Set集合的遍历迭代器重点归纳:set本质上是一种集合接口,类似于数学中的集合,常用于存储一组元素,用来判断一种元素是否在集合中。Set接口的常见实现类包括HashSet、TreeSet和LinkedHashSet......
  • 深入剖析Java中的“==”与“equals”:不同之处及实践
    引言比较在任何编程语言中都是基本操作,Java提供了“==”运算符和“equals()”方法进行比较,它们在比较对象时有着本质的区别。“==”:引用数据类型与基本数据类型比较对于基本数据类型,如int、double等“==”比较的是值本身。对于引用数据类型,如String、Arrays等,它比较的......