Spring6和SpringBoot3的新特性-你不得不了解的AOT原来这么简单

news2025/2/24 10:34:58

Spring6.0新特性

一、Spring的发展历史

image.png

二、AOT

  AOT是Spring6.0提供的一个新特性,Ahead of Time 提前编译。

image.png

1.AOT概述

1.1 JIT和AOT的关系

1.1.1 JIT

   JIT(Just-in-time) 动态编译,即时编译,也就是边运行边编译,也就是在程序运行时,动态生成代码,启动比较慢,编译时需要占用运行时的资源。

1.1.2 AOT

  AOT,Ahead Of Time 指的是运行前编译,预先编译,AOT 编译能直接将源代码转化为机器码,内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化,AOT 缺点就是在程序运行前编译会使程序安装的时间增加。

简单来讲:JIT即时编译的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

image.png

三、GraalVM

GraalVM即支持AOT也支持JIT。支持多种开发语言。

  Spring6 支持的 AOT 技术,这个 GraalVM 就是底层的支持,Spring 也对 GraalVM 本机映像提供了一流的支持。GraalVM 是一种高性能 JDK,旨在加速用 Java 和其他 JVM 语言编写的应用程序的执行,同时还为 JavaScript、Python 和许多其他流行语言提供运行时。 GraalVM 提供两种运行 Java 应用程序的方法:在 HotSpot JVM 上使用 Graal 即时 (JIT) 编译器或作为提前 (AOT) 编译的本机可执行文件。 GraalVM 的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外语调用成本。GraalVM 向 HotSpot Java 虚拟机添加了一个用 Java 编写的高级即时 (JIT) 优化编译器。

GraalVM 具有以下特性:

(1)一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源

(2)AOT 本机图像编译提前将 Java 应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能

(3)Polyglot 编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销

(4)高级工具在 Java 和多种语言中调试、监视、分析和优化资源消耗

1.GraalVM安装

1.1 下载GraalVM

下载地址:https://www.graalvm.org/downloads/image.png

下载社区版本即可,点击进入选择相关的版本:https://github.com/graalvm/graalvm-ce-builds/releases

image.png

下载好后解压缩出来

image.png

1.2 配置环境变量

添加:GRAALVM_HOME

编辑用户变量

image.png

把JAVA_HOME修改为graalvm的位置

image.png

检查是否配置成功

image.png

1.3 安装native-image插件

使用命令 gu install native-image 下载安装插件,因为社区版默认不提供支持。需要手动下载

image.png

image.png

1.4 Native Image

  Native image(本地镜像)是一种在Java平台上构建本地应用程序的技术。它将Java应用程序编译成本地机器代码,以便在不需要Java虚拟机(JVM)的情况下运行。这使得应用程序可以更快地启动,更高效地执行,并且占用更少的内存。

  Native image使用GraalVM编译器技术,可以将Java应用程序转换为本地可执行文件,支持Windows、Linux和MacOS等多个操作系统平台。此外,Native image还可以将Java应用程序打包成单个可执行文件,从而方便部署和分发。

  使用Native image,开发人员可以将Java应用程序作为本地应用程序来构建和部署,从而获得更好的性能和更好的用户体验。

2.安装C++的编译环境

2.1 下载Visual Studio

https://visualstudio.microsoft.com/zh-hans/downloads/

image.png

同样我们下载社区版本即可

2.2 安装Visual Studio

下载后双击直接安装即可

image.png

等待在线下载

image.png

image.png

image.png

注意安装选项,然后继续等待

image.png

创建一个普通Hello.java文件

public class Hello{

	public static void main(String[] args){
		System.out.println("Hello World ...");
	}
}

然后通过 javac Hello.java 编译

image.png

通过native-image Hello 执行

image.png

通过 native-image 生成了 Hello.exe 文件,我们就可以直接生成了。

image.png

四、SpringBoot实战

  我们同样可以在SpringBoot项目中通过AOT来提前编译我们的项目,新建一个Maven项目。然后添加相关的依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.2</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

同时我们还需要添加相关的SpringBoot插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

然后我们编写一点简单的代码测试即可

image.png

然后我们打开 x64 Native Tools Command Prompt for VS 2019 。然后我们需要切换到工程目录下

image.png

然后执行 mvn -Pnative native:compile 进行编译就可以了,编译成功就会在target目录下生成 EXE 文件。后续执行该文件就可以了

image.png

image.png

编译成功

image.png

然后我们双击执行exe文件即可。你会发现速度会快很多

image.png

五、RuntimeHints

  与常规 JVM 运行时相比,将应用程序作为本机映像运行需要额外的信息。例如,GraalVM 需要提前知道组件是否使用反射。同样,除非明确指定,否则类路径资源不会在本机映像中提供。因此,如果应用程序需要加载资源,则必须从相应的 GraalVM 原生图像配置文件中引用它。

APIRuntimeHints在运行时收集反射、资源加载、序列化和 JDK 代理的需求。

1.案例分析

声明个普通的实体类型

public class UserEntity {
    public String hello(){
        return "hello ...";
    }
}

然后我们在控制器中通过反射来操作处理

    @GetMapping("/hello")
    public String hello(){
        String res = "hello";
        try {
            Method hello = UserEntity.class.getMethod("hello");
            res =  (String)hello.invoke(UserEntity.class.newInstance(),null);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

然后通过命令编译为 exe 文件

image.png

运行exe文件后。我们通过浏览器发起请求

image.png

在HelloController中。我们通过反射的方式使用到了UserEntity的无参构造方法。如果不做任何处理。那么打成二进制可执行文件后是执行不了的。上面是具体的报错信息。针对这种情况。我们可以通过 Runtime Hints 机制来处理。

2. RuntimeHintsRegistrar

官网提供的解决方案。我们自定义一个RuntimeHintsRegistrar接口的实现类,然后把该实现类注入到Spring中

image.png

我们自己的实现

@RestController
@ImportRuntimeHints(HelloController.UserEntityRuntimeHints.class)
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        String res = "hello";
        try {
            Method hello = UserEntity.class.getMethod("hello");
            res =  (String)hello.invoke(UserEntity.class.newInstance(),null);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    static class UserEntityRuntimeHints implements RuntimeHintsRegistrar{

        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            try {
                hints.reflection().registerConstructor(UserEntity.class.getConstructor(), ExecutableMode.INVOKE);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

六、SpringBoot的核心代码

SpringApplicationAotProcessor

  我们执行 mvn -Pnative native:compile时会执行GraalVM中的相关指令。最终会调用SpringApplicationAotProcessor中的main 方法来完成相关提前编译操作。

	public static void main(String[] args) throws Exception {
		int requiredArgs = 6; // 调用main方法接收的有6个参数
		Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + SpringApplicationAotProcessor.class.getName()
				+ " <applicationName> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId> <originalArgs...>");
		// 获取SpringBoot项目的入口class
		Class<?> application = Class.forName(args[0]);
		// 通过传递过来的参数完成相关生成目录的配置
		Settings settings = Settings.builder().sourceOutput(Paths.get(args[1])).resourceOutput(Paths.get(args[2]))
				.classOutput(Paths.get(args[3])).groupId((StringUtils.hasText(args[4])) ? args[4] : "unspecified")
				.artifactId(args[5]).build();
		String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length)
				: new String[0];
	  // 执行 process 方法
		new SpringApplicationAotProcessor(application, settings, applicationArgs).process();
	}

进入到process 方法中

	public final T process() {
		try {
			// 设置状态
			System.setProperty(AOT_PROCESSING, "true");
			return doProcess(); // 处理的核心方法
		}
		finally {
			System.clearProperty(AOT_PROCESSING);
		}
	}

进入到 doProcess() 方法中

 	@Override
	protected ClassName doProcess() {
		deleteExistingOutput(); // 删除已经存在的目录
		// 启动SpringBoot服务  但是不会做扫描bean
		GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());
		return performAotProcessing(applicationContext);
	}

prepareApplicationContext方法

	@Override
	protected GenericApplicationContext prepareApplicationContext(Class<?> application) {
		return new AotProcessorHook(application).run(() -> {
			Method mainMethod = application.getMethod("main", String[].class);
			return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs });
		});
	}

此次会执行启动类中的main方法来启动SpringBoot,

image.png

在启动中创建Spring上下文对象时会做如下的处理

	private ConfigurableApplicationContext createContext() {
		if (!AotDetector.useGeneratedArtifacts()) {
			return new AnnotationConfigServletWebServerApplicationContext();
		}
		return new ServletWebServerApplicationContext();
	}

  如果没有使用AOT,那么就会创建AnnotationConfigServletWebServerApplicationContext,它里面会添加ConfigurationClassPostProcessor,从而会解析配置类,从而会扫描,而如果使用了AOT,则会创建  ServletWebServerApplicationContext,它就是一个空容器,它里面没有ConfigurationClassPostProcessor,所以后续不会触发扫描了

再回到performAotProcessing方法中

	protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {
		FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
		DefaultGenerationContext generationContext = new DefaultGenerationContext(
				createClassNameGenerator(), generatedFiles);
		ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
		// 进行相关的扫描操作
		ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext);
		//如果有反射的注册信息。这里会完成相关信息的生成到reflect-config.json对应的RuntimeHints中去
		registerEntryPointHint(generationContext, generatedInitializerClassName);
		// 生成source目录下的Java文件
		generationContext.writeGeneratedContent();
		//  将RuntimeHints中的内容写入resource目录下的Graalvm的各个配置文件中
		writeHints(generationContext.getRuntimeHints());
		writeNativeImageProperties(getDefaultNativeImageArguments(getApplicationClass().getName()));
		return generatedInitializerClassName;
	}

processAheadOfTime中的逻辑

	public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
			GenerationContext generationContext) {
		return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
			// 扫描处理
			applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
		// 获取bean工厂对象
		DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
			ApplicationContextInitializationCodeGenerator codeGenerator =
					new ApplicationContextInitializationCodeGenerator(generationContext);
			new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext, codeGenerator);
			return codeGenerator.getGeneratedClass().getName();
		});
	}

进入到refreshForAotProcessing方法中

	public void refreshForAotProcessing(RuntimeHints runtimeHints) {
		if (logger.isDebugEnabled()) {
			logger.debug("Preparing bean factory for AOT processing");
		}
		prepareRefresh();
		obtainFreshBeanFactory(); // 获取工厂对象。并完成扫描操作
		prepareBeanFactory(this.beanFactory);
		postProcessBeanFactory(this.beanFactory);
		invokeBeanFactoryPostProcessors(this.beanFactory); // 之后工厂的后置处理器
		this.beanFactory.freezeConfiguration();
		PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
		preDetermineBeanTypes(runtimeHints);
	}

BeanFactoryInitializationAotContributions方法的逻辑:会读取aot.properties文件的加载器以及BeanFactory封装成为一个Loader对象,然后传入

	BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory) {
		this(beanFactory, AotServices.factoriesAndBeans(beanFactory));
	}

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/541943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LangChain与大型语言模型(LLMs)应用基础教程:角色定义

如果您还没有看过我之前写的两篇博客&#xff0c;请先看一下&#xff0c;这样有助于对本文的理解&#xff1a; LangChain与大型语言模型(LLMs)应用基础教程:Prompt模板 LangChain与大型语言模型(LLMs)应用基础教程:信息抽取 LangChain是大型语言模型(LLM)的应用框架,LangCha…

RK3568平台开发系列讲解(LCD篇)快速了解RK LCD的使用

