Springboot 3.0之Spring Native初体验

Springboot 3.0之Spring Native初体验

Spring 3.0 中引入了一个新特性,即Spring 对Graalvm Image的支持。

Graalvm 官网

https://www.graalvm.org/native-image/

GraalVM编译器

Graalvm 是一个高效能,支持云原生的编译器。支持Java、JavaScript、Python、Ruby、R、WASM等多种语言。编译器的作用就是生成需要更少计算资源的更快、更精简的代码,拿Java 语言举例,Java 代码经过编译后生成class文件,启动Java程序的时候,需要通过JVM虚拟机将class文件加载到JVM内存中运行。现在使用Graalvm 生成Image镜像时,在编译Java代码时会使用 AOT(Ahead-Of-Time),即在编译时直接编译为本机二进制文件,这些文件可立即启动,无需预热即可提供最佳性能。编译完成的二进制文件不需要Java虚拟机即可运行。在不使用Graalvm 的镜像编译功能时,也可以使用Graalvm当作JDK来使用。

Graalvm 架构图[来自官网:https://www.graalvm.org/22.3/docs/introduction/]:

在这里插入图片描述

GraalVM为HotSpot Java虚拟机添加了一个高级的即时(JIT)优化编译器,Graalvm 的语言实现框架(Truffle) 可以在JVM上运行JavaScript、Ruby、Python和一些其他支持的流行语言。

Graalvm和JDK的区别:
  • Graalvm 企业对标Oracle JDK,Graalvm 社区版对OpenJDK

  • Graalvm 在基础支持的JDK上又添加了一个高级的JIT编译器,并且这个编译器默认为顶层的JIT编译器,运行时程序正常在JVM加载和执行,编译器将字节码编译为机器码并将其返回JVM时,支持的语言解释器是在Truffle 框架之上编写。

  • Graalvm 支持Native Image,JDK并不支持

  • Graalvm 支持多语言API,即在共享运行中组合编程语言的API(待探究)

Graalvm 目前支持的Java 框架有:
  • Micronaut Java 云原生框架

  • Spring (Spring AOT 插件支持)

  • Helidon (没听过这个)

  • Quarkus Java 云原生框架

Graalvm 目前平台的支持情况:

Community Edition 22.1 by platform.

FeatureLinux AMD64Linux ARM64macOSmacOS ARM64Windows
Native Imagestablestablestableexperimentalstable
LLVM runtimestablestablestableexperimentalnot available
LLVM toolchainstablestablestableexperimentalnot available
JavaScriptstablestablestableexperimentalstable
Node.jsstablestablestablenot availablestable
Java on Truffleexperimentalexperimentalexperimentalexperimentalexperimental
Pythonexperimentalnot availableexperimentalnot availablenot available
Rubyexperimentalexperimentalexperimentalexperimentalnot available
Rexperimentalnot availableexperimentalnot availablenot available
WebAssemblyexperimentalexperimentalexperimentalexperimentalexperimental
JVM 部署模式和原生镜像部署的关键区别
  • 编译为原生镜像时的静态代码分析是从主入口点执行,即 Java 的main方法

  • 无法识别的代码将会被删除,并且不会成为可执行文件的一部分(有点坑)

  • Graalvm 编译时不能识别代码的动态元素,如:JVM的反射机制、Classpath Resource、序列化、动态代理等

  • 应用程序的类路径在生成时是固定的,不能更改

  • 没有所谓的延迟加载(LAZY),所有可执行文件的内容会在程序启动时全部加载到内存中

  • Java 中的一些限制并没有完全受支持

理解Ahead-of-Time

Springboot依赖的就是动态配置很大程度依赖运行时的状态,而Graalvm 在创建NativeImage时,需要在代码编译时对代码进行静态分析,编译成对应的机器码,也就是说,针对于反射、序列化这种依赖于虚拟机的操作,都会被移除。Spring 的Ahead-of-time(AOT插件)就是在代码编译前做一些适配Graalvm的工作,以便Graalvm 能正确解析Springboot的代码,这些提前的工作包括:

  • Spring AOT 生成对应的源代码(需要动态生成的类直接解析生成固定的代码)

  • 字节码的处理,如Spring 中需要动态代理的Bean的处理

  • 依据应用代码生成Graalvm需要的配置文件,告诉Graalvm哪里有反射、资源文件、动态代理等,包括:

    • Resource hints (resource-config.json)

    • Reflection hints (reflect-config.json)

    • Serialization hints (serialization-config.json)

    • Java Proxy Hints (proxy-config.json)

    • JNI Hints (jni-config.json)

以@Configuration 注解举例

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

}

