使用 Spring Boot 和 GraalVM 实现原生(系列五)
为原生图像提供提示
在到目前为止的示例中,您无需执行任何操作即可使应用程序作为本机可执行文件工作。它只是奏效了。这种易用性是您大多数时候期望的结果。但有时,您必须提供本机映像的线索,。
让我们看另一个例子。首先,转到 Spring Initializr,命名项目扩展,选择 Java 17,添加 ,然后单击 。接下来,我们将手动添加一个不在 Initialzr 上的依赖项:Spring Native
Generate
<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,
Customer
Customer
mvn -Pnative -DskipTests clean package
./target/extensions
您可以使用注释来解决此问题。将以下内容添加到类中:@TypeHint
ExtensionsApplication
@TypeHint(types = ReflectionRunner.Customer.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS })
在这里,我们说我们希望对 的构造函数和方法进行反射访问。对于不同类型的反射,还有其他值。ReflectionRunner.Customer
TypeAccess
第二个从类路径上的依赖项之一加载文件。它不起作用,并且会给你一个“java.lang.IllegalArgumentException:文件必须存在”错误!原因是这个资源存在于其他一些,而不是我们的应用程序代码。如果此资源位于 中,则加载此资源将起作用。您可以使用注释使其工作。将以下内容添加到类中:ApplicationRunner, ResourceRunner,
.jar
.jar
src/main/resources
@ResourceHint
ExtensionsApplication
@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.Advised
org.springframework.core.DecoratingProxy
ExtensionsApplication
@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
- 点赞
- 收藏
- 关注作者
评论(0)