🚀返回专栏总目录 文章目录 一、内核Config配置二、MIPI配置2.1 引脚配置2.2 背光配置2.3 显示时序配置2.3.1 Power on/off sequence2.3.2 Display-Timings三、EDP配置3.1 引脚配置3.2 EDP背光配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇章带大家快速了…

数据结构_查找

目录 1. 查找的基本概念 2. 顺序查找和折半查找 2.1 顺序查找 2.1.1 一般线性表的顺序查找 2.1.2 有序表的顺序查找 2.2 折半查找 2.3 分块查找 2.4 相关练习 3. 树型查找 3.1 二叉排序树 3.1.1 二叉排序树的定义 3.1.2 二叉排序树的查找 3.1.3 二叉排序树…

想要一个本地部署的海洋实景三维展示系统吗?

最近几年实景三维非常火&#xff0c;很多人包括博主都想将自己平时干的海洋测绘项目进行实景三维化&#xff0c;这样做的好处就是无论是管理数据还是成果展示都非常方便。我们可能会使用谷歌地图、奥维地图、图新地球等地图服务软件&#xff0c;它们也提供了一些测量、画图功和…

使用Hexo在Github上搭建个人博客

使用Hexo在Github上搭建个人博客 1. 安装Node和git2. 安装Hexo3. Git与Github的准备工作4. 将Hexo部署到Github5. 开始写作 1. 安装Node和git 在Mac上安装Node.js可以使用Homebrew&#xff0c;使用以下命令安装&#xff1a; brew install node使用以下命令安装Git&#xff1a; …

解决 Uncaught TypeError: SpriteCanvasMaterial is not a constructor.

文章目录 前言一、替代语法总结 前言 上周买了本《Three.js开发指南》, 第三版, 里面的语法不太跟趟, 有点旧, 倒也不能全怪作者, three迭代的确很快. 一、替代语法 这几天没事做, 加上前面本来就接触过Three, 很快进展到了第六章. 在推进 利用Canvas贴图给精灵(Sprite)增加样…

研发工程师玩转Kubernetes——启用microk8s的监控面板(dashboard)

安装插件 microk8s enable dashboard 查看dashboard 地址 由于dashboard是在kube-system的namespace中&#xff0c;我们可以使用下面指令查看它服务的地址。 microk8s kubectl get service -n kube-system kubernetes-dashboard 可以得到地址是https://10.152.183.167。 登…

Android Jetpack-Databinding基本使用

文章目录 让你的项目支持Databinding基本使用布局和绑定表达式常用运算符判空null获取对象属性避免空指针异常其他控件引用资源引用 事件处理import,variables,and includesimportVariablesIncludes 数据更新->UI更新监听字段变化更新监听对象变化更新 UI更新->数据更新绑…

MPLS隧道——隧道迭代与MPLS高可靠性

目录 MPLS隧道迭代 什么情况下需要隧道迭代 解决方法 MPLS高可靠性 VPN FRR VPN GR MPLS隧道迭代 什么情况下需要隧道迭代 解决方法 两种解决方法 将IBGP邻居更改为Vpnv4的IBGP邻居&#xff08;为目标网段产生私网标签&#xff0c;然后此带标签的BGP路由直接进入标签隧道…

作为程序员的你,常用的工具软件有哪些?

不会还有程序员没用过Chatgpt吧&#xff1f; 我现在的工作日常&#xff1a;把需求提给Chatgpt&#xff0c;代码出来后再自行润色一下代码&#xff0c;然后到工业环境里跑一下&#xff0c;没问题就可以提交了。一来一回效率提高了好几倍&#xff0c;摸鱼的时间都变多了…… 除了…

自学黑客(网络安全)必学内容

随着时代的发展&#xff0c;经济、社会、生产、生活越来越依赖网络。而随着万物互联的物联网技术的兴起&#xff0c;线上线下已经打通&#xff0c;虚拟世界和现实世界的边界正变得模糊。这使得来自网络空间的攻击能够穿透虚拟世界的边界&#xff0c;直接影响现实世界的安全。 …

环环相扣,循环不止:深入解析循环队列

本盘博客会讲解力扣“622. 设计循环队列”的解题思路&#xff0c;这是题目链接。 先来审下题&#xff1a; 以下是示例&#xff1a; 以下是提示&#xff1a; 如何设计一个循环队列呢&#xff1f;这里我用数组来实现。结构的定义如下&#xff1a; typedef struct {int* a; …

又一神器开源!无需服务器支持!打通手机,浏览器的Web LLM!

大家好&#xff0c;我是千与千寻&#xff0c;大家可以叫我“千寻哥”&#xff0c;之前和大家分享了两篇关于ChatGPT的技术文章&#xff1a; 1.chatgpt 2.chatgpt ChatGPT毫无疑问是现在最大的风口&#xff0c;各个行业都在集成ChatGPT的API接口以及各类的应用插件&#xff0…

本地部署 GPT Academic

本地部署 GPT Academic GPT Academic 项目概述Github 地址部署 GPT Academic配置 GPT Academic 参数启动 GPT Academic访问 GPT AcademicNew Bing Cookie 的获取方法 GPT Academic 项目概述 GPT 学术优化 (GPT Academic)&#xff0c;为GPT/GLM提供图形交互界面&#xff0c;特别…

SSM整合(单元测试、结果封装、异常处理)

文章目录 1&#xff0c;SSM整合1.1 流程分析1.2 整合配置步骤1&#xff1a;创建Maven的web项目步骤2:添加依赖步骤3:创建项目包结构步骤4:创建SpringConfig配置类步骤5:创建JdbcConfig配置类步骤6:创建MybatisConfig配置类步骤7:创建jdbc.properties步骤8:创建SpringMVC配置类步…

【Leetcode刷题】算法:最长公共前缀

文章目录 一、题目描述二、解题思路2.1 解法12.2 解法22.3 解法32.4 解法4 三、结果提交 一、题目描述 二、解题思路 2.1 解法1 class Solution:def longestCommonPrefix(self, strs: List[str]) -> str:if not strs: # 如果字符串数组为空&#xff0c;则返回空字符串ret…

MIL-STD-1553B总线系统搭建指导

MIL-STD-1553B总线系统搭建指导 1.1553B总线协议 1.11553B总线介绍 MIL-STD-1553B&#xff08;GJB 289A&#xff09;是一种应用于机载电子设备间通信的共享式总线通信协议&#xff0c;以总线式拓扑结构连接最多31个终端设备互联&#xff0c;传输速率为1Mbps&#xff0c;在航…

任务跟踪器重要性探析:提升项目效率,实现管理优化

使用任务跟踪器完成项目的最显著好处之一是它们大大减少了开始新项目的初始阻力&#xff0c;尤其是当它们是大型、长期和复杂的项目时。任务跟踪器可用于将这些艰巨的项目分解为更小的、相互依赖的任务&#xff0c;这些任务有助于激发动力和行动以实现最终目标。使用项目任务跟…

Python: 让单元测试输出像GoogleTest一样

文章目录 1. 目的2. 原版 unittest 的输出3. 仿 GoogleTest 的输出效果4. 实现原理浅析传入 testRunner 参数testRunner 参数应该满足的条件颜色高亮&#xff1a; ASCII 转义字符的使用测试用例输出文本内容的格式调整&#xff1a;仿googletest 5. 完整实现代码6. 完整调用代码…

$‘\r‘: command not found syntax error near unexpected token `$‘do\r‘‘ 解决方案

问题描述 今天在执行代码时出现了这样的错误&#xff1a; bash xxx.sh xxx.sh: line 2: $\r: command not found xxx.sh: line 7: $\r: command not found xxx.sh: line 8: syntax error near unexpected token $do\r 经查阅&#xff0c;发现是.sh文件在windows下编辑&#xf…