Quarkus入門
GlassFish Users Group Japan 勉強会 2019 SpringでQuarkusについて紹介しました。話そうと思っていたけれどもスライドに入らなかったことをブログで補足してみます。
Quarkusとは何か
とにかく起動が高速な Java EE / MicroProfile ベースのAPIでコードが書けるフレームワークです。スライド中でも言及していますが、GraalVMのnative-imageコマンドによってネイティブコンパイルすると100ミリを切るスピードでJAX-RS/CDI/JPAで構成されるCRUDアプリケーションが起動します。
Javaは起動が遅い、Goのようなシングルバイナリによる起動の方が便利でJavaは手間が掛かると言われる課題を解決しています。
続きについてはスライドを参照してください。ここからは、スライドに入らなかった内容や、当日話そうと思っていたけど忘れていた内容をまとめます。
Quarkusがビルド時に自動生成するクラス
mvn clean package
すると、target/wiring-classes
にQuarkusがビルド時にクラスファイルを展開してuber-jarの中に含めるような動きをします。JAX-RS/CDI/JPAのサンプルアプリケーションの場合、アプリケーションのパッケージsample.quarkus.jpajaxrscdijta
だけでなく、ランタイム自体のクラスファイルが生成されている様子がわかります。
$ tree target/wiring-classes target/wiring-classes ├── META-INF │ ├── build-config.properties │ ├── quarkus-default-config.properties │ └── services │ ├── io.quarkus.arc.ComponentsProvider │ └── io.quarkus.arc.ResourceReferenceProvider ├── io │ └── quarkus │ ├── agroal │ │ └── runtime │ │ ├── DataSourceProducer.class │ │ ├── DataSourceProducer_Bean$$function$$6.class │ │ ├── DataSourceProducer_Bean.class │ │ ├── DataSourceProducer_ClientProxy.class │ │ └── DataSourceProducer_ProducerMethod_createDefaultDataSource_7c487e3ef869f878aa871e917c94f4d26d5d5c56_Bean.class │ ├── arc │ │ ├── ActivateRequestContextInterceptor_Bean.class │ │ ├── runtime │ │ │ └── LifecycleEventRunner_Bean.class │ │ ├── runtimebean │ │ │ └── RuntimeBeanProducers.class │ │ └── setup │ │ └── Default_ComponentsProvider.class │ ├── deployment │ │ └── steps │ │ ├── AgroalProcessor$build3.class │ │ ├── AgroalProcessor$configureRuntimeProperties6.class │ │ ├── ArcAnnotationProcessor$build10.class │ │ ├── ConfigBuildStep$validateConfigProperties11.class │ │ ├── HibernateOrmProcessor$build8.class │ │ ├── HibernateOrmProcessor$build9.class │ │ ├── HibernateOrmProcessor$startPersistenceUnits14.class │ │ ├── LifecycleEventsBuildStep$startupEvent17.class │ │ ├── LoggingResourceProcessor$setupLoggingRuntimeInit5.class │ │ ├── LoggingResourceProcessor$setupLoggingStaticInit1.class │ │ ├── NarayanaJtaProcessor$build4.class │ │ ├── ResteasyScanningProcessor$setupInjection12.class │ │ ├── RuntimeBeanProcessor$build2.class │ │ ├── ThreadPoolSetup$createExecutor7.class │ │ ├── UndertowArcIntegrationBuildStep$integrateRequestContext13.class │ │ ├── UndertowBuildStep$boot16.class │ │ └── UndertowBuildStep$build15.class │ ├── hibernate │ │ └── orm │ │ ├── panache │ │ │ └── PanacheEntity.class │ │ └── runtime │ │ ├── DefaultEntityManagerProducer_Bean.class │ │ ├── DefaultEntityManagerProducer_ProducerField_entityManager_Bean.class │ │ ├── JPAConfig_Bean.class │ │ ├── RequestScopedEntityManagerHolder_Bean$$function$$7.class │ │ ├── RequestScopedEntityManagerHolder_Bean.class │ │ ├── RequestScopedEntityManagerHolder_ClientProxy.class │ │ └── TransactionEntityManagers_Bean.class │ ├── narayana │ │ └── jta │ │ └── runtime │ │ ├── NarayanaJtaProducers_Bean.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_Bean$$function$$8.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_Bean.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionManager_9989455b3b53ac81c17ca945c636473b7202fe4e_ClientProxy.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_Bean$$function$$9.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_Bean.class │ │ ├── NarayanaJtaProducers_ProducerMethod_transactionSynchronizationRegistry_ad29dd72d7aa0c9be8f98e90052c29fc262ea31a_ClientProxy.class │ │ └── interceptor │ │ ├── TransactionalInterceptorMandatory_Bean.class │ │ ├── TransactionalInterceptorNever_Bean.class │ │ ├── TransactionalInterceptorNotSupported_Bean.class │ │ ├── TransactionalInterceptorRequired_Bean.class │ │ ├── TransactionalInterceptorRequiresNew_Bean.class │ │ └── TransactionalInterceptorSupports_Bean.class │ ├── runner │ │ ├── ApplicationImpl1.class │ │ ├── AutoFeature.class │ │ └── GeneratedMain.class │ └── runtime │ └── generated │ ├── BuildTimeConfig.class │ ├── BuildTimeConfigRoot.class │ ├── RunTimeConfig.class │ ├── RunTimeConfigRoot.class │ └── RunTimeDefaultConfigSource.class ├── javax │ ├── enterprise │ │ ├── context │ │ │ └── control │ │ │ └── ActivateRequestContext_Shared_AnnotationLiteral.class │ │ └── inject │ │ └── Produces_Shared_AnnotationLiteral.class │ ├── persistence │ │ └── PersistenceContext_Shared_AnnotationLiteral.class │ └── transaction │ └── Transactional_Shared_AnnotationLiteral.class └── sample └── quarkus └── jpajaxrscdijta ├── EmployeeResource_Bean.class ├── EmployeeService_Bean$$function$$1.class ├── EmployeeService_Bean.class ├── EmployeeService_ClientProxy.class ├── EmployeeService_Subclass$$function$$2.class ├── EmployeeService_Subclass$$function$$3.class ├── EmployeeService_Subclass$$function$$4.class ├── EmployeeService_Subclass$$function$$5.class └── EmployeeService_Subclass.class
スライド中で言及している以下のようなコードの@Injectの解決の場合、
package sample.quarkus. jpajaxrscdijta; @Path("/") public class EmployeeResource { @Inject private EmployeeService service;
wiring-classes/sample/quarkus/jpajaxrscdijta/EmployeeResource_Bean.class
にインジェクション実装が含まれており、putfield命令でシンプルにフィールド名service
に値を設定するクラスファイルが自動生成されている様子が確認できます。
javap -v wiring-classes/sample/quarkus/jpajaxrscdijta/EmployeeResource_Bean.class ... public sample.quarkus.jpajaxrscdijta.EmployeeResource create(javax.enterprise.context.spi.CreationalContext); descriptor: (Ljavax/enterprise/context/spi/CreationalContext;)Lsample/quarkus/jpajaxrscdijta/EmployeeResource; flags: ACC_PUBLIC ... 30: aload_3 31: checkcast #57 // class sample/quarkus/jpajaxrscdijta/EmployeeService 34: putfield #61 // Field sample/quarkus/jpajaxrscdijta/EmployeeResource.service:Lsample/quarkus/jpajaxrscdijta/EmployeeService;
これらのクラスをデプロイしてから動的バイトコード生成で行うのではなく、事前に可能な処理はビルド時に実行するため、GraalVMによってネイティブバイナリを生成しなくても、java -jarでも従来のThorntail/WildFlyと比較して高速に起動しています。
Quarkusでスレッドダンプ
GraalVM Community Editionのnative-imageコマンドで生成された実行バイナリでは、kill -3 (SIGQUIT) が投げられるとHotSpotJVMと異なりプロセスが終了します。
Quarkusではシグナルハンドラを独自に実装しており、kill -3を実行プロセスに投げるとコンソールにスレッドダンプを出力します。内部的にはsun.misc.SignalHandler
を使って実装しています。
2019-05-21T14:22:58.915Z Thread dump follows: "XNIO-1 Accept" #22 prio=5 tid=0x7f6cb77e9700 java.lang.thread.State: RUNNABLE at com.oracle.svm.core.posix.headers.linux.LinuxEPoll.epoll_wait(LinuxEPoll.java) at sun.nio.ch.EPollArrayWrapper.epollWait(EPollArrayWrapper.java:326) at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93) at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97) at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101) at org.xnio.nio.WorkerThread.run(WorkerThread.java:532) at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:473) at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193) ...
ヒープダンプはGraalVM Enterprise EditionによりGraalVMレイヤで実現されています。無償で利用可能なGraalVM Community Editionでは現状ヒープダンプを取得することはできません。