@Configuration 中配置的@Bean注解,会在程序启动时,由Spring的IOC 容器进行初始化,也就是运行时才创建的Bean对象,当我们创建一个Native image时,Spring就会使用另一种方法去解析这个Bean并创建Bean,Spring AOT 插件会将这个代码做以下处理:

/**
 * Bean definitions for {@link MyConfiguration}.
 */
public class MyConfiguration__BeanDefinitions {

    /**
     * Get the bean definition for 'myConfiguration'.
     */
    public static BeanDefinition getMyConfigurationBeanDefinition() {
        Class<?> beanType = MyConfiguration.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(MyConfiguration::new);
        return beanDefinition;
    }

    /**
     * Get the bean instance supplier for 'myBean'.
     */
    private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
        return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean").withGenerator(
                (registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
    }

    /**
     * Get the bean definition for 'myBean'.
     */
    public static BeanDefinition getMyBeanBeanDefinition() {
        Class<?> beanType = MyBean.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
        return beanDefinition;
    }

可以看到上边生成的代码创建的MyConfiguration的类与@Configuration创建的类大致等效,区别就在于,SpringAOT插件生成的代码 是以Graalvm编译器能直接识别的方式创建的,在Spring AOT处理期间,并不会创建Bean的实例,而是在启动时创建。

初体验Spirng 3.0 Native-Image 支持

  1. 准备工作

    环境准备:

    • IDEA 2021.3,具体版本自己看,最好是要支持JDK 17的有些低版本的不支持

    • Maven 3.8.1 Maven 版本要和IDEA兼容,有些不兼容,执行Maven命令会报错,Settings.xml配置,可以暂时取消阿里的Maven仓库镜像,不然会导致无法下载Spring maven 仓库的镜像,因为有些SNAPSHOT版本在阿里仓库没有

    • Graalvm 17 (graalvm-ce-java17-22.1.0,担心和本机JDK冲突的,可以直接在IDEA里配置)

      • cd Graalvm 安装目录

      • gu list 验证是否安装native-image
        在这里插入图片描述

      • 没有安装的 执行gu install native-image 命令安装native-image

  2. 代码编写

    HelloService

    public interface HelloService {
    
        String sayHello(String name);
    
        default String sayHello(String prefix,String name){
            return String.format("%s %s",prefix,name);
        }
    }
    
    
    &#x20;ResourceHelloService
    
