これは、なにをしたくて書いたもの?
以前に、簡単なJavaアプリケーションをGraalVM(Substrate VM)を使ってネイティブイメージにしてみたのですが、
もう少しライブラリなどを使った複雑な(?)ものをネイティブイメージにしてみようと思いまして。
で、どのくらい大変か?というのを知ってみましょう、というお題で。
環境
今回の環境は、こちら。
$ java -version openjdk version "1.8.0_202" OpenJDK Runtime Environment (build 1.8.0_202-20190206132807.buildslave.jdk8u-src-tar--b08) OpenJDK GraalVM CE 1.0.0-rc16 (build 25.202-b08-jvmci-0.59, mixed mode) $ mvn -version Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00) Maven home: $HOME/maven/current Java version: 1.8.0_202, vendor: Oracle Corporation, runtime: /usr/local/graalvm-ce/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-48-generic", arch: "amd64", family: "unix"
お題
RESTEasyとUndertowを使った、超簡単なアプリケーションをGraalVMを使ってネイティブイメージにしてみます。
使用する依存関係は、こちら。
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow</artifactId> <version>3.6.3.SP1</version> </dependency>
JAX-RSリソースクラス。
src/main/java/org/littlewings/graal/nativeimage/HelloResource.java
package org.littlewings.graal.nativeimage; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("hello") public class HelloResource { @GET @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello RESTEasy!!"; } }
Applicationクラス。
src/main/java/org/littlewings/graal/nativeimage/JaxrsActivator.java
package org.littlewings.graal.nativeimage; import java.util.HashSet; import java.util.Set; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { private Set<Class<?>> resourceClasses; public JaxrsActivator() { resourceClasses = new HashSet<>(); resourceClasses.add(HelloResource.class); } @Override public Set<Class<?>> getClasses() { return resourceClasses; } }
起動クラス。
src/main/java/org/littlewings/graal/nativeimage/App.java
package org.littlewings.graal.nativeimage; import io.undertow.Undertow; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.spi.ResteasyDeployment; public class App { public static void main(String... args) { UndertowJaxrsServer server = new UndertowJaxrsServer(); ResteasyDeployment deployment = new ResteasyDeployment(); deployment.setApplication(new JaxrsActivator()); server.deploy(deployment); server.start(Undertow.builder().addHttpListener(8080, "0.0.0.0")); } }
確認。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.graal.nativeimage.App $ curl localhost:8080/hello Hello RESTEasy!!
OKです。これを、ネイティブイメージにしていきます。
Native Image Maven Pluginを追加して、とりあえず動かしてみる
pom.xmlに、以下のようにNative Image Maven Pluginを追加して、まずは動かしてみましょう。なんとなく、Profileは分けました。
<profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc16</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <mainClass>org.littlewings.graal.nativeimage.App</mainClass> <imageName>resteasy-undertow</imageName> </configuration> </plugin> </plugins> </build> </profile> </profiles>
ビルド。
$ mvn package -Pnative
警告が出ます。
Warning: Abort stand-alone image build. com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time. Detailed message: Trace: at parsing org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) Call path from entry point to org.xnio.Xnio$OsgiSupport.doGetOsgiService(): at org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) at org.xnio.Xnio.doGetInstance(Xnio.java:261) at org.xnio.Xnio.getInstance(Xnio.java:187) at io.undertow.Undertow.start(Undertow.java:118) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.start(UndertowJaxrsServer.java:281) at org.littlewings.graal.resteay.App.main(App.java:20) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:153) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
スタンドアロンなイメージのビルドが失敗したと言っています。
Warning: Abort stand-alone image build.
最後には、実行時にJDKが必要なイメージにフォールバックしたよ、と言われます。
Warning: Image 'resteasy-undertow' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
ファイル自体はできています。
実際、Javaがインストールされていない環境で作成された動作させると、起動に失敗します。
$ target/resteasy-undertow Error: No bin/java and no environment variable JAVA_HOME
Javaがインストールされている環境であれば、起動させることができます。
$ target/resteasy-undertow 4 27, 2019 9:05:38 午後 org.jboss.resteasy.spi.ResteasyDeployment processApplication INFO: RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.graal.nativeimage.JaxrsActivator 4 27, 2019 9:05:38 午後 org.jboss.resteasy.spi.ResteasyDeployment processApplication INFO: RESTEASY002200: Adding class resource org.littlewings.graal.nativeimage.HelloResource from Application class org.littlewings.graal.nativeimage.JaxrsActivator 4 27, 2019 9:05:38 午後 org.xnio.Xnio <clinit> INFO: XNIO version 3.3.8.Final 4 27, 2019 9:05:38 午後 org.xnio.nio.NioXnio <clinit> INFO: XNIO NIO Implementation Version 3.3.8.Final
微妙ですね。
ではこれを、Javaがインストールされていない環境でも動作するネイティブイメージができるまで、頑張って変えていって
みましょう。
参考にしたのは、こちらです。
Netty HTTP HelloWorldの起動が600msくらいだったんだけどGraalVMのNative Imageを使うと15msくらいになったー - Mitsuyuki.Shiiba
GitHub - cstancu/netty-native-demo: Instant Netty startup using GraalVM's Native Image Generation
ビルド時に存在しないクラスを無視してみる
先ほどの警告を見ると、ビルド時に存在しないクラスに対して怒っているようです。
Warning: Abort stand-alone image build. com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time.
「--allow-incomplete-classpath」オプションを付けてみたら?と言っているので、試してみましょう。
以下のように、Native Image Maven Pluginの設定に反映。
<configuration> <mainClass>org.littlewings.graal.nativeimage.App</mainClass> <imageName>resteasy-undertow</imageName> <buildArgs>--allow-incomplete-classpath</buildArgs> </configuration>
すると、警告の内容が変わって、数がだいぶ増えます。
Warning: class initialization of class io.undertow.protocols.alpn.JettyAlpnProvider$Impl failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider. This class will be initialized at run time because either option --report-unsupported-elements-at-runtime or option --allow-incomplete-classpath is used for image building. Use the option --delay-class-initialization-to-runtime=io.undertow.protocols.alpn.JettyAlpnProvider$Impl to explicitly request delayed initialization of this class. [resteasy-undertow:14110] analysis: 9,815.47 ms Warning: Abort stand-alone image build. Unsupported features in 3 methods Detailed message: Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(AjpServerRequestConduit.java:195) Call path from entry point to io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(ByteBuffer): at io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(AjpServerRequestConduit.java:183) at io.undertow.protocols.ssl.SslConduit.doUnwrap(SslConduit.java:703) at io.undertow.protocols.ssl.SslConduit.doHandshake(SslConduit.java:648) at io.undertow.protocols.ssl.SslConduit.access$900(SslConduit.java:63) at io.undertow.protocols.ssl.SslConduit$5$1.run(SslConduit.java:1084) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(AjpServerResponseConduit.java:404) Call path from entry point to io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(): at io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(AjpServerResponseConduit.java:402) at org.xnio.conduits.ConduitStreamSinkChannel.flush(ConduitStreamSinkChannel.java:162) at io.undertow.server.protocol.framed.AbstractFramedChannel.flushSenders(AbstractFramedChannel.java:609) at io.undertow.server.protocol.framed.AbstractFramedChannel$5.run(AbstractFramedChannel.java:724) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing org.xnio.channels.Channels.drain(Channels.java:817) Call path from entry point to org.xnio.channels.Channels.drain(StreamSourceChannel, long): at org.xnio.channels.Channels.drain(Channels.java:801) at io.undertow.server.HttpServerExchange.endExchange(HttpServerExchange.java:1646) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:381) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
そして、やっぱりJavaがないと起動できないイメージにフォールバックします…。
Warning: Image 'resteasy-undertow' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
そもそもフォールバックして欲しくないので、フォールバックする状況になったらエラーになるように、「--no-fallback」オプションを
つけてネイティブイメージを作成することにしましょう。
<configuration> <mainClass>org.littlewings.graal.nativeimage.App</mainClass> <imageName>resteasy-undertow</imageName> <buildArgs>--no-fallback</buildArgs> </configuration>
この状態でビルドすると、以下のように警告からエラーに変わります。
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time. Detailed message: Trace: at parsing org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) Call path from entry point to org.xnio.Xnio$OsgiSupport.doGetOsgiService(): at org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) at org.xnio.Xnio.doGetInstance(Xnio.java:261) at org.xnio.Xnio.getInstance(Xnio.java:187) at io.undertow.Undertow.start(Undertow.java:118) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.start(UndertowJaxrsServer.java:281) at org.littlewings.graal.resteay.App.main(App.java:20) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:153) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception Error: Image build request failed with exit status 1
以降は、このbuildArgsを調整する時は、buildArgs要素のみを記載します。
ちなみにですね、このアプリケーション、うちの環境だとネイティブイメージにビルドするのに、1回あたり
50秒くらいかかります。
代替のメソッドを作成する
というわけで、この部分をなんとかしていきましょう。
Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: org.osgi.framework.FrameworkUtil. To diagnose the issue you can use the --allow-incomplete-classpath option. The missing type is then reported at run time when it is accessed the first time. Detailed message: Trace: at parsing org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) Call path from entry point to org.xnio.Xnio$OsgiSupport.doGetOsgiService(): at org.xnio.Xnio$OsgiSupport.doGetOsgiService(Xnio.java:276) at org.xnio.Xnio.doGetInstance(Xnio.java:261) at org.xnio.Xnio.getInstance(Xnio.java:187) at io.undertow.Undertow.start(Undertow.java:118) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.start(UndertowJaxrsServer.java:281) at org.littlewings.graal.resteay.App.main(App.java:20) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:153) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
今回エラーになっているのは、XNIOのこの部分なのですが
try { Xnio xnio = OsgiSupport.doGetOsgiService(); if (xnio != null) { return xnio; } } catch (NoClassDefFoundError t) { // Ignore } catch (Throwable t) { msg.debugf(t, "Not using OSGi service"); } throw msg.noProviderFound(); } static class OsgiSupport { static Xnio doGetOsgiService() { Bundle bundle = FrameworkUtil.getBundle(Xnio.class); BundleContext context = bundle.getBundleContext(); if (context == null) { throw new IllegalStateException("Bundle not started"); } ServiceReference<Xnio> sr = context.getServiceReference(Xnio.class); if (sr == null) { return null; } return context.getService(sr); } }
今回、OSGiは使わないので無視したいところです。ここで、この代替となる処理を書いていきます。
まずは、pom.xmlに以下の依存関係をprovidedスコープで追加します。
<dependency> <groupId>com.oracle.substratevm</groupId> <artifactId>svm</artifactId> <version>1.0.0-rc16</version> <scope>provided</scope> </dependency>
そして、以下のようなクラスを作成します。
src/main/java/org/littlewings/graal/nativeimage/RestEasyUndertowSubstitutions.java
package org.littlewings.graal.nativeimage; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import org.xnio.Xnio; public class RestEasyUndertowSubstitutions { } @TargetClass(className = "org.xnio.Xnio$OsgiSupport") final class Target_org_xnio_Xnio_OsgiSupport { @Substitute static Xnio doGetOsgiService() { return null; } }
@TargetClassアノテーションには、Classクラス、可視性の都合でアクセスできないクラスは文字列で渡し、@Substituteアノテーションを
使うことでその代替処理を書くことができます。
以下の例だと、org.xnio.Xnio$OsgiSupportクラスの、staticメソッドdoGetOsgiServiceを置き換えています。
@TargetClass(className = "org.xnio.Xnio$OsgiSupport") final class Target_org_xnio_Xnio_OsgiSupport { @Substitute static Xnio doGetOsgiService() { return null; } }
今回はこのメソッドの結果は使わないので、nullを返すことにしました。
ちなみに、作成したクラスにはfinal修飾子が付いていますが、これを外すとエラーになります…。
Error: Annotated class must be final: class org.littlewings.graal.nativeimage.Target_org_xnio_Xnio_OsgiSupport
で、ビルド…まだまだエラーは出ます…。
Error: Unsupported features in 5 methods Detailed message: Error: Class initialization failed: io.undertow.protocols.alpn.JettyAlpnProvider$Impl Original exception that caused the problem: java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider at sun.misc.Unsafe.ensureClassInitialized(Native Method) at com.oracle.svm.hosted.classinitialization.CommonClassInitializationSupport.ensureClassInitialized(CommonClassInitializationSupport.java:127) at com.oracle.svm.hosted.classinitialization.EagerClassInitialization.computeInitKindAndMaybeInitializeClass(EagerClassInitialization.java:66) at com.oracle.svm.hosted.classinitialization.CommonClassInitializationSupport.computeInitKindAndMaybeInitializeClass(CommonClassInitializationSupport.java:84) at com.oracle.svm.hosted.classinitialization.CommonClassInitializationSupport.maybeInitializeHosted(CommonClassInitializationSupport.java:113) at com.oracle.svm.hosted.SVMHost.registerType(SVMHost.java:179) at com.oracle.graal.pointsto.meta.AnalysisUniverse.createType(AnalysisUniverse.java:263) at com.oracle.graal.pointsto.meta.AnalysisUniverse.lookupAllowUnresolved(AnalysisUniverse.java:204) at com.oracle.graal.pointsto.meta.AnalysisUniverse.lookup(AnalysisUniverse.java:181) at com.oracle.graal.pointsto.meta.AnalysisMethod.getDeclaringClass(AnalysisMethod.java:336) at com.oracle.graal.pointsto.meta.AnalysisMethod.<init>(AnalysisMethod.java:115) at com.oracle.graal.pointsto.meta.AnalysisUniverse.createMethod(AnalysisUniverse.java:411) at com.oracle.graal.pointsto.meta.AnalysisUniverse.lookupAllowUnresolved(AnalysisUniverse.java:399) at com.oracle.graal.pointsto.infrastructure.WrappedConstantPool.lookupMethod(WrappedConstantPool.java:115) at org.graalvm.compiler.java.BytecodeParser.lookupMethod(BytecodeParser.java:4272) at org.graalvm.compiler.java.BytecodeParser.genInvokeStatic(BytecodeParser.java:1506) at org.graalvm.compiler.java.BytecodeParser.processBytecode(BytecodeParser.java:5257) at org.graalvm.compiler.java.BytecodeParser.iterateBytecodesForBlock(BytecodeParser.java:3416) at org.graalvm.compiler.java.BytecodeParser.processBlock(BytecodeParser.java:3223) at org.graalvm.compiler.java.BytecodeParser.build(BytecodeParser.java:944) at org.graalvm.compiler.java.BytecodeParser.buildRootMethod(BytecodeParser.java:838) at org.graalvm.compiler.java.GraphBuilderPhase$Instance.run(GraphBuilderPhase.java:84) at org.graalvm.compiler.phases.Phase.run(Phase.java:49) at org.graalvm.compiler.phases.BasePhase.apply(BasePhase.java:197) at org.graalvm.compiler.phases.Phase.apply(Phase.java:42) at org.graalvm.compiler.phases.Phase.apply(Phase.java:38) at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:211) at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:330) at com.oracle.graal.pointsto.flow.MethodTypeFlow.doParse(MethodTypeFlow.java:310) at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureParsed(MethodTypeFlow.java:300) at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107) at com.oracle.graal.pointsto.DefaultAnalysisPolicy$DefaultVirtualInvokeTypeFlow.onObservedUpdate(DefaultAnalysisPolicy.java:191) at com.oracle.graal.pointsto.flow.TypeFlow.notifyObservers(TypeFlow.java:352) at com.oracle.graal.pointsto.flow.TypeFlow.update(TypeFlow.java:394) at com.oracle.graal.pointsto.BigBang$2.run(BigBang.java:509) at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$execute$0(CompletionExecutor.java:171) at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.alpn.ALPN$Provider at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 41 more Error: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved method during parsing: io.undertow.protocols.alpn.JettyAlpnProvider$Impl.setProtocols(javax.net.ssl.SSLEngine, java.lang.String[]). To diagnose the issue you can use the --allow-incomplete-classpath option. The missing method is then reported at run time when it is accessed the first time. Trace: at parsing io.undertow.protocols.alpn.JettyAlpnProvider.setProtocols(JettyAlpnProvider.java:62) Call path from entry point to io.undertow.protocols.alpn.JettyAlpnProvider.setProtocols(SSLEngine, String[]): at io.undertow.protocols.alpn.JettyAlpnProvider.setProtocols(JettyAlpnProvider.java:62) at io.undertow.server.protocol.http.AlpnOpenListener$1$1.run(AlpnOpenListener.java:294) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(AjpServerRequestConduit.java:195) Call path from entry point to io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(ByteBuffer): at io.undertow.server.protocol.ajp.AjpServerRequestConduit.read(AjpServerRequestConduit.java:183) at org.xnio.conduits.ConduitStreamSourceChannel.read(ConduitStreamSourceChannel.java:127) at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:158) at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136) at io.undertow.server.protocol.http.HttpReadListener.run(HttpReadListener.java:401) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(AjpServerResponseConduit.java:404) Call path from entry point to io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(): at io.undertow.server.protocol.ajp.AjpServerResponseConduit.flush(AjpServerResponseConduit.java:402) at org.xnio.conduits.ConduitStreamSinkChannel.flush(ConduitStreamSinkChannel.java:162) at io.undertow.server.protocol.framed.AbstractFramedChannel.flushSenders(AbstractFramedChannel.java:609) at io.undertow.server.protocol.framed.AbstractFramedChannel$5.run(AbstractFramedChannel.java:724) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing org.xnio.channels.Channels.drain(Channels.java:817) Call path from entry point to org.xnio.channels.Channels.drain(StreamSourceChannel, long): at org.xnio.channels.Channels.drain(Channels.java:801) at io.undertow.server.HttpServerExchange.endExchange(HttpServerExchange.java:1646) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:381) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0) Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception Error: Image build request failed with exit status 1
こちらについては、ALPNは今回使わないので代替処理を書くようにします。
Detailed message: Error: Class initialization failed: io.undertow.protocols.alpn.JettyAlpnProvider$Impl Original exception that caused the problem: java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider at sun.misc.Unsafe.ensureClassInitialized(Native Method)
先ほど作成した、RestEasyUndertowSubstitutionsクラスのファイルの中に、以下を追加。
@TargetClass(io.undertow.protocols.alpn.JettyAlpnProvider.class) final class Target_io_undertow_protocols_alpn_JettyAlpnProvider { @Substitute public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { // no-op return null; } }
遅延初期化する
こういうものについては、クラスの初期化を遅延させてあげる必要があるようです。
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option --delay-class-initialization-to-runtime=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point. Trace: at parsing org.xnio.channels.Channels.drain(Channels.java:817) Call path from entry point to org.xnio.channels.Channels.drain(StreamSourceChannel, long): at org.xnio.channels.Channels.drain(Channels.java:801) at io.undertow.server.HttpServerExchange.endExchange(HttpServerExchange.java:1646) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:381) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144) at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89) at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145) at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
エラーメッセージとして、「--delay-class-initialization-to-runtime=<class-name>」が出力されているものですね。
pom.xmlに、以下のように必要な分だけ「--delay-class-initialization-to-runtime」オプションとクラス名のペアを追加します。
<buildArgs>--no-fallback --delay-class-initialization-to-runtime=io.undertow.server.protocol.ajp.AjpServerRequestConduit --delay-class-initialization-to-runtime=io.undertow.server.protocol.ajp.AjpServerResponseConduit --delay-class-initialization-to-runtime=org.xnio.channels.Channels</buildArgs>
すると、ビルドが通るようになります。
ちょっとこういう警告が出るのですが、今回は無視します…。
WARNING: Could not register reflection metadata for io.undertow.protocols.alpn.JettyAlpnProvider. Reason: java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$ClientProvider.
実行してみましょう。
$ target/resteasy-undertow Exception in thread "main" java.lang.IllegalArgumentException: UT010010: Servlet of type class org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher must have a default constructor at io.undertow.servlet.api.ServletInfo.<init>(ServletInfo.java:85) at io.undertow.servlet.Servlets.servlet(Servlets.java:102) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.undertowDeployment(UndertowJaxrsServer.java:66) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.undertowDeployment(UndertowJaxrsServer.java:90) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:164) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:157) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:145) at org.littlewings.graal.resteay.App.main(App.java:16)
エラーになりました…。どうやら、コンストラクタがわからないようです。
リフレクションの情報を登録する
ネイティブイメージを作る際には、リフレクションを使ったコードは置き換えられることになりますが、ある程度ビルド時に
解析してはくれるようです。
graal/REFLECTION.md at vm-1.0.0-rc16 · oracle/graal · GitHub
ですが、コード解析でわからないものは、登録が必要になります。
登録には、設定ファイルで行う方法とAPIで行う方法があります。
参考にしたサイトは設定ファイルを作成し、「native-image」コマンドのオプション(「-H:ReflectionConfigurationResources」)
として設定していますが、今回はAPIを使って登録してみます。
import文を追加。
package org.littlewings.graal.nativeimage; import javax.net.ssl.SSLEngine; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; import org.xnio.Xnio; public class RestEasyUndertowSubstitutions { }
以下のように、対象のコンストラクタを登録します。
@AutomaticFeature class RuntimeReflectionRegistrationFeature implements Feature { @Override public void beforeAnalysis(Feature.BeforeAnalysisAccess access) { try { RuntimeReflection.register(HttpServlet30Dispatcher.class.getDeclaredConstructor()); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }
このように、対象となるメソッドなどを登録していきます。
今回は、最終的にこのようになりました。
import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import io.undertow.server.protocol.http.HttpRequestParser$$generated; import io.undertow.servlet.handlers.DefaultServlet; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; import org.xnio.OptionMap; import org.xnio.Xnio; // 省略 @AutomaticFeature class RuntimeReflectionRegistrationFeature implements Feature { @Override public void beforeAnalysis(Feature.BeforeAnalysisAccess access) { try { RuntimeReflection.register(HelloResource.class.getDeclaredConstructor()); RuntimeReflection.register(HelloResource.class.getDeclaredMethod("message")); RuntimeReflection.register(HttpServlet30Dispatcher.class.getDeclaredConstructor()); RuntimeReflection.register(DefaultServlet.class.getDeclaredConstructor()); RuntimeReflection.register(HttpRequestParser$$generated.class); RuntimeReflection.register(HttpRequestParser$$generated.class.getDeclaredConstructor(OptionMap.class)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }
自分が作成したクラスやメソッドも登録しないと、今回のケースではJAX-RSリソースとして機能しませんでした…。
RuntimeReflection.register(HelloResource.class.getDeclaredConstructor()); RuntimeReflection.register(HelloResource.class.getDeclaredMethod("message"));
スタックトレースの足跡。
$ target/resteasy-undertow Exception in thread "main" java.lang.RuntimeException: RESTEASY003190: Could not find constructor for class: org.littlewings.graal.resteay.HelloResource at org.jboss.resteasy.spi.metadata.ResourceBuilder.getConstructor(ResourceBuilder.java:805) at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.registered(POJOResourceFactory.java:56) at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:213) at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:199) at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:185) at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:162) at org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:79) at org.jboss.resteasy.spi.ResteasyDeployment.registration(ResteasyDeployment.java:499) at org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:282) at org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:89) at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:119) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:36) at io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:117) at io.undertow.servlet.core.ManagedServlet$DefaultInstanceStrategy.start(ManagedServlet.java:303) at io.undertow.servlet.core.ManagedServlet.createServlet(ManagedServlet.java:143) at io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:583) at io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:554) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at io.undertow.servlet.core.DeploymentManagerImpl.start(DeploymentManagerImpl.java:596) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:269) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:183) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:157) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:145) at org.littlewings.graal.resteay.App.main(App.java:16) $ target/resteasy-undertow Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassNotFoundException: io.undertow.server.protocol.http.HttpRequestParser$$generated at io.undertow.Undertow.start(Undertow.java:249) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.start(UndertowJaxrsServer.java:281) at org.littlewings.graal.resteay.App.main(App.java:18) Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: io.undertow.server.protocol.http.HttpRequestParser$$generated at io.undertow.server.protocol.http.HttpRequestParser.instance(HttpRequestParser.java:221) at io.undertow.server.protocol.http.HttpOpenListener.<init>(HttpOpenListener.java:93) at io.undertow.Undertow.start(Undertow.java:179) ... 2 more Caused by: java.lang.ClassNotFoundException: io.undertow.server.protocol.http.HttpRequestParser$$generated at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51) at java.lang.Class.forName(DynamicHub.java:1143) at io.undertow.server.protocol.http.HttpRequestParser.instance(HttpRequestParser.java:216) ... 4 more
脱線:JAXP
で、これでネイティブイメージを作成してビルドしてみると、今度はXerces関係で怒られます。
Caused by: java.lang.ClassNotFoundException: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
こんな感じに。
$ target/resteasy-undertow Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: RESTEASY003940: Unable to instantiate MessageBodyReader at org.jboss.resteasy.plugins.providers.RegisterBuiltin.register(RegisterBuiltin.java:49) at org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:262) at org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:89) at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:119) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:36) at io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:117) at io.undertow.servlet.core.ManagedServlet$DefaultInstanceStrategy.start(ManagedServlet.java:303) at io.undertow.servlet.core.ManagedServlet.createServlet(ManagedServlet.java:143) at io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:583) at io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:554) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at io.undertow.servlet.core.DeploymentManagerImpl.start(DeploymentManagerImpl.java:596) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:269) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:183) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:157) at org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer.deploy(UndertowJaxrsServer.java:145) at org.littlewings.graal.resteay.App.main(App.java:16) Caused by: java.lang.RuntimeException: RESTEASY003940: Unable to instantiate MessageBodyReader at org.jboss.resteasy.spi.ResteasyProviderFactory.registerProvider(ResteasyProviderFactory.java:1761) at org.jboss.resteasy.spi.ResteasyProviderFactory.registerProvider(ResteasyProviderFactory.java:1684) at org.jboss.resteasy.plugins.providers.RegisterBuiltin.registerProviders(RegisterBuiltin.java:133) at org.jboss.resteasy.plugins.providers.RegisterBuiltin.register(RegisterBuiltin.java:45) ... 17 more Caused by: java.lang.RuntimeException: RESTEASY003325: Failed to construct public org.jboss.resteasy.plugins.providers.DocumentProvider() at org.jboss.resteasy.core.ConstructorInjectorImpl.construct(ConstructorInjectorImpl.java:164) at org.jboss.resteasy.spi.ResteasyProviderFactory.createProviderInstance(ResteasyProviderFactory.java:2750) at org.jboss.resteasy.spi.ResteasyProviderFactory.addMessageBodyReader(ResteasyProviderFactory.java:1019) at org.jboss.resteasy.spi.ResteasyProviderFactory.registerProvider(ResteasyProviderFactory.java:1756) ... 20 more Caused by: javax.xml.parsers.FactoryConfigurationError: Provider com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl not found at javax.xml.parsers.FactoryFinder.newInstance(FactoryFinder.java:200) at javax.xml.parsers.FactoryFinder.newInstance(FactoryFinder.java:152) at javax.xml.parsers.FactoryFinder.find(FactoryFinder.java:277) at javax.xml.parsers.DocumentBuilderFactory.newInstance(DocumentBuilderFactory.java:120) at org.jboss.resteasy.plugins.providers.DocumentProvider.<init>(DocumentProvider.java:57) at org.jboss.resteasy.plugins.providers.DocumentProvider.<init>(DocumentProvider.java:51) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.jboss.resteasy.core.ConstructorInjectorImpl.construct(ConstructorInjectorImpl.java:152) ... 23 more Caused by: java.lang.ClassNotFoundException: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51) at java.lang.Class.forName(DynamicHub.java:1143) at javax.xml.parsers.FactoryFinder.getProviderClass(FactoryFinder.java:124) at javax.xml.parsers.FactoryFinder.newInstance(FactoryFinder.java:188) ... 30 more
以下の部分で、フォールバックしたデフォルトクラスがわからないようです。
public DocumentProvider(final @Context ResteasyConfiguration config) { LogMessages.LOGGER.debugf("Provider : %s, Method : DocumentProvider", getClass().getName()); this.documentBuilder = DocumentBuilderFactory.newInstance(); this.transformerFactory = TransformerFactory.newInstance();
public static DocumentBuilderFactory newInstance() { return (DocumentBuilderFactory)FactoryFinder.find(DocumentBuilderFactory.class, "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); }
もう面倒になったので、以下のように置き換えました。
@TargetClass(javax.xml.parsers.DocumentBuilderFactory.class) final class Target_javax_xml_parsers_DocumentBuilderFactory { @Substitute public static DocumentBuilderFactory newInstance() { return new DocumentBuilderFactoryImpl(); } } @TargetClass(javax.xml.transform.TransformerFactory.class) final class Target_javax_xml_transform_TransformerFactory { @Substitute public static TransformerFactory newInstance() throws TransformerFactoryConfigurationError { return new TransformerFactoryImpl(); } }
これでビルドすると、今度は起動するようになります。
$ target/resteasy-undertow
やっと動きました!
$ curl localhost:8080/hello Hello RESTEasy!!
ログが出ていない?
ところで、この起動したネイティブイメージ、Javaアプリケーションの時やJavaが必要なフォールバックイメージの時と
比べると、標準出力にログが出なくなりました。
どうしたんでしょう?
ここで使われているのは、JBoss Logging越しのJavaの標準のLogger(JUL)です。
デフォルトの設定内容を見てみましょう。
$ grep -v '#' /usr/local/graalvm-ce/jre/lib/logging.properties handlers= java.util.logging.ConsoleHandler .level= INFO java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter com.xyz.foo.level = SEVERE
この内容を失ったみたいですね…。
仕方がないので、今回はアプリケーション内でRoot Loggerを作成してあげることにします。
package org.littlewings.graal.nativeimage; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import io.undertow.Undertow; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.spi.ResteasyDeployment; public class App { public static void main(String... args) { ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.INFO); consoleHandler.setFormatter(new SimpleFormatter()); Logger.getLogger("").addHandler(consoleHandler); UndertowJaxrsServer server = new UndertowJaxrsServer(); ResteasyDeployment deployment = new ResteasyDeployment(); deployment.setApplication(new JaxrsActivator()); server.deploy(deployment); server.start(Undertow.builder().addHttpListener(8080, "0.0.0.0")); } }
再度ビルド。
これで、今度はログが出るようになりました…。
$ target/resteasy-undertow Apr 27, 2019 10:04:53 PM org.jboss.resteasy.spi.ResteasyDeployment processApplication INFO: RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.graal.nativeimage.JaxrsActivator Apr 27, 2019 10:04:53 PM org.jboss.resteasy.spi.ResteasyDeployment processApplication INFO: RESTEASY002200: Adding class resource org.littlewings.graal.nativeimage.HelloResource from Application class org.littlewings.graal.nativeimage.JaxrsActivator
遠かった…。
まとめ
GraalVM(Substrate VM)を使ってネイティブイメージを作る時には、リフレクションや動的ロードに関する部分など、
その制限事項を回避したり置き換えたりするための細工をいろいろしないといけないことがよくわかりました。
graal/LIMITATIONS.md at vm-1.0.0-rc16 · oracle/graal · GitHub
いやぁ、大変でしたが、触った部分の話はまあまあわかったので、よしとしましょう。
ちなみに、1回のビルドにかかる時間とその内訳は、こんな感じです。
Build on Server(pid: 14110, port: 36203) [resteasy-undertow:14110] classlist: 2,017.46 ms [resteasy-undertow:14110] (cap): 761.32 ms [resteasy-undertow:14110] setup: 1,025.83 ms WARNING: Could not register reflection metadata for io.undertow.protocols.alpn.JettyAlpnProvider. Reason: java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$ClientProvider. WARNING: Could not register reflection metadata for io.undertow.protocols.alpn.JettyAlpnProvider. Reason: java.lang.NoClassDefFoundError: io/undertow/protocols/alpn/JettyAlpnProvider$ALPNClientSelectionProvider. [resteasy-undertow:14110] (typeflow): 12,887.02 ms [resteasy-undertow:14110] (objects): 14,266.38 ms [resteasy-undertow:14110] (features): 1,301.23 ms [resteasy-undertow:14110] analysis: 29,128.81 ms [resteasy-undertow:14110] universe: 431.87 ms [resteasy-undertow:14110] (parse): 1,647.91 ms [resteasy-undertow:14110] (inline): 3,288.45 ms [resteasy-undertow:14110] (compile): 8,829.37 ms [resteasy-undertow:14110] compile: 14,800.83 ms [resteasy-undertow:14110] image: 1,704.56 ms [resteasy-undertow:14110] write: 612.32 ms [resteasy-undertow:14110] [total]: 49,791.81 ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 53.236 s [INFO] Finished at: 2019-04-27T22:03:49+09:00 [INFO] ------------------------------------------------------------------------
オマケ
部分部分で書いてきた、GraalVMに関するコードやpom.xmlの部分を載せておきます。
pom.xmlのNative Image Maven Pluginの部分。
<plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>1.0.0-rc16</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <mainClass>org.littlewings.graal.nativeimage.App</mainClass> <imageName>resteasy-undertow</imageName> <buildArgs>--no-fallback --delay-class-initialization-to-runtime=io.undertow.server.protocol.ajp.AjpServerRequestConduit --delay-class-initialization-to-runtime=io.undertow.server.protocol.ajp.AjpServerResponseConduit --delay-class-initialization-to-runtime=org.xnio.channels.Channels</buildArgs> </configuration> </plugin>
代替機能や、リフレクションに関する情報を登録するクラス。
src/main/java/org/littlewings/graal/nativeimage/RestEasyUndertowSubstitutions.java
package org.littlewings.graal.nativeimage; import javax.net.ssl.SSLEngine; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl; import io.undertow.server.protocol.http.HttpRequestParser$$generated; import io.undertow.servlet.handlers.DefaultServlet; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; import org.xnio.OptionMap; import org.xnio.Xnio; public class RestEasyUndertowSubstitutions { } @TargetClass(className = "org.xnio.Xnio$OsgiSupport") final class Target_org_xnio_Xnio_OsgiSupport { @Substitute static Xnio doGetOsgiService() { return null; } } @TargetClass(io.undertow.protocols.alpn.JettyAlpnProvider.class) final class Target_io_undertow_protocols_alpn_JettyAlpnProvider { @Substitute public SSLEngine setProtocols(SSLEngine engine, String[] protocols) { // no-op return null; } } @TargetClass(javax.xml.parsers.DocumentBuilderFactory.class) final class Target_javax_xml_parsers_DocumentBuilderFactory { @Substitute public static DocumentBuilderFactory newInstance() { return new DocumentBuilderFactoryImpl(); } } @TargetClass(javax.xml.transform.TransformerFactory.class) final class Target_javax_xml_transform_TransformerFactory { @Substitute public static TransformerFactory newInstance() throws TransformerFactoryConfigurationError { return new TransformerFactoryImpl(); } } @AutomaticFeature class RuntimeReflectionRegistrationFeature implements Feature { @Override public void beforeAnalysis(Feature.BeforeAnalysisAccess access) { try { RuntimeReflection.register(HelloResource.class.getDeclaredConstructor()); RuntimeReflection.register(HelloResource.class.getDeclaredMethod("message")); RuntimeReflection.register(HttpServlet30Dispatcher.class.getDeclaredConstructor()); RuntimeReflection.register(DefaultServlet.class.getDeclaredConstructor()); RuntimeReflection.register(HttpRequestParser$$generated.class); RuntimeReflection.register(HttpRequestParser$$generated.class.getDeclaredConstructor(OptionMap.class)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }