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

本文主要是介绍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、原理图

在这里插入图片描述

参考资料

周瑜老师

这篇关于SpringBoot3.0新特性尝鲜,秒启动的快感!熟悉SpringAOT与RuntimeHints的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/934632

相关文章

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除