GraalVM Native Image简介
- GraalVM提供一种全新的方式让你发布及运行你的java应用,与传统的JVM相比,具备更小的内存占用合更快的启动速度。
- 非常适合用容器来部署应用,适合Function as a service(FaaS)应用场景。
- GrralVM使用AOT技术,在程序运行前将源代码编译为二进制的可执行的静态文件,并确定应用程序入口。
- GraalVM本机映像是一个完整的、特定于平台的可执行文件。不需要为了运行本机映像而提供Java虚拟机。
- 开始时间(以毫秒为单位)
- 立即提供峰值性能,无需热身
示例项目
GetResourceRequest.java
/** * 查询资源 request */ @Data public class GetResourceRequest { /** * 资源类型:1、资源1,2、资源2 */ @Min(value = 1,message = "资源类型不能小于1") @Max(value = 2,message = "资源类型不能大于2") private int typeId; }
BaseResponse.java
/** * 基础 response */ @Data @NoArgsConstructor public class BaseResponse<T> { /** * 返回值code */ private int code; /** * 错误消息 */ private String failMsg; /** * 数据 */ public T data; public BaseResponse(T data) { this.data = data; this.code = 0; this.failMsg = "success"; } public boolean isSuccess() { return code == 0; } }
GetResourceResponse.java
/** * 查询资源 response */ @Data public class GetResourceResponse extends BaseResponse<GetResourceResponse.Resource> { public GetResourceResponse(String resource) { super(new Resource(resource)); } /** * 资源 */ @Data public static class Resource { /** * 资源内容 */ String content; public Resource(String content) { this.content = content; } } }
IndexController.java
package show.lmm.demo.springboot_aot.controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import show.lmm.demo.springboot_aot.util.FileUtils; import show.lmm.demo.springboot_aot.view.request.GetResourceRequest; import show.lmm.demo.springboot_aot.view.response.GetResourceResponse; @RequestMapping("/") @RestController public class IndexController { @GetMapping("/get_resource") public Mono<GetResourceResponse> getResource(@Validated GetResourceRequest info) { String resource = null; if (info.getTypeId() == 1) { resource = FileUtils.readAllText(this.getClass().getResourceAsStream("/config/demo.txt")); } else { resource = FileUtils.readAllText(this.getClass().getResourceAsStream("/config/demo2.txt")); } return Mono.just(new GetResourceResponse(resource)); } }
报错及解决办法
No primary or single unique constructor found
- 问题产生的原因:当请求url时,springboot会将参数转成java bean,用到了序列化。
- 详细错误日志
[springboot_aot] [or-http-epoll-2] a.w.r.e.AbstractErrorWebExceptionHandler : [18d5e511-1] 500 Server Error for HTTP GET "/get_resource?typeId=1" java.lang.IllegalStateException: No primary or single unique constructor found for class show.lmm.demo.springboot_aot.view.request.GetResourceRequest at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[na:na] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ HTTP GET "/get_resource?typeId=1" [ExceptionHandlingWebHandler] Original Stack Trace: at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[na:na] at org.springframework.validation.DataBinder.createObject(DataBinder.java:924) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:6.1.4] at org.springframework.validation.DataBinder.construct(DataBinder.java:903) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:6.1.4] at org.springframework.web.bind.support.WebExchangeDataBinder.lambda$construct$0(WebExchangeDataBinder.java:88) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:6.1.4] at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:185) ~[na:na] at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200) ~[na:na] at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122) ~[na:na] at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:297) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:478) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1865) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.signalCached(MonoCacheTime.java:337) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onNext(MonoCacheTime.java:354) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoCacheTime$CoordinatorSubscriber.onSubscribe(MonoCacheTime.java:293) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[na:na] at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:143) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:63) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoZip$ZipCoordinator.request(MonoZip.java:220) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxMap$MapSubscriber.request(FluxMap.java:164) ~[na:na] at reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:138) ~[na:na] at reactor.core.publisher.FluxPeek$PeekSubscriber.request(FluxPeek.java:138) ~[na:na] at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onSubscribe(MonoIgnoreElements.java:72) ~[na:na] at reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:171) ~[na:na] at reactor.core.publisher.FluxPeek$PeekSubscriber.onSubscribe(FluxPeek.java:171) ~[na:na] at reactor.core.publisher.FluxMap$MapSubscriber.onSubscribe(FluxMap.java:92) ~[na:na] at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:129) ~[na:na] at reactor.core.publisher.Mono.subscribe(Mono.java:4563) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[na:na] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoZip$ZipCoordinator.request(MonoZip.java:220) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onSubscribe(MonoIgnoreThen.java:135) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:129) ~[na:na] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[na:na] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:241) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:204) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:189) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.Operators.complete(Operators.java:137) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:121) ~[na:na] at reactor.core.publisher.Mono.subscribe(Mono.java:4563) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[na:na] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[na:na] at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[na:na] at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865) ~[na:na] at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[na:na] at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180) ~[na:na] at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139) ~[na:na] at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171) ~[na:na] at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2331) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:339) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2241) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201) ~[na:na] at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83) ~[na:na] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53) ~[na:na] at reactor.core.publisher.Mono.subscribe(Mono.java:4563) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51) ~[na:na] at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:3.6.3] at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55) ~[na:na] at reactor.netty.http.server.HttpServer$HttpServerHandle.onStateChange(HttpServer.java:1169) ~[na:na] at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:710) ~[na:na] at reactor.netty.transport.ServerTransport$ChildObserver.onStateChange(ServerTransport.java:481) ~[na:na] at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:652) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:1.1.16] at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:1.1.16] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:238) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:1.1.16] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:4.1.107.Final] at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:509) ~[na:na] at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:407) ~[na:na] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[na:na] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[na:na] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[na:na] at java.base@21.0.2/java.lang.Thread.runWith(Thread.java:1596) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:na] at java.base@21.0.2/java.lang.Thread.run(Thread.java:1583) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:na] at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:838) ~[show.lmm.demo.springboot_aot.SpringbootAotApplication:na] at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:211) ~[na:na]
- 解决方法:由于graalvm默认不支持序列化/反序列化,如需支持需要手动将需要序列化/反序列化的bean配置到
reflect-config.json
配置文件中
读取resource提示java.lang.NullPointerException
- 问题产生的原因:graalvm不支持动态读取资源文件
- 解决方法:将
resources
目录中的资源文件配置到resource-config.json
配置文件中
springboot动态生成reflect和resource配置
实现
RuntimeHintsRegistrar
AotDemoRuntimeHints.javapackage show.lmm.demo.springboot_aot; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @Slf4j public class AotDemoRuntimeHints implements RuntimeHintsRegistrar { @SneakyThrows @Nullable @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar(); PathMatchingResourcePatternResolver pathMatching = new PathMatchingResourcePatternResolver(classLoader); // 配置java bean reflect registerAotPackage(bindingRegistrar, pathMatching, hints.reflection(), "show.lmm.demo.springboot_aot.view"); // 配置 资源文件 hints.resources().registerPattern("config/*.txt"); } /** * 注册AOT包 */ private void registerAotPackage(BindingReflectionHintsRegistrar bindingRegistrar, PathMatchingResourcePatternResolver pathMatching, ReflectionHints hints, String packageName ) throws Exception { CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); String resourcePath = packageName.replaceAll("\\.", "/"); resourcePath = String.format("classpath*:%s/**/*.class", resourcePath); Resource[] resources = pathMatching.getResources(resourcePath); for (Resource resource : resources) { String filename = resource.getFilename(); if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { // Ignore CGLIB-generated classes in the classpath continue; } MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); String className = metadataReader.getClassMetadata().getClassName(); // 手动aot Class<?> classObj = Class.forName(className); bindingRegistrar.registerReflectionHints(hints, classObj); } } }
配置
resources\META-INF\spring\aot.factories
org.springframework.aot.hint.RuntimeHintsRegistrar= \ show.lmm.demo.springboot_aot.AotDemoRuntimeHints