使用 Spring Boot 和 GraalVM 实现原生(系列五)

举报
小云悠悠zZ 发表于 2023/01/30 15:56:19 2023/01/30
【摘要】 该示例包括三个 ApplicationRunner 实例,Spring 在应用程序启动时运行这些实例。每个 bean 都会做一些会惹恼 GraalVM 本机映像的事情。但它在 JVM 上工作得很好:mvn spring-boot:run

为原生图像提供提示

在到目前为止的示例中,您无需执行任何操作即可使应用程序作为本机可执行文件工作。它只是奏效了。这种易用性是您大多数时候期望的结果。但有时,您必须提供本机映像的线索,。

让我们看另一个例子。首先,转到 Spring Initializr,命名项目扩展,选择 Java 17,添加 ,然后单击 。接下来,我们将手动添加一个不在 Initialzr 上的依赖项:Spring NativeGenerate

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
</dependency>

这里的目标是看看当出现问题时会发生什么。Spring Native 提供了一组提示,使增加默认配置变得微不足道。将 a 更改为以下内容:ExtensionsApplication.jav

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;

@SpringBootApplication
public class ExtensionsApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExtensionsApplication.class, args);
    }
}

@Component
class 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);
    }
}

@Component
class 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() + "...");
        }
    }
}

@Component
class 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 本机映像的事情。但它在 JVM 上工作得很好:mvn spring-boot:run

第一个读取 JSON 数据并以反射方式将结构映射到 Java 类 上。它不起作用,因为本机映像将删除该类!构建它并运行 .然后,您将自己看到“com.oracle.svm.core.jdk.UnsupportedFeatureError:由接口定义的代理类”错误。ApplicationRunner, ReflectionRunner,CustomerCustomermvn -Pnative -DskipTests clean package./target/extensions

您可以使用注释来解决此问题。将以下内容添加到类中:@TypeHint ExtensionsApplication

@TypeHint(types =  ReflectionRunner.Customer.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS })

在这里,我们说我们希望对 的构造函数和方法进行反射访问。对于不同类型的反射,还有其他值。ReflectionRunner.CustomerTypeAccess

第二个从类路径上的依赖项之一加载文件。它不起作用,并且会给你一个“java.lang.IllegalArgumentException:文件必须存在”错误!原因是这个资源存在于其他一些,而不是我们的应用程序代码。如果此资源位于 中,则加载此资源将起作用。您可以使用注释使其工作。将以下内容添加到类中:ApplicationRunner, ResourceRunner,.jar.jarsrc/main/resources@ResourceHintExtensionsApplication

@ResourceHint(patterns = "Log4j-charsets.properties", isBundle = false)

第三,创建一个JDK代理。代理创建类型的子类或实现。Spring 知道两种:JDK 代理和 AOT 代理。JDK代理仅限于与Java的接口。AOT 代理是弹簧主义,不是 JRE 的一部分。这些 JDK 代理通常是给定具体类的子类,可能包括接口。本机映像需要知道代理将使用哪些接口和具体类。ApplicationRunner, ProxyRunner java.lang.reflect.Proxy

继续将第三个应用程序编译为本机可执行文件。Native Image 会再次给你友好的错误消息“com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class by interfaces define ”,并列出 Spring 尝试代理的所有接口。请注意这些类型:和 。我们将使用它们为类制作以下提示:com.example.extensions.ProxyRunner.Animal, org.springframework.aop.SpringProxy, org.springframework.aop.framework.Advisedorg.springframework.core.DecoratingProxyExtensionsApplication

@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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。