网易首页 > 网易号 > 正文 申请入驻

SpringBoot3.0 SpringAOT 与 RuntimeHints 新特性:0.1 秒启动,超低内存!

0
分享至

钓友宝 (微信小程序):一款专门为 钓友 开发的 免费的 分享钓点地图与实时天气的软件,地图中标记了所有野钓、钓场、公共水域等的精确位置,支持导航功能。

一. 前置知识

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、项目准备


     

 org.springframework.boot groupId>     

 spring-boot-starter-parent artifactId>     

 3.2.5 version>     
parent> 

     

         

 org.springframework.boot groupId>         

 spring-boot-starter-web artifactId>      dependency>     

         

 org.springframework.boot groupId>         

 spring-boot-starter-test artifactId>         

 test scope>      dependency> dependencies> 

     

         

             

 org.graalvm.buildtools groupId>             

 native-maven-plugin artifactId>          plugin>         

             

 org.springframework.boot groupId>             

 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-image

docker 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 {

        @Override
        public 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 {

        @Override
        public 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() {
     @Override
     public 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{

    @Reflective
    public MyService() {
    }

    @Reflective
    public String test(){
        return "hello";
    }
}

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

四. AOT的原理

1、插件执行逻辑

我们执行mvn -Pnative native:compile时,实际上执行的是插件native-maven-plugin的逻辑。

会先编译我们自己的java代码,然后执行ProcessAotMojo.executeAot()方法(会生成一些Java文件并编译成class文件,以及GraalVM的配置文件),然后才执行利用GraalVM打包出二进制可执行文件。

maven插件在编译的时候,就会调用到executeAot()这个方法,这个方法会:

  • 先执行org.springframework.boot.SpringApplicationAotProcessor的main方法

  • 从而执行SpringApplicationAotProcessorprocess()

  • 从而执行ContextAotProcessordoProcess(),从而会生成一些Java类并放在spring-aot/main/sources目录下,详情看后文

  • 然后把生成在spring-aot/main/sources目录下的Java类进行编译,并把对应class文件放在项目的编译目录下target/classes

  • 然后把spring-aot/main/resources目录下的graalvm配置文件复制到target/classes

  • 然后把spring-aot/main/classes目录下生成的class文件复制到target/classes

2、AOT生成的类

AOT会提前启动Spring容器,并执行Bean扫描的过程,将这个过程产生的所有BeanDefinition提前生成为Java文件,如下那样,所以,可以在编译期间通过插件生成BeanDefinition,而不是在启动期间进行扫描。

我们看一下SpringApplication.run方法:

可以选择以AOT的方式运行,可以跳过Bean扫描的过程。

3、使用

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

4、原理图


插播一下 Java精选面试题 (微信小程序):5000+道面试题和选择题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计、大厂真题等,在线随时刷题!

作者:秃了也弱了 https://juejin.cn/post/7361255930648248320

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注

文章有帮助的话,点在看,转发吧!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
墨西哥告别世界杯:比输球更痛的,是生活的烦恼

墨西哥告别世界杯:比输球更痛的,是生活的烦恼

体育产业生态圈
2026-07-06 22:42:34
哈兰德父亲是他,曾参加美国世界杯,如今父凭子贵,儿子给他长脸

哈兰德父亲是他,曾参加美国世界杯,如今父凭子贵,儿子给他长脸

一窥究竟
2026-07-07 01:42:14
库尔图瓦:看到巴洛贡解禁只觉得好笑;塞内加尔比美国更强

库尔图瓦:看到巴洛贡解禁只觉得好笑;塞内加尔比美国更强

懂球帝
2026-07-07 11:04:30
玄学提醒:不要让任何人去你家,记住,是任何人

玄学提醒:不要让任何人去你家,记住,是任何人

背包旅行
2026-07-04 16:24:16
全美吵翻!62岁退伍老兵超市停车场被当众枪杀,开枪女子竟然“当场被释放”?

全美吵翻!62岁退伍老兵超市停车场被当众枪杀,开枪女子竟然“当场被释放”?

北美省钱快报
2026-07-07 04:10:14
欧洲出土2900年青铜古剑:神奇一幕再现,欧洲考古果然可疑

欧洲出土2900年青铜古剑:神奇一幕再现,欧洲考古果然可疑

百家杂评
2026-07-05 07:32:44
冉莹颖曝7年败光2亿,婚姻不堪与邹市明走到尽头

冉莹颖曝7年败光2亿,婚姻不堪与邹市明走到尽头

笑饮孤鸿非
2026-07-07 01:07:47
蒋方舟全面硬刚清华教授!网友抵制,再扒猛料,论文造假只是一角

蒋方舟全面硬刚清华教授!网友抵制,再扒猛料,论文造假只是一角

大鱼简科
2026-07-04 16:39:14
伊朗最高领袖果然聪明:不听俄罗斯的馊主意,向中国做永久保证!

伊朗最高领袖果然聪明:不听俄罗斯的馊主意,向中国做永久保证!

观察者小海风
2026-07-06 13:41:38
广西在呼救,“外面一片汪洋”

广西在呼救,“外面一片汪洋”

南风窗
2026-07-07 10:01:27
麻雀的寿命很短,为何却很少见到它们的尸体?麻雀死后都去哪了?

麻雀的寿命很短,为何却很少见到它们的尸体?麻雀死后都去哪了?

千秋文化
2026-07-06 18:52:17
余承东:CPU我们有备胎版本,如果强迫不让使用,我们会使用自己的,甚至会有更好的表现!

余承东:CPU我们有备胎版本,如果强迫不让使用,我们会使用自己的,甚至会有更好的表现!

大白聊IT
2026-07-06 18:22:13
为何天安门不悬挂毛主席的照片,反倒要挂主席的画像?

为何天安门不悬挂毛主席的照片,反倒要挂主席的画像?

错过美好
2026-07-07 06:14:36
葡萄牙出局!名宿开炮:只怪毫无骨气的主教练,他向C罗卑躬屈膝

葡萄牙出局!名宿开炮:只怪毫无骨气的主教练,他向C罗卑躬屈膝

奥拜尔
2026-07-07 05:28:59
斯卡洛尼:由于天气问题和航班延误,我们只能把恢复放在首位

斯卡洛尼:由于天气问题和航班延误,我们只能把恢复放在首位

懂球帝
2026-07-07 09:29:18
六十年隐忍,83岁傅冬菊临终忏悔:我所有的荣光,都是父亲换来的

六十年隐忍,83岁傅冬菊临终忏悔:我所有的荣光,都是父亲换来的

人生录
2026-06-29 15:47:08
比利时队官号嘲讽美国,库尔图瓦:我只想笑

比利时队官号嘲讽美国,库尔图瓦:我只想笑

体坛周报
2026-07-07 11:40:20
司晓迪再爆猛料!晒与鹿晗亲密照,自曝“他的床我都睡了几百次”

司晓迪再爆猛料!晒与鹿晗亲密照,自曝“他的床我都睡了几百次”

叶公子
2026-07-06 20:42:36
世界杯最新对阵图:四分之一决赛挪威对阵英格兰

世界杯最新对阵图:四分之一决赛挪威对阵英格兰

新华社
2026-07-06 17:20:00
心理学有个残忍发现:让别人对你产生敬畏感的,不是你的强势,不是你的反击,而是你骨子里的这2种“人性破绽”利用术

心理学有个残忍发现:让别人对你产生敬畏感的,不是你的强势,不是你的反击,而是你骨子里的这2种“人性破绽”利用术

心理观察局
2026-07-07 06:15:06
2026-07-07 14:00:49
Java精选
Java精选
一场永远也演不完的戏
1796文章数 3859关注度
往期回顾 全部

科技要闻

Claude说话前 脑子里可能先亮起了这些概念

头条要闻

媒体:比利时赛前内讧频发 若无"红牌"事件未必能赢

头条要闻

媒体:比利时赛前内讧频发 若无"红牌"事件未必能赢

体育要闻

世界杯最强17岁,贝林厄姆主动和他交换球衣

娱乐要闻

司晓迪再爆鹿晗,称两人已睡过

财经要闻

百万粉丝主播的减肥“生意”

汽车要闻

试驾全新坦克300 Hi4-Z/激光雷达/全场景NOA

态度原创

手机
本地
艺术
公开课
军事航空

手机要闻

行业人士预测iPhone 18 Pro Max销量,他用了“恐怖”二字

本地新闻

国内足球之旅?这座小城给你高分答案

艺术要闻

188米!何镜堂院士出手,前海新地标不走寻常路!

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

俄乌冲突再升级 康斯坦丁诺夫卡成争夺焦点

无障碍浏览 进入关怀版