前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >借助 Spring Boot 和 GraalVM 实现原生 Java

借助 Spring Boot 和 GraalVM 实现原生 Java

作者头像
深度学习与Python
发布于 2023-04-01 05:40:23
发布于 2023-04-01 05:40:23
92600
代码可运行
举报
运行总次数:0
代码可运行

作者 | Josh Long

译者 | 张卫滨

策划 | 丁晓昀

本文是“Native Compilations Boosts Java”系列文章的一部分。你可以通过 RSS 订阅接收新文章更新通知。 Java 在主导着企业级应用。但是在云中,采用 Java 的成本要比其竞争者更高。使用 GraalVM 进行原生编译降低了在云中 Java 的成本:它所创建的应用启动更快,使用的内存也更少。 原生编译为 Java 用户带来了很多的问题:原生 Java 会如何改变开发方式?我们在什么情况下该转向原生 Java?在什么情况下又该避免转向原生 Java?要使用原生 Java,我们该采用哪个框架?本系列的文章将回答这些问题。

1 Java 社区包罗万象

我们知道,Java 是一个神奇的生态系统,很难列出哪些东西是最适合我们的。这样做的话,清单似乎是无穷无尽的。但是,要说出它的几个缺点也不是那么困难。正如 本系列文章 所说的,在 JRE 上运行的应用往往需要 10 秒钟或更长的时间才能启动,并需要数百或数千兆字节的内存。

这样的性能在如今的世界并不处于领先的位置。有些新的领域和机会正在出现:函数即服务产品、容器化与容器编排。它们有一个共同点,即对启动速度和内存占用有很高的要求。

2 迈向 GraalVM!

GraalVM 提供了一个前进的方向,但它也有一定的代价。GraalVM 是 OpenJDK 的替代方案,它有一个名为 Native Image 的工具,支持预先(ahead-of-time,AOT)编译。

AOT 编译与常规的 Java 编译有一些差异。正如 本系列的第一篇文章 所概述的那样,Native Image 消除了 Java 中“所有不必要的东西”。那么,Native Image 是如何知道 Java 或 Spring Boot 中哪些是不必要的呢?

Native Image 会探查我们的源码,并确定所有可达的代码,也就是通过调用或我们代码的使用所能链接到的代码。其他的所有内容,不管是位于应用的 classpath 下还是位于 JRE 中,都会被视为不必要的,它们都会被抛弃掉。

当我们做了一些 Native Image 无法明确知道该怎么处理的事情时,麻烦就来了。毕竟,Java 是一个非常动态化的语言。我们有可能会创建这样一个 Java 应用:在运行时,将一个字符串编译成文件系统中一个合法 Java 类文件,并将其加载到 ClassLoader 中,然后使用反射创建它的实例或者为其创建代理。我们还可能会将实例序列化到磁盘上,然后将其加载到另外一个 JVM 中。在这个过程中,我们可能不需要链接任何比java.lang.Object更具体的类。但是,如果这些类型没有被放到原生可执行堆中,所有的这些方式在原生 Java 中是无法正常运行的。

但是,我们并没有失去任何东西。我们可以在一个配置文件中告诉 Native Image 要保留哪些类型,这样,在运行时使用反射、代理、classpath 资源加载、JNI 等特性的时候,它依然可以运行。

现在,Java 和 Spring 生态系统非常庞大。所有的东西都要进行配置将会非常痛苦。所以我们有了两种方案:1)教会 Spring 尽可能避免使用这些机制,或者 2)教会 Spring 尽可能多地提供配置文件,这个配置文件必然要包含 Spring 框架和 Spring Boot,并且要在一定程度上包含 Spring Boot 支持的第三方集成功能。友情剧透一下,这两种方案我们都需要!

要运行样例项目,你需要在自己的机器上安装 GraalVM。GraalVM 有 安装指南。如果你使用 Mac 的话,也可以 使用 SDKMAN! 来安装 GraalVM。

3 Spring Native

Spring 团队在 2019 年启动了 Spring Native 项目,为 Spring Boot 生态系统引入了原生可执行程序编译的功能。它已经为多种不同的方式提供了研究场所。但是,Spring Native 并没有从根本上改变 Framework 5.x 或 Spring Boot 2.x。而且它也绝不是终点,只是漫长旅程中的第一步:它已经为下一代 Spring Framework(6.x)和 Spring Boot(3.x)证明了很多概念,这两个版本预计都会在 2022 年晚些时候发布。这些新一代的项目会进行更多的优化,所以前景看起来是非常光明的!鉴于这些版本尚未发布,我们将会在本文中研究一下 Spring Native。

Spring Native 会对发送给 Native Image 的源码进行转换。比如,Spring Native 会将spring.factories服务加载机制转换为静态类,从而使 Spring Native 应用知道要使用它们。它会将所有的 Java 配置类(带有@Configuration注解的类)转换成 Spring 的函数式配置,从而消除应用及其依赖的反射。

Spring Native 还会自动分析我们的代码,探测需要 GraalVM 配置的场景,并以编程的方式提供这些配置。Spring Native 为 Spring、Spring Boot 以及第三方集成提供了线索(hint)类。

4 第一个 Spring Native 应用:JPA、Spring MVC 和 H2

我们开始使用 Spring Native 的方式与所有其他 Spring 项目相同:访问 Spring Initializr,点击 cmd + B(或 Ctrl + B)或者 Add Dependencies,并选择 Spring Native。

Spring Initializr 会配置 Apache Maven 和 Gradle 构建。随后,只需添加必要的依赖即可。我们先从一些典型的依赖开始。将 Artifact 名称改为 jpa,接下来添加如下依赖:Spring NativeSpring WebLombokH2 Database和Spring Data JPA。请确保选择 Java 17,当然你也可以选择 Java 11,但这就像你挥舞着一个橡胶做的小鸡满世界乱跑,这看上去非常傻,对吧?点击“Generate”,解压生成的项目并将其导入到你最喜欢的 IDE 中。

这个样例非常简单,将JpaApplication.java类改成如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.jpa;
import lombok.*;import org.springframework.boot.ApplicationArguments;import org.springframework.boot.ApplicationRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.*;import javax.persistence.*;import java.util.Collection;import java.util.stream.Stream;
@SpringBootApplicationpublic class JpaApplication {
    public static void main(String[] args) {        SpringApplication.run(JpaApplication.class, args);    }}
@Componentrecord Initializr(CustomerRepository repository)       implements ApplicationRunner {
   @Override   public void run(ApplicationArguments args) throws Exception {       Stream.of("A", "B", "C", "D")               .map(c ->; new Customer(null, c))               .map(this.repository::save)               .forEach(System.out::println);   }}
@RestControllerrecord CustomerRestController(CustomerRepository repository) {
    @GetMapping("/customers")    Collection<Customer> customers() {        return this.repository.findAll();    }}
interface CustomerRepository extends JpaRepository<Customer, Integer> {}
@Entity@Getter@Setter@ToString@NoArgsConstructor@AllArgsConstructor@Table (name = "customer")class Customer {    @Id    @GeneratedValue    private Integer id;    private String name;}

我们也可以将测试以原生可执行文件的形式进行编译和运行。但是需要注意的是,有些内容还不能很好的运行,比如 Mockito。我们修改测试类JpaApplicationTests.java,使其如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.jpa;
import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.util.Assert;
@SpringBootTestclass JpaApplicationTests {
    private final CustomerRepository customerRepository;
    @Autowired    JpaApplicationTests(CustomerRepository customerRepository) {        this.customerRepository = customerRepository;    }
    @Test    void contextLoads() {        var size = this.customerRepository.findAll().size();        Assert.isTrue(size > 0, () -> "there should be more than one result!");    }
}

在本文中,我将会展示 macOS 下的命令。对于 WindowsLinux,请相应的进行调整。

我们可以按照常规的方式运行应用和测试,比如在终端中运行mvn spring-boot:run命令。直接运行这些样例其实是个不错的主意,至少可以保证应用能够正常运行。但是,这并不是我们的目的。相反,我们想要将应用及其测试编译成 GraalVM 原生应用。

如果你看过 pom.xml 文件的话,你就会发现里面有很多额外的配置,它们搭建了 GraalVM 原生镜像并添加了一个 Maven profile(叫做native)以支持构建原生可执行文件。我们可以使用mvn clean package像以往那样编译应用。也可以使用mvn -Pnative clean package对应用进行原生编译。需要记住的是,你需要将 GraalVM 设置为成自己的 JDK。这个过程会持续几分钟,所以现在是来一杯茶、咖啡、水或其他饮品的时间。我就是这么做的,因为我需要它。当我回来的时候,我看到了如下所示的输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
...13.9s (16.9% of total time) in 71 GCs | Peak RSS: 10.25GB | CPU load: 5.66...[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:  03:00 min[INFO] Finished at: 2022-04-28T17:57:56-07:00[INFO] ------------------------------------------------------------------------

我们花费了三分钟的时间来编译原生测试,如果测试成功的话,还会编译原生应用本身。在这个过程中,Native Image 使用了高达 10.25GB 的 RAM。为了加快讲解的速度,在后文中我将会跳过编译和运行测试的过程。所以,当我们编译下面的样例时,将会使用如下的命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mvn -Pnative -DskipTests clean package

编译时间因应用的 classpath 不同而有所差异。根据经验,如果跳过编译测试的话,我的大多数构建将会需要 1 分钟到 90 秒的时间。例如,本应用包含了 JPA(和 Hibernate)、Spring Data、H2 数据库、Apache Tomcat 和 Spring MVC。

运行应用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
./target/jpa

在我的机器上,将会看到:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
…Started TraditionalApplication in 0.08 seconds (JVM running for 0.082)

非常不错,80 毫秒,也就是千分之八十秒!更棒的,该应用几乎不占用任何内存。我使用如下的脚本来测试应用的 RSS(resident set size)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/usr/bin/env bash  PID=$1RSS=`ps -o rss ${PID} | tail -n1`RSS=`bc <<< "scale=1; ${RSS}/1024"`echo "RSS memory (PID: ${PID}): ${RSS}M"

我们需要正在运行的应用的进程 ID(PID)。在 macOS 上,我可以通过运行pgrep jpa来获取它。我所使用的脚本如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
~/bin/RSS.sh $(pgrep jpa)RSS memory (PID: 35634): 96.9M

大约 97MB 的 RAM。这个数值可能会因运行应用的操作系统和架构的不同而有所差异。在 Intel 上的 Linux 和 M1 上的 macOS 中运行应用时,这个值就是不一样的。与 JRE 应用相比,这当前是一个明显的改进,但依然并不是最好的。

我喜欢反应式编程,而且我认为它更适合我现在的工作负载。我创建了一个类似的反应式应用。它不仅耗费了更少的空间(原因很多,包括 Spring Data R2DBC 支持 Java 17 的 record 语法),应用的编译时间是 1:14(差不多快了两分钟),启动时间是 0.044 秒。它占用的内存少了 35%,大约为 63.5MB。这个应用每秒还可以处理更多的请求。所以,它的编译和执行速度更快,内存效率更高,启动更快并且能够处理更高的流量。我说的是,在各方面这都是一笔不亏的买卖。

5 集成应用

Spring 不仅仅是 HTTP 端点,还有很多其他的东西。它包括很多框架,比如 Spring Batch、Spring Integration、Spring Security、Spring Cloud 以及不断增加的其他框架,它们都提供了对 Spring Native 的良好支持。

我们看一个 Spring Integration 的应用样例。Spring Integration 是一个支持企业级应用集成(enterprise-application integration,EAI)的框架。Gregor Hohpe 和 Bobby Woolf 的开创性著作 Enterprise Integration Patterns 为集成模式提供了通用的术语。Spring Integration 提供了实现这些模式的抽象。

返回 Spring Initializr,将项目命名为 integration,并选择 Java 17,添加Spring Native、Spring Integration、Spring Web,然后点击Generate。我们需要在pom.xml文件中手动添加一个依赖项:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>  <groupId>org.springframework.integration</groupId>  <artifactId>spring-integration-file</artifactId>  <version>${spring-integration.version}</version></dependency>

修改IntegrationApplication.java的代码,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.integration;
import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.integration.dsl.IntegrationFlow;import org.springframework.integration.dsl.IntegrationFlows;import org.springframework.integration.file.dsl.Files;import org.springframework.integration.file.transformer.FileToStringTransformer;import org.springframework.integration.transformer.GenericTransformer;
import java.io.File;
@SpringBootApplicationpublic class IntegrationApplication {
    @Bean    IntegrationFlow integration(@Value("file://${user.home}/Desktop/integration") File root) {        var in = new File(root, "in");        var out = new File(root, "out");        var inboundFileAdapter = Files                .inboundAdapter(in)                .autoCreateDirectory(true)                .get();        var outboundFileAdapter = Files                .outboundAdapter(out)                .autoCreateDirectory(true)                .get();        return IntegrationFlows //                .from(inboundFileAdapter, spec -> spec.poller(pm -> pm.fixedRate(1000)))//                .transform(new FileToStringTransformer())                .transform((GenericTransformer<String, String>) source -> new StringBuilder(source)                        .reverse()                        .toString()                        .trim())                .handle(outboundFileAdapter)                .get();    }
    public static void main(String[] args) {        SpringApplication.run(IntegrationApplication.class, args);    } }

这个应用非常简单:它会监控一个目录($HOME/Desktop/integration/in)中的新文件。一旦发现新文件,它就会创建一个副本,其String内容与源文件恰好相反,并将其写入到$HOME/Desktop/integration/out中。在 JRE 上,该应用的启动时间为 0.429 秒。这已经非常不错了,接下来我们看一下将其转换成 GraalVM 可执行文件会带来什么变化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mvn -Pnative -DskipTests clean package

该应用的编译时间为 55.643 秒。它的启动时间(./target/integration)为 0.029 秒,占用了 35.5MB 的 RAM。很不错!

我们可以看到,没有所谓的典型结果。编译过程的输入对输出有着很大的影响。

6 将应用带入生产环境

在某个时间点,我们可能希望将应用部署到生产环境中,如今典型的生产环境就是 Kubernetes 了。Kubernetes 以容器的方式运行。Buildpacks 项目 背后的核心概念是集中和重用将应用制品转换成容器的习惯性做法。使用 Buildpacks 的方式有很多,可以借助 pack CLI,也可以在 Kubernetes 集群中使用 KPack,还可以使用 Spring Boot 的构建插件。我们将使用最后一种方式,因为它仅需要 Docker Desktop 即可。请 点击官网 了解 Docker Desktop 的更多信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mvn spring-boot:build-image

该命令会在容器内构建原生可执行文件,所以我们会得到一个包含 Linux 原生二进制文件的 Linux 容器。随后,我们可以通过 docker tag 和 docker push 为其添加标签并推送至所选择的容器 registry 中。当我在 2022 年 5 月撰写这篇文章的时候,在 M1 架构的 Mac 上,Docker Buildpacks 仍然有点不稳定。但我相信这很快就会得到解决。

7 为 Native Image 提供一些线索

在到目前为止所看到的样例中,为了让应用能够以原生可执行文件的形式运行,我们并没有做其他更多的事情。按照上述默认的配置,它自然就可以运行。在大多数情况下,这种易用性就是我们期望的结果。但有时候,我们需要给 Native Image 提供一些线索,正如我在前面的“迈向 GraalVM!”章节所提到的那样。

我们看一下另外一个样例。首先,进入 Spring Initializr,将项目命名为 extensions,选择 Java 17 并添加Spring Native,然后点击Generate。接下来,我们会手动添加一个在 Initialzr 上不存在的依赖项:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-json</artifactId></dependency>

我们在这里的目标是看一下当出错的时候,会发生些什么。Spring Native 提供了一组线索,允许我们很容易地增强默认的配置。将ExtensionsApplication.java修改为如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.extensions;
import com.fasterxml.jackson.core.type.TypeReference;import com.fasterxml.jackson.databind.ObjectMapper;import org.aopalliance.intercept.MethodInterceptor;import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.boot.*;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.core.io.ClassPathResource;import org.springframework.nativex.hint.*;import org.springframework.stereotype.Component;import org.springframework.util.*;
import java.io.InputStreamReader;import java.util.List;import java.util.function.Supplier;
@SpringBootApplicationpublic class ExtensionsApplication {    public static void main(String[] args) {        SpringApplication.run(ExtensionsApplication.class, args);    }}
@Componentclass ReflectionRunner implements ApplicationRunner {
    private final ObjectMapper objectMapper ;
    ReflectionRunner(ObjectMapper objectMapper) {        this.objectMapper = objectMapper;    }
    record Customer(Integer id, String name) {    }
    @Override    public void run(ApplicationArguments args) throws Exception {        var json = """                [                 { "id" : 2, "name": "Dr. Syer"} ,                 { "id" : 1, "name": "Jürgen"} ,                 { "id" : 4, "name": "Olga"} ,                 { "id" : 3, "name": "Violetta"}                  ]                """;        var result = this.objectMapper.readValue(json, new TypeReference<List<Customer>>() {        });        System.out.println("there are " + result.size() + " customers.");        result.forEach(System.out::println);    }}
@Componentclass ResourceRunner implements ApplicationRunner {
    @Override    public void run(ApplicationArguments args) throws Exception {        var resource = new ClassPathResource("Log4j-charsets.properties");        Assert.isTrue(resource.exists(), () -> "the file must exist");        try (var in = new InputStreamReader(resource.getInputStream())) {            var contents = FileCopyUtils.copyToString(in);            System.out.println(contents.substring(0, 100) + System.lineSeparator() + "...");        }    }}
@Componentclass ProxyRunner implements ApplicationRunner {
    private static Animal buildAnimalProxy(Supplier<String> greetings) {        var pfb = new ProxyFactoryBean();        pfb.addInterface(Animal.class);        pfb.addAdvice((MethodInterceptor) invocation -> {            if (invocation.getMethod().getName().equals("speak"))                System.out.println(greetings.get());
            return null;        });        return (Animal) pfb.getObject();    }
    @Override    public void run(ApplicationArguments args) throws Exception {        var cat = buildAnimalProxy(() -> "meow!");        cat.speak();
        var dog = buildAnimalProxy(() -> "woof!");        dog.speak();    }
    interface Animal {        void speak();    }}

这个样例包含了三个 ApplicationRunner 实例,Spring 应用在启动的时候会运行它们。每个 Bean 都会做一些让 GraalVM Native Image 感觉不爽的事情。但是,在 JVM 上,它们能够很好地运行:mvn spring-boot:run。

第一个ApplicationRunner,即ReflectionRunner,会读取 JSON 数据并使用反射将它的结构映射到一个 Java 类Customer上。它无法正常运行,因为 Native Image 将会移除Customer类。使用mvn -Pnative -DskipTests clean package构建应用,并使用./target/extensions运行它。我们将会看到“com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces”这样的错误。

我们可以使用@TypeHint注解来修复该问题。添加如下的内容到ExtensionsApplication类上:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@TypeHint(types =  ReflectionRunner.Customer.class, access = { TypeAccess.DECLARE

在这里,我们声明我们希望对ReflectionRunner.Customer的构造器和方法进行反射访问。对于不同类型的反射,还有其他的TypeAccess值。

第二个ApplicationRunner即ResourceRunner,会从 classpath 下某个依赖的.jar中加载文件。它也无法正常运行,并且会提示“java.lang.IllegalArgumentException: the file must exist”这样的错误。原因在于该文件位于其他的.jar中,而不是在我们的应用代码中。如果文件位于src/main/resources中的话,加载资源是可以正常运行的。我们可以使用@ResourceHint注解来解决这个问题。将如下的内容添加到ExtensionsApplication类中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ResourceHint(patterns = "Log4j-charsets.properties", isBundle = false)

第三个ApplicationRunner,即ProxyRunner,创建了一个 JDK 代理。代理会创建相关类型的子类或实现类。Spring 支持两种类型的代理,即 JDK 代理和 AOT 代理。JDK 代理仅限于使用 Java java.lang.reflect.Proxy的接口。AOT 代理则是 Spring 特有的,并不是 JRE 的一部分。JDK 代理通常是给定具体类的子类,也可能是接口。Native Image 需要知道我们的代理要使用哪些接口和具体类。

继续将第三个应用编译为原生可执行文件。Native Image 将会给出一条友好的错误信息“com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces”,并且会列出所有 Spring 试图要代理的接口。请注意这些类型:com.example.extensions.ProxyRunner.Animal、org.springframework.aop.SpringProxy、org.springframework.aop.framework.Advised和org.springframework.core.DecoratingProxy。我们将会使用它们为ExtensionsApplication添加如下的线索:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@JdkProxyHint(types = {    com.example.extensions.ProxyRunner.Animal.class,    org.springframework.aop.SpringProxy.class,    org.springframework.aop.framework.Advised.class,    org.springframework.core.DecoratingProxy.class})

如果你现在尝试构建(mvn -DskipTests -Pnative clean package)并运行(./target/extensions)样例的话,就不会有任何问题了。

8 构建期和运行期的 Processor

Spring 有很多的Processor实现。Spring Native 提供了一些新的Processor接口,它们只会在构建期激活。这些Processor会动态地为构建过程提供线索信息。理想情况下,这些Processor的实现会位于一个可重用的库中。访问 Spring Initializr,将项目命名为 processors,并添加Spring Native。在 IDE 中打开生成的项目,在pom.xml文件中移除build节点,这样会删除所有的 Maven 插件配置。接下来,我们需要手动添加一个新的库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>  <groupId>org.springframework.experimental</groupId>  <artifactId>spring-aot</artifactId>  <version>${spring-native.version}</version>  <scope>provided</scope></dependency>

Maven 构建会生成一个常规的 Java “.jar”制品,我们可以像对待任意 Maven “.jar”那样对其进行安装和部署:mvn -DskipTests clean install。

这个新的库会引入新的类型,包括:

  • BeanFactoryNativeConfigurationProcessor:它在构建期的行为等同于BeanFactoryPostProcessor
  • BeanNativeConfigurationProcessor:它在构建期的行为等同于BeanPostProcessor

我发现自己大多数时候都在和这两个接口打交道。在每个接口中,我们都可以得到一个可供探测的引用以及一个注册表的引用,我们据此能够以编程的方式向注册表中贡献线索内容。如果使用BeanNativeConfigurationProcessor,我们会得到一个 bean 元数据的实例,它代表了 bean factory 中的一个 bean。如果使用BeanFactoryNativeConfigurationProcessor的话,我们会得到对整个BeanFactory本身的引用。需要注意的是,我们只能使用 bean 的名称和BeanDefinition实例,无法使用真正的 bean。BeanFactory能够知道所有在运行时会存在的对象,但是它此时还没有实例化它们。相反,它的作用是帮助我们理解运行中的应用中 bean 的样子,比如类、方法等,以便于得到适当的线索信息。

我们不能以常规 Spring bean 的形式来注册这些Processor类型,而是要在spring.factories服务加载器中进行注册。所以,鉴于BeanFactoryNativeConfigurationProcessor的实现名为com.example.nativex.MyBeanFactoryNativeConfigurationProcessor,BeanNativeConfigurationProcessor的实现名为com.example.nativex.MyBeanNativeConfigurationProcessor,spring.factories文件如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
org.springframework.aot.context.bootstrap.generator.infrastructure.nativex.BeanFactoryNativeConfigurationProcessor=\  com.example.nativex.MyBeanFactoryNativeConfigurationProcessororg.springframework.aot.context.bootstrap.generator.infrastructure.nativex.BeanNativeConfigurationProcessor=\  com.example.nativex.MyBeanNativeConfigurationProcessor

借助这些 Processor 类型,我们可以很容易地在 Spring Native 应用中消费集成功能或库。我写了一个库(com.joshlong:hints:0.0.1),里面包含了各种集成功能(如 Kubernetes Java 客户端、Fabric8 Kubernetes Java 客户端、Spring GraphQL、Liquibase 等),这些集成功能不大适合放到官方的 Spring Native 版本中。目前这就是一个大杂烩,但结果是很酷的:只要把相关的功能添加到 classpath 中,就像 Spring Boot 的自动配置一样,我们就会得到一个很棒的结果!

9 更多信息

我希望你能够从这个关于 Spring Native 原生可执行文件的简单介绍中有所收获。请继续关注 Spring 博客 和我的 Twitter (@starbuxman) ,以获取更多信息。

作者介绍:

Josh Long(Twitter 为 @starbuxman)是第一个 Spring 开发者倡导者,始于 2010 年。Josh 是一个 Java Champion,写了 6 本图书(包括 O'Reilly 的“Cloud Native Java: Designing Resilient Systems with Spring Boot, Spring Cloud, and Cloud Foundry”和“Reactive Spring”)和制作了许多畅销的视频培训(包括与 Spring Boot 联合创始人 Phil Webb 合作的“Building Microservices with Spring Boot Livelessons”),并且是开源贡献者(Spring Boot、Spring Integration, Spring Cloud、Activiti 和 Vaadin 等)、播客(“A Bootiful Podcast”)和 YouTuber。

原文链接:

https://www.infoq.com/articles/native-java-spring-boot/

点击底部阅读原文 访问 InfoQ 官网,获取更多精彩内容!

今日好文推荐

微软开始封禁商业开源:从 App Store 入手,7 月 16 日生效?!

迁移进行时,告别 GitHub 的时候到了?

腾讯安全回应数据产品线裁撤;马斯克称终止收购推特;拼多多“砍一刀”涉嫌欺诈案一审宣判 |Q 资讯

GitLab 技术选型为何如此不同:坚持用过气 Web 框架十多年、坚决不用微服务

点个在看少个 bug 👇

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 InfoQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
​LeetCode刷题实战62:不同路径
算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !
程序员小猿
2021/01/20
3220
​LeetCode刷题实战62:不同路径
【LeetCode】62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
韩旭051
2020/06/23
2500
【LeetCode】62. 不同路径
​LeetCode刷题实战63:不同路径 II
https://leetcode-cn.com/problems/unique-paths-ii/
程序员小猿
2021/01/20
2750
【算法】动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )
LeetCode 62.不同路径 : https://leetcode.cn/problems/unique-paths
韩曙亮
2023/03/30
5870
【算法】动态规划 ③ ( LeetCode 62.不同路径 | 问题分析 | 自顶向下的动态规划 | 自底向上的动态规划 )
打卡群刷题总结0716——不同路径
链接:https://leetcode-cn.com/problems/unique-paths
木又AI帮
2020/07/20
2460
打卡群刷题总结0716——不同路径
DP(动态规划)经典路径问题 | LeetCode
动态规划中的路径问题,题目来自于 LeetCode,子标题为 题号 名称 的格式。
做棵大树
2022/09/27
5820
DP(动态规划)经典路径问题 | LeetCode
【leetcode刷题】T156-不同路径
https://leetcode-cn.com/problems/unique-paths/
木又AI帮
2019/09/03
2990
LeetCode 62. 不同路径(DP)
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
Michael阿明
2021/02/20
2140
LeetCode 62. 不同路径(DP)
LeetCode 62. 不同路径 - Go 实现
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
王小明_HIT
2023/03/01
1590
LeetCode 62. 不同路径 - Go 实现
leetcode每日一题:62.不同路径
题目: https://leetcode-cn.com/problems/unique-paths/
用户3578099
2020/12/14
3210
09— 不同路径【LeetCode62】
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
吃猫的鱼Code
2023/07/24
2020
09— 不同路径【LeetCode62】
DP入门之不同路径
力扣题目链接:https://leetcode-cn.com/problems/unique-paths
代码随想录
2022/01/07
5250
DP入门之不同路径
leetcode刷题(123)——63. 不同路径 II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
老马的编程之旅
2022/11/14
1900
leetcode刷题(123)——63. 不同路径 II
【leetcode刷题】T157-不同路径 II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
木又AI帮
2019/09/03
3420
LeetCode 75 —— 62. 不同路径
示例 3: 输入:m = 7, n = 3 输出:28 示例 4: 输入:m = 3, n = 3 输出:6 提示: 1 <= m, n <= 100 题目数据保证答案小于等于 2 * 10^9 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/unique-paths 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
Regan Yue
2023/07/10
1720
LeetCode 75 —— 62. 不同路径
LeetCode 63. 不同路径 II
拿到题目之后,这是一道考察动态规划的题。状态转移方程定义如下:dp[i][j] 表示 从起点到(i,j)位置所有路径。dp[i][j] = dp[i-1][j] + dp[i][j-1]。需要注意的是题目1表示障碍,则只有当前位置(i,j)为0时,才更新方程。而且在初始化dp数组的第1行和第1列时,dp[i][0], dp[0][j]为1。需要注意如果初始化过程中遇到障碍(i,j)为1时,则后续dp[i][0],dp[0][j] 设置为1。
用户7447819
2022/03/04
1990
【一天一大 lee】不同路径 (难度:中等) - Day20201209
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
前端小书童
2020/12/17
2940
打卡群刷题总结0717——不同路径 II
链接:https://leetcode-cn.com/problems/unique-paths-ii
木又AI帮
2020/07/22
2900
打卡群刷题总结0717——不同路径 II
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
lucifer210
2019/10/21
3470
62. 不同路径
Leetcode No.63 不同路径 II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
week
2021/05/06
4370
相关推荐
​LeetCode刷题实战62:不同路径
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验