专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
目录
相关文章推荐
芋道源码  ·  李飞飞团队50美元训练出DeepSeek R1? ·  昨天  
Java编程精选  ·  为什么不建议用 equals 判断对象相等? ·  2 天前  
芋道源码  ·  MySQL数据实时同步到Elasticsea ... ·  3 天前  
芋道源码  ·  SpringBoot CORS ... ·  3 天前  
51好读  ›  专栏  ›  芋道源码

SpringBoot3.0新特性尝鲜,秒启动的快感!SpringAOT与RuntimeHints来了解一下?

芋道源码  · 公众号  · Java  · 2025-02-07 09:49

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、 商城 、支付、工作流、大屏报表、微信公众号、 ERP CRM AI 大模型 等等功能:

  • Boot 多模块架构:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服务架构:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 双版本

来源:juejin.cn/post/
7361255930648248320


一、前置知识

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扫描阶段提前到了编译器,而不是启动期间进行扫描,大大提高了启动速度。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、打包SpringBoot3.0

1、项目准备

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>3.2.5version>
    <relativePath/> 
parent>


<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
dependencies>



<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtoolsgroupId>
            <artifactId>native-maven-pluginartifactId>
        plugin>
        <plugin>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-maven-pluginartifactId>
        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、打包


> 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
> * 项目地址:
> * 视频教程:

# 注意需要使用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(methodNamenull);
            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.json proxy-config.json 中的内容,GraalVM就知道了。







请到「今天看啥」查看全文