    ```java
    public class ResourceHelloService implements HelloService{  
    
      private final Resource resource;  
    
      public ResourceHelloService(Resource resource) {  
    
        this.resource = resource;  
    
      }  
    
        @Override  
    
      public String sayHello(String name) {  
    
        try {  
    
          try(InputStream in = this.resource.getInputStream()){  
    
            String prefix = StreamUtils.copyToString(in, StandardCharsets.UTF_8);  
    
      return sayHello(prefix, name);  
    
      }  
    
        }catch (Exception ex){  
    
          throw new IllegalStateException("Failed to read resource " + null, ex);  
    
      }  
    
        }  
    
    }
    

    SimpleHelloService

    public class SimpleHelloService implements HelloService{
    
        @Override
        public String sayHello(String name) {
            return sayHello("Hello", name);
        }
    }
    

    DemoConfiguration

    @Configuration(proxyBeanMethods = false)
    public class DemoConfiguration {
        @Bean
        HelloService helloService() {
            return new SimpleHelloService();
        }
    }
    

    DemoController

    @RestController
    // 一定到导入
    @ImportRuntimeHints(DemoController.DemoControllerRuntimeHints.class)
    public class DemoController {
    
        private final ObjectProvider<HelloService> helloServices;
    
        public DemoController(ObjectProvider<HelloService> helloServices) {
            this.helloServices = helloServices;
        }
    
        @GetMapping("/hello")
        HelloResponse hello(@RequestParam(required = false) String mode) throws Exception {
            String message = getHelloMessage(mode, "Native");
            return new HelloResponse(message);    }
    
        private String getHelloMessage(String mode, String name) throws Exception {
            if (mode == null) {
                return "No option provided";
            } else if (mode.equals("bean")) {
                HelloService helloService = this.helloServices.getIfUnique();
                return (helloService != null) ? helloService.sayHello(name) : "No Bean found";
            } else if (mode.equals("reflection")) {
                String implementationName = Optional.ofNullable(getDefaultHelloServiceImplementation())
                        .orElse(SimpleHelloService.class.getName());
    
                Class<?> implementationClass = ClassUtils.forName(implementationName, getClass().getClassLoader());
                Method method = implementationClass.getMethod("sayHello", String.class);
                Object instance = BeanUtils.instantiateClass(implementationClass);
                return (String) ReflectionUtils.invokeMethod(method, instance, name);
            }
            else if(mode.equals("resource")){
                ResourceHelloService resourceHelloService = new ResourceHelloService(new ClassPathResource("hello.txt"));
                return resourceHelloService.sayHello(name);
            }
            return "Unknown mode: "+mode;
        }
        public record HelloResponse(String message) {
    
        }
    
        private String getDefaultHelloServiceImplementation() {
    
            return null;
        }
    
        static class DemoControllerRuntimeHints implements RuntimeHintsRegistrar{
        // 注册Spring AOT 运行时解析的配置,此代码会被Spring AOT 识别并处理
            @Override
            public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
                hints.reflection().registerConstructor(SimpleHelloService.class.getConstructors()[0], ExecutableMode.INVOKE)
                        .registerMethod(ReflectionUtils.findMethod(SimpleHelloService.class,"sayHello",String.class),ExecutableMode.INVOKE);
                hints.resources().registerPattern("hello.txt");
            }
        }
    }
    
    

    DemoAotNativeApplication

    @SpringBootApplication
    public class DemoAotNativeApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoAotNativeApplication.class, args);
        }
    
    }
    
    
  3. 启动对比验证

    • 启动时间对比

    JVM 运行:maven 的profiles 不要勾选native 然后在IDE 启动应用

在这里插入图片描述

Native模式运行:选择maven profile为native 然后点击IDEA 的plugins 中的native:build

在这里插入图片描述
在这里插入图片描述
编译比较耗时,请耐心等待后。target目录下有一个可执行文件
在这里插入图片描述

可执行文件的启动时间非常的短只有0.848s

  1. Spring AOT执行代码对比

    target目录下的spring-aot的文件夹中存在资源文件的描述

在这里插入图片描述

反射的资源文件描述

在这里插入图片描述

  1. Spring AOT 资源文件查看

    target目录下Spring AOT 自动生成的代码查看

在这里插入图片描述

  1. Spring 3.0 DEMO-AOT-NATIVE 项目地址:

参考外国程序员小哥snicoll的项目(也是Spring freamwork的开发人员):https://github.com/snicoll/demo-aot-native.git

补充知识:

不同云原生框架之间的对比

Spring /Micronaut/Quarkus 对比

Spring Native:

优点:

  • 完善的框架,

  • 使用 Spring webFlux 的反应式堆栈

  • 最大的社区

  • 更多的集成

  • 多语言支持

缺点:

  • 大量使用反射

  • 启动时间和内存使用不太适合无服务器云功能

  • 仅对 Graalvm 的实验性支持

Micronaut

优点:

  • 现代云原生框架

  • 反应堆

  • 最小的内存占用和启动时间

  • 编译期间不修改字节码

  • 删除所有级别的反射使用

  • Graalvm / 无服务器云功能

  • 多语言支持(Java grovy Kotlin)

  • 类似于Spring

缺点:

  • 较慢的编译时间 (AOT)

  • 社区比Spring 更小

Quarkus:

优点:

  • 现代云原生框架

  • 反应堆

  • 最小的内存占用和启动时间

  • 基于标准和框架(JAX-RS、Netty、Eclipse Micro profile)

  • Graalvm / Serverless 云功能

  • 个人感觉文档支持较为全面,用起来也比较好用

缺点:

  • 预览中的多语言支持 (Kotlin Scala)

  • 较慢的编译时间 (AOT)

目前Spring AOT 也都是在实验阶段,相对于Quarkus 和Micronaut 来说起步应该比较晚,预计等SpringFramework6 和Spring 3.0 正式版发布之后,有更多的开发者使用起来之后才会发展的更快,Quarkus、Micronaut目前来看支持度较好,不过更看好Quarkus框架,感觉文档更全面一些。现在对云原生框架的探索也仅仅停留在能简单用起来的阶段,国内这部分资料也比较少,后边涉及到微服务这些配套组件的集成还需慢慢探索。需要先会用,才能探究其原理。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 普通Spring Boot 3.0的新特性包括:Java 15支持,Spring WebFlux改进,R2DBC 1.1支持,改进的依赖管理,改进的Gradle插件,改进的JUnit 5支持,改进的Tomcat,Jetty和Undertow支持,改进的安全功能,更加友好的错误页面,更加友好的控制台日志,等等。 ### 回答2: Spring Boot 3.0Spring Boot框架的更新版本,带来了一些令人兴奋的新特性。下面是一些主要的变化和新增功能: 1. 支持Java 17:Spring Boot 3.0完全兼容Java 17,允许开发者使用最新的Java版本享受其提供的性能和特性。 2. 模块化开发支持:Spring Boot 3.0引入了对Java 9模块化系统的支持,可以更好地管理项目的依赖关系和模块化结构,提高了开发效率和代码可维护性。 3. 更强大的自动配置功能:Spring Boot 3.0进一步增强了自动配置功能,使得开发者可以更轻松地集成各种第三方库和组件,并且可以根据自己的需要进行自定义配置和覆盖。 4. WebFlux支持:Spring Boot 3.0对WebFlux进行了更新和改进,提供了更快,更稳定的响应式编程方式,让开发者能够更好地处理高并发和大流量请求。 5. 更好的性能和扩展性:Spring Boot 3.0进行了性能优化和代码重构,提高了应用程序的性能和扩展性,减少了资源消耗和内存占用,使得应用程序可以更快地响应和处理请求。 6. 更友好的错误处理:Spring Boot 3.0改进了错误处理机制,提供了更友好和详细的错误提示信息,有助于开发者更快地定位和解决问题。 总之,Spring Boot 3.0带来了许多新的功能和改进,使得开发者能够更轻松地构建高性能,可扩展和易于维护的应用程序。无论是在性能方面还是在开发体验上,它都是一个值得期待和尝试的版本。 ### 回答3: Spring Boot 3.0是在Spring Boot 2.x版本的基础上进行的全面升级和改进。它引入了许多新的特性和改进,进一步提升了开发者的开发效率和应用的性能。 首先,Spring Boot 3.0采用了最新的Java版本,如Java 17,这意味着开发者可以使用最新的Java语言特性和API进行开发,同时也提高了应用的性能和稳定性。 其次,Spring Boot 3.0引入了更加灵活的自动配置机制。开发者可以通过配置简单的属性来自定义各种组件的行为,而无需编写大量的代码。这极大地简化了配置工作,并提供了更高的灵活性和可扩展性。 另外,Spring Boot 3.0还引入了一些全新的模块,如Spring Cloud NativeSpring Fu,用于支持云原生应用的开发和部署。这些模块提供了丰富的功能和工具,帮助开发者更好地利用云计算和容器技术进行开发,并实现应用的快速部署和弹性伸缩。 此外,Spring Boot 3.0还加强了对微服务架构的支持。它提供了丰富的开发工具和框架,如Spring Cloud和Spring Cloud Gateway,帮助开发者构建可伸缩、可靠和高性能的微服务应用。 最后,Spring Boot 3.0还进一步提升了性能和稳定性。它通过优化内部实现和引入新的技术,如响应式编程和异步处理,提高了应用的吞吐量和并发处理能力。同时,它还通过引入更严格的错误处理机制和自动化监控工具,提供了更可靠的应用运行环境。 总的来说,Spring Boot 3.0是一个功能强大、性能优越和易用性极高的开发框架。它的新特性和改进使得开发者能够更轻松地构建高性能、可扩展和可维护的应用程序,并更好地满足不断变化的业务需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值