SpringBoot3.0新特性尝鲜,秒启动的快感!熟悉SpringAOT与RuntimeHints

文章目录

一、前置知识

1、官网

Spring6.0新特性:https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-6.x
SpringBoot3.0:https://docs.spring.io/spring-boot/docs/current/reference/html/

2、安装GraalVM

下载地址:https://github.com/graalvm/graalvm-ce-builds/releases
按照jdk版本下载GraalVM。SpringBoot3.0必须要使用jdk17以上了。
在这里插入图片描述

安装过程与普通的jdk安装一样。

安装成功之后,使用java -version,可以看到VM是GraalVM了。
在这里插入图片描述

3、GraalVM的限制

GraalVM在编译成二进制可执行文件时,需要确定该应用到底用到了哪些类、哪些方法、哪些属性,从而把这些代码编译为机器指令(也就是exe文件)。但是我们一个应用中某些类可能是动态生成的,也就是应用运行后才生成的,为了解决这个问题,GraalVM提供了配置的方式,比如我们可以在编译时告诉GraalVM哪些方法会被反射调用,比如我们可以通过reflect-config.json来进行配置。

4、安装maven

略,注意配置阿里云加速。

5、背景

为了应对Serverless大环境,使Springboot项目快速启动,所以才会推出AOT与直接编译为字节码的功能。

因为Java程序运行,需要启动虚拟机,然后由虚拟机将class字节码文件编译为机器指令,所以启动过程比较慢。
而如果像C语言那样,直接编译为机器指令,会大大提高启动速度,但是会丢失Java反射、动态代理等功能(有解决方案-RuntimeHints)。
而且Springboot3.0-AOT更是将Bean扫描阶段提前到了编译器,而不是启动期间进行扫描,大大提高了启动速度。

二、打包SpringBoot3.0

1、项目准备

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.5</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></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></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class Controller {@GetMapping("/demo1")public String demo1() {return "hello world";}
}

2、打包

# 注意需要使用GraalVM环境,需要有C语言环境 ,最好使用linux系统
mvn -Pnative native:compile

打包过程会久一些。
在这里插入图片描述
会将可执行文件、jar包都打出来:
在这里插入图片描述
运行demo:仅仅几十毫秒就能启动!!!
在这里插入图片描述
使用jar包运行:很明显会慢很多!!
在这里插入图片描述

3、打包成docker

# 打包成docker
mvn -Pnative spring-boot:build-imagedocker run --rm -p 8080:8080 demo# 如果要传参数,可以通过-e
docker run --rm -p 8080:8080 -e methodName=test demo
# 不过代码中,得通过以下代码获取:
String methodName = System.getenv("methodName")
#也可以使用Environment获取,注入Environment 
environment.getProperty("methodName");

注意,打包的过程会下载java环境镜像,比较慢。
在这里插入图片描述
仅仅几十毫秒就可以启动一个简单的Springboot项目!

三、认识AOT

1、RuntimeHints

假设以下代码:

@Component
public class UserService {public String test(){String result = "";try {Method test = MyService.class.getMethod("test", null);result = (String) test.invoke(MyService.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 result;}
}

在MyService中,通过反射的方式使用到了MyService的无参构造方法(MyService.class.newInstance()),如果我们不做任何处理,那么打成二进制可执行文件后是运行不了的,可执行文件中是没有MyService的无参构造方法的,会报方法找不到的错误。

我们可以使用Spring提供的Runtime Hints机制来间接的配置reflect-config.json。

2、RuntimeHintsRegistrar

提供一个RuntimeHintsRegistrar接口的实现类,并导入到Spring容器中就可以了:

@Component
@ImportRuntimeHints(UserService.MyServiceRuntimeHints.class)
public class UserService {public String test(){String result = "";try {Method test = MyService.class.getMethod("test", null);result = (String) test.invoke(MyService.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 result;}static class MyServiceRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {try {hints.reflection().registerConstructor(MyService.class.getConstructor(), ExecutableMode.INVOKE);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}
}

3、@RegisterReflectionForBinding

// 该类的所有方法都会编译为机器码
@RegisterReflectionForBinding(MyService.class)
public String test(){String result = "";try {Method test = MyService.class.getMethod("test", null);result = (String) test.invoke(MyService.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 result;
}

4、@ImportRuntimeHints

注意
如果代码中的methodName是通过参数获取的,那么GraalVM在编译时就不能知道到底会使用到哪个方法,那么test方法也要利用RuntimeHints来进行配置。

@Component
@ImportRuntimeHints(MyService.MyServiceRuntimeHints.class)
public class UserService {public String test(){String methodName = System.getProperty("methodName");String result = "";try {Method test = MyService.class.getMethod(methodName, null);result = (String) test.invoke(MyService.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 result;}static class MyServiceRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {try {hints.reflection().registerConstructor(MyService.class.getConstructor(), ExecutableMode.INVOKE);hints.reflection().registerMethod(MyService.class.getMethod("test"), ExecutableMode.INVOKE);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}
}

5、使用JDK动态代理也需要配置

public String test() throws ClassNotFoundException {String className = System.getProperty("className");Class<?> aClass = Class.forName(className);Object o = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.getName();}});return o.toString();
}

也可以利用RuntimeHints来进行配置要代理的接口:

public void registerHints(RuntimeHints hints, ClassLoader classLoader) {hints.proxies().registerJdkProxy(UserInterface.class);
}

6、@Reflective

对于反射用到的地方,我们可以直接加一个@Reflective,前提是MyService得是一个Bean:

@Component
public class MyService{@Reflectivepublic MyService() {}@Reflectivepublic String test(){return "hello";}
}

以上Spring6提供的RuntimeHints机制,我们可以使用该机制更方便的告诉GraalVM我们额外用到了哪些类、接口、方法等信息,最终Spring会生成对应的reflect-config.json、proxy-config.json中的内容,GraalVM就知道了。

四、AOT的原理

1、插件执行逻辑

我们执行mvn -Pnative native:compile时,实际上执行的是插件native-maven-plugin的逻辑。
会先编译我们自己的java代码,然后执行ProcessAotMojo.executeAot()方法(会生成一些Java文件并编译成class文件,以及GraalVM的配置文件),然后才执行利用GraalVM打包出二进制可执行文件。

在这里插入图片描述
maven插件在编译的时候,就会调用到executeAot()这个方法,这个方法会:

  1. 先执行org.springframework.boot.SpringApplicationAotProcessor的main方法
  2. 从而执行SpringApplicationAotProcessor的process()
  3. 从而执行ContextAotProcessor的doProcess(),从而会生成一些Java类并放在spring-aot/main/sources目录下,详情看后文
  4. 然后把生成在spring-aot/main/sources目录下的Java类进行编译,并把对应class文件放在项目的编译目录下target/classes
  5. 然后把spring-aot/main/resources目录下的graalvm配置文件复制到target/classes
  6. 然后把spring-aot/main/classes目录下生成的class文件复制到target/classes

2、AOT生成的类

AOT会提前启动Spring容器,并执行Bean扫描的过程,将这个过程产生的所有BeanDefinition提前生成为Java文件,如下那样,所以,可以在编译期间通过插件生成BeanDefinition,而不是在启动期间进行扫描。
在这里插入图片描述
我们看一下SpringApplication.run方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以选择以AOT的方式运行,可以跳过Bean扫描的过程。

3、使用

一般配合GraalVM使用,如果单独使用的话,需要加上面那个参数开启AOT,并且通过插件进行打包。

4、原理图

在这里插入图片描述

参考资料

周瑜老师

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

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

相关文章

【禅道客户案例】专访鸿泉物联研发副总监徐小倩,感受上市公司研发项目管理“知与行”

杭州鸿泉物联网技术股份有限公司&#xff08;以下简称“鸿泉物联”、“公司”&#xff09;成立于2009年6月11日&#xff0c;2019年11月6日登陆上海证券交易所科创板&#xff08;股票代码&#xff1a;688288&#xff09;&#xff0c;注册资本10034.392万元&#xff0c;目前员工6…

电磁仿真--基本操作-CST-(3)

目录 1. 目的 2. 建模过程 2.1 创建工程 2.2 修改单位 2.3 创建线和圆柱 2.4 创建螺旋结构 2.5 创建另一个圆柱 2.6 设置频率、背景和边界 2.7 选择RLC求解器 2.8 设置端口 2.9 配置求解器 3. 仿真结果 4. 总结 1. 目的 本文将介绍一种较为复杂的建模方法&#x…

计算机网络物理层思维导图+大纲笔记

大纲笔记&#xff1a; 物理层的基本概念 解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是具体的传输媒体 主要任务 确定与传输媒体接口有关的一些特性 机械特性 电气特性 功能特性 规程特性信道上传送的信号 基带信号 来自信源的信号&#xff0c;直接表…

全彩屏负氧离子监测站的使用

TH-FZ5在繁忙的都市生活中&#xff0c;我们往往忽视了一个至关重要的问题——空气质量。随着工业化的进程加速&#xff0c;空气污染已成为影响人们健康的一大隐患。为了实时监测和了解身边的空气质量&#xff0c;全彩屏负氧离子监测站应运而生&#xff0c;成为了我们守护呼吸健…

Golang | Leetcode Golang题解之第50题Pow(x,n)

题目&#xff1a; 题解&#xff1a; func myPow(x float64, n int) float64 {if n > 0 {return quickMul(x, n)}return 1.0 / quickMul(x, -n) }func quickMul(x float64, n int) float64 {if n 0 {return 1}y : quickMul(x, n/2)if n%2 0 {return y * y}return y * y * …

Redis 安装及配置教程(Windows)【安装】

文章目录 一、简介一、 下载1. GitHub 下载2. 其它渠道 二、 安装1. ZIP2. MSI 软件 / 环境安装及配置目录 一、简介 Redis 官网地址&#xff1a;https://redis.io/   Redis 源码地址&#xff1a;https://github.com/redis/redis   Redis 官网安装地址&#xff08;无Windo…

读天才与算法:人脑与AI的数学思维笔记09_分形

1. 分形 1.1. 1904年&#xff0c;瑞典数学家科赫&#xff08;Helge von Koch&#xff09;首次发表了雪花图案的结构——科赫曲线&#xff08;又称雪花曲线&#xff09;&#xff0c;它被认为是一种数学怪胎&#xff0c;一种奇怪的人工构造 1.1.1. 但实际上并不是&#xff0c;自…

4- 24

day02 1.100个英语单词 2.vp div3 不过有点小悲惨&#xff0c;第一题正常的直接看出来答案。第二题其实是map模拟&#xff0c;一直没有读懂题目的意思&#xff0c;题目给的序列是打乱的。找出最小的&#xff0c;讲原来的序列补全&#xff0c;如果mp中没有这个数字&#xff0c;…

Android 系统充电动画

效果 Android获取电池充电状态是否为快充可参考. Android_source/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java private int lastBatteryStatus;private final BroadcastReceiver mBatteryChangedReceiver new BroadcastRece…

杰发科技AC7840——CAN通信简介(6)_监听模式

参考&#xff1a;http://t.csdnimg.cn/AFFPC 0. 简介 7840支持4种扩展模式&#xff0c;其中监听模式。 监听模式概念 作用: 这里写的用于诊断&#xff0c;实际上我还没有用到&#xff0c;不太理解为啥可以用作诊断。 我的理解是&#xff0c;在多个总线下&#xff0c;使用监听…

BUUCTF-MISC-10.LSB1

10.LSB1 题目&#xff1a;lsb隐写&#xff0c;stegsolve可以看到包含了一个PNG图片 使用stegsolve打开这个图片 由PNG文件头可以看出隐写内容为PNG文件&#xff0c;按save Bin键保存为PNG文件。 得到一张二维码图片&#xff0c;使用CQR扫一下

PostgreSQL中的临时表与永久表的区别,以及它们的最佳使用场景?

文章目录 临时表与永久表的区别临时表永久表区别总结 最佳使用场景临时表的使用场景永久表的使用场景 解决方案及示例代码临时表示例创建临时表插入数据查询数据 永久表示例创建永久表插入数据查询数据 总结 在PostgreSQL中&#xff0c;临时表和永久表都是用于存储数据的表结构…

Tensorflow小技巧01:检测本地Tensorflow的版本

前言&#xff1a; 以Pycharm为例&#xff0c;Windwos10系统&#xff0c;检测本地环境的Tensorflow的版本&#xff1a; 1 打开Pycharm窗口 2 在窗口中输入&#xff1a; pythonPython 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win…

智慧文旅:引领旅游产业智慧升级的创新模式

一、智慧文旅是什么&#xff1f; 智慧文旅是指以当地特色文化为核心&#xff0c;借助现代科技手段&#xff0c;实现旅游景区全面智慧升级的旅游模式。在智慧文旅中&#xff0c;新一代信息网络技术和装备得到充分运用&#xff0c;文化旅游基础设施得到新建和改善&#xff0c;特…

【唯美情侣爱情表白纪念HTML单页】

唯美情侣爱情表白纪念HTML单页 效果图部分代码领取代码下期更新预报 效果图 整图 背景图 部分代码 index.html <!DOCTYPE html> <html lang"en"><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"…

valgrind,memcheck的使用

一&#xff0c;valgrind介绍 ​ valgrind是一个开源的&#xff0c;检测内存泄漏的工具&#xff0c;通常在linux下使用&#xff0c;除此之外&#xff0c;他还能检测内存管理错误&#xff0c;线程bug等错误。粗浅的来讲&#xff0c;valgrind由两部分构成&#xff0c;一部分用来模…

爬虫学习笔记-数美验证

测试网址&#xff1a;智能验证码体验_图片验证码_数美科技数美科技智能验证码在线体验&#xff0c;智能识别风险用户级别&#xff0c;自行切换智能验证码难度及类型&#xff0c;提供滑动、拼图、点选、数字、动态等多种智能验证码服务&#xff0c;精准拦截机器行为。https://ww…

R语言详解二

一&#xff0c;列表详解 创建一个列表 > myList<-list(id2,name"张三",age20) > myList $id [1] 2$name [1] "张三"$age [1] 20 获取第一个元素 > myList[[2]] [1] "张三" 获取第一个子列表 > myList[2] $name [1] "张…

20240309web前端_第四次作业_完成随机点名程序

要求 一、结合抽奖案例完成随机点名程序&#xff0c;要求如下: 1.点击点名按钮&#xff0c;名字界面随机显示&#xff0c;按钮文字由点名变为停止 2.再次点击点名按钮&#xff0c;显示当前被点名学生姓名&#xff0c;按钮文字由停止变为点名 3.样式请参考css及html自由发挥完成…

解读宁波IATF16949认证:开启成功之门的钥匙️

&#x1f449;解读宁波IATF16949认证&#xff1a;&#x1f970;开启成功之门的钥匙&#x1f5dd;️ &#x1f432;在风起云涌的&#x1f4fa;商业浪潮中&#xff0c;&#x1f6b6;每一个追求卓越的&#x1f685;企业都渴望找到一把&#x1f511;开启成功之门的钥匙。&#x1f3…