--- title: Spring WebFlux.fnハンズオン - X. GraalVMのSubstrateVMでNative Imageにコンパイル tags: ["Spring WebFlux.fn Handson", "Reactor", "Reactor Netty", "Netty", "Spring 5", "Spring WebFlux", "Spring Boot", "Java", "Cloud Foundry", "Pivotal Web Services", "Pivotal Cloud Foundry", "GraalVM"] categories: ["Programming", "Java", "org", "springframework", "web", "reactive"] date: 2019-09-05T09:10:06Z updated: 1970-01-01T00:00:00Z --- 本ハンズオンで、次の図のような簡易家計簿のAPIサーバーをSpring WebFlux.fnを使って実装します。 あえてSpring BootもDependency Injectionも使わないシンプルなWebアプリとして実装します。 **ハンズオンコンテンツ** 1. [はじめに](/entries/500) 1. [簡易家計簿Moneygerプロジェクトの作成](/entries/501) 1. [YAVIによるValidationの実装](/entries/502) 1. [R2DBCによるデータベースアクセス](/entries/503) 1. [Web UIの追加](/entries/504) 1. [例外ハンドリングの改善](/entries/505) 1. [収入APIの実装](/entries/506) 1. [Spring Bootアプリに変換](/entries/507) 1. [GraalVMのSubstrateVMでNative Imageにコンパイル](/entries/510) 👈 > この章ではSpring Bootアプリに**変換前**の状態で行なってください。 かなりチャレンジングですが、ここまで作ったアプリをGraalVMのSubstrateVMでNative Imageにコンパイルしてみます。 > ⚠️ 2019-09-05時点で、GraalVM Version 19.2.0 CEで動作確認していますが、
> 今度も動作する保証はありません。"とりあえず動いた"レベルの内容なのであまり信用しないでください。
> 後々[spring-graal-feature](https://github.com/spring-projects-experimental/spring-graal-feature)を使う用に変更する予定です。 [vanilla-spring-webflux-fn-blank](https://github.com/making/vanilla-spring-webflux-fn-blank)の`0.2.15`でプロジェクトを作成した場合はあらかじめNative Imageビルド用の設定が用意されています。 **目次** ### App.javaの修正 `Jackson2ObjectMapperBuilder`デフォルトでは代表的な`com.fasterxml.jackson.databind.Module`がクラスパス上にいると自動で登録する。 リフレクションを使ってチェックをしており、Native Imageではデフォルトでは無視されます。 今回は`com.fasterxml.jackson.datatype.jsr310.JavaTimeModule`を使用したいだけなので、`modules`メソッドで明示的に登録します。 ```java public static HandlerStrategies handlerStrategies() { return HandlerStrategies.empty() .codecs(configure -> { configure.registerDefaults(true); ServerCodecConfigurer.ServerDefaultCodecs defaults = configure .defaultCodecs(); ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() .dateFormat(new StdDateFormat()) .modules(new JavaTimeModule()) // <-- ここを追加 .build(); defaults.jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper)); defaults.jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper)); }) .exceptionHandler(new ErrorResponseExceptionHandler()) .build(); } ``` > ちなみに、`modules`メソッドで明示的に登録する代わりに、設定ファイルでリフレクションを有効にすることもできます。
> その場合は`src/main/resources/META-INF/native-image/com.example.moneyger/reflect-config.json`のJSON配列に次を追加すれば良いです。 > > ```json > { > "name": "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", > "allDeclaredConstructors": true, > "allDeclaredMethods": true > } > ``` 次に、`ConnectionFactory`の生成方法を変更します。`ConnectionFactories.get`はクラスローダーから`ServiceLoader`で`io.r2dbc.spi.ConnectionFactoryProvider`をロードするが、 これもNative Image上で利かないので、今回は明示的にPostgreSQL用の`PostgresqlConnectionFactory`を直接作成するように変更します。 ```java static ConnectionFactory connectionFactory() { // postgresql://username:password@hostname:5432/dbname String databaseUrl = System.getenv("DATABASE_URL"); final URI uri = URI.create(databaseUrl); final PostgresqlConnectionConfiguration configuration = PostgresqlConnectionConfiguration.builder() .host(uri.getHost()) .port(uri.getPort()) .database(uri.getPath().substring(1)) .username(uri.getUserInfo().split(":", 2)[0]) .password(uri.getUserInfo().split(":", 2)[1]) .build(); return new PostgresqlConnectionFactory(configuration); } ``` ### R2dbcExpenditureRepository.javaの変更 `R2dbcExpenditureRepository`ではSpring Data R2DBCによるオブジェクトマッピングやSQL生成を自動で行なっていました。 リフレクションや動的Proxyが使われているので、ここでは明示的なマッピングを行うように修正します。 ```java package com.example.expenditure; import io.r2dbc.spi.Row; import org.springframework.data.r2dbc.core.DatabaseClient; import org.springframework.transaction.reactive.TransactionalOperator; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.LocalDate; import java.util.function.Function; public class R2dbcExpenditureRepository implements ExpenditureRepository { private final DatabaseClient databaseClient; private final TransactionalOperator tx; @SuppressWarnings("ConstantConditions") private final Function rowExpenditureMapper = row -> new ExpenditureBuilder() .withExpenditureId(row.get("expenditure_id", Integer.class)) .withExpenditureName(row.get("expenditure_name", String.class)) .withQuantity(row.get("quantity", Integer.class)) .withUnitPrice(row.get("unit_price", Integer.class)) .withExpenditureDate(row.get("expenditure_date", LocalDate.class)) .build(); public R2dbcExpenditureRepository(DatabaseClient databaseClient, TransactionalOperator tx) { this.databaseClient = databaseClient; this.tx = tx; } @Override public Flux findAll() { return this.databaseClient.execute("SELECT expenditure_id, expenditure_name, unit_price, quantity, expenditure_date FROM expenditure") .map(this.rowExpenditureMapper) .all(); } @Override public Mono findById(Integer expenditureId) { return this.databaseClient .execute("SELECT expenditure_id, expenditure_name, unit_price, quantity, expenditure_date FROM expenditure WHERE expenditure_id = :expenditure_id") .bind("expenditure_id", expenditureId) .map(this.rowExpenditureMapper) .one(); } @Override public Mono save(Expenditure expenditure) { final String databaseUrl = System.getenv("DATABASE_URL"); if (databaseUrl != null && databaseUrl.contains("postgres") /* TODO 🤔 */) { return this.databaseClient .execute("INSERT INTO expenditure(expenditure_name, unit_price, quantity, expenditure_date) VALUES(:expenditure_name, :unit_price, :quantity, :expenditure_date) RETURNING " + "expenditure_id") .bind("expenditure_name", expenditure.getExpenditureName()) .bind("unit_price", expenditure.getUnitPrice()) .bind("quantity", expenditure.getQuantity()) .bind("expenditure_date", expenditure.getExpenditureDate()) .fetch() .one() .map(map -> new ExpenditureBuilder(expenditure) .withExpenditureId((Integer) map.get("expenditure_id")) .build()) .as(this.tx::transactional); } else { return this.databaseClient .execute("INSERT INTO expenditure(expenditure_name, unit_price, quantity, expenditure_date) VALUES(:expenditure_name, :unit_price, :quantity, :expenditure_date)") .bind("expenditure_name", expenditure.getExpenditureName()) .bind("unit_price", expenditure.getUnitPrice()) .bind("quantity", expenditure.getQuantity()) .bind("expenditure_date", expenditure.getExpenditureDate()) .then() .then(/* TODO 🤔 */ this.databaseClient.execute("CALL SCOPE_IDENTITY()").fetch().one() .map(map -> new ExpenditureBuilder(expenditure) .withExpenditureId(((Long) map.get("SCOPE_IDENTITY()")).intValue()) .build())) .as(this.tx::transactional); } } @Override public Mono deleteById(Integer expenditureId) { return this.databaseClient.execute("DELETE FROM expenditure WHERE expenditure_id = :expenditure_id") .bind("expenditure_id", expenditureId) .then() .as(this.tx::transactional); } } ``` `R2dbcIncomeRepository`も同様に修正します。 ソースコード変更後もJUnitのテストが依然成功することを確認してください。 ### リフレクションの定義 JacksonによるJSONのシリアライズ、デシリアライズはリフレクションで行われます。アプリ側でJSONシリアライズ、デシリアライズ対象となるクラスは `src/main/resources/META-INF/native-image/com.example.moneyger/reflect-config.json`のJSON配列に次のように追加します。 ```json { "name": "com.example.expenditure.Expenditure", "allDeclaredConstructors": true, "allDeclaredMethods": true }, { "name": "com.example.expenditure.ExpenditureBuilder", "allDeclaredConstructors": true, "allDeclaredMethods": true }, { "name": "com.example.income.Income", "allDeclaredConstructors": true, "allDeclaredMethods": true }, { "name": "com.example.income.IncomeBuilder", "allDeclaredConstructors": true, "allDeclaredMethods": true }, { "name": "com.example.error.ErrorResponse", "allDeclaredConstructors": true, "allDeclaredMethods": true } ``` ### Native Imageのビルド [GraalVM 19.2.0](https://github.com/oracle/graal/releases/tag/vm-19.2.0)をインストールしてください。 `JAVA_HOME`を更新した後、次のコマンドを実行して、`native-image` ``` gu install native-image ``` で ``` export DATABASE_URL=postgresql://:@localhost:5432/moneyger mvn clean package -Pgraal -DskipTests=true ``` 次のようにビルドができます。 ``` $ mvn clean package -Pgraal -DskipTests=true [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building moneyger 1.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ moneyger --- [INFO] Deleting /Users/maki/handson/moneyger/target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ moneyger --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 8 resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ moneyger --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 10 source files to /Users/maki/handson/moneyger/target/classes [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ moneyger --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ moneyger --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 2 source files to /Users/maki/handson/moneyger/target/test-classes [INFO] [INFO] --- maven-surefire-plugin:2.22.0:test (default-test) @ moneyger --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ moneyger --- [INFO] Building jar: /Users/maki/handson/moneyger/target/moneyger-1.0.0-SNAPSHOT.jar [INFO] [INFO] --- native-image-maven-plugin:19.2.0:native-image (default) @ moneyger --- [INFO] ImageClasspath Entry: com.github.making:moneyger-ui:jar:master-7d3a8b2729-1:compile (file:///Users/maki/.m2/repository/com/github/making/moneyger-ui/master-7d3a8b2729-1/moneyger-ui-master-7d3a8b2729-1.jar) [INFO] ImageClasspath Entry: org.springframework:spring-context:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-context/5.2.0.BUILD-SNAPSHOT/spring-context-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-aop:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-aop/5.2.0.BUILD-SNAPSHOT/spring-aop-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-beans:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-beans/5.2.0.BUILD-SNAPSHOT/spring-beans-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-core:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-core/5.2.0.BUILD-SNAPSHOT/spring-core-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-jcl:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-jcl/5.2.0.BUILD-SNAPSHOT/spring-jcl-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-expression:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-expression/5.2.0.BUILD-SNAPSHOT/spring-expression-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-webflux:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-webflux/5.2.0.BUILD-SNAPSHOT/spring-webflux-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-web:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-web/5.2.0.BUILD-SNAPSHOT/spring-web-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: io.projectreactor:reactor-core:jar:3.3.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/projectreactor/reactor-core/3.3.0.BUILD-SNAPSHOT/reactor-core-3.3.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.reactivestreams:reactive-streams:jar:1.0.3:compile (file:///Users/maki/.m2/repository/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3.jar) [INFO] ImageClasspath Entry: ch.qos.logback:logback-classic:jar:1.2.3:compile (file:///Users/maki/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar) [INFO] ImageClasspath Entry: ch.qos.logback:logback-core:jar:1.2.3:compile (file:///Users/maki/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar) [INFO] ImageClasspath Entry: org.slf4j:slf4j-api:jar:1.7.28:compile (file:///Users/maki/.m2/repository/org/slf4j/slf4j-api/1.7.28/slf4j-api-1.7.28.jar) [INFO] ImageClasspath Entry: io.projectreactor.netty:reactor-netty:jar:0.9.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/projectreactor/netty/reactor-netty/0.9.0.BUILD-SNAPSHOT/reactor-netty-0.9.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: io.netty:netty-codec-http:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-codec-http/4.1.39.Final/netty-codec-http-4.1.39.Final.jar) [WARNING] jar:file:///Users/maki/.m2/repository/io/netty/netty-codec-http/4.1.39.Final/netty-codec-http-4.1.39.Final.jar!/META-INF/native-image/io.netty/codec-http/native-image.properties does not match recommended META-INF/native-image/${groupId}/${artifactId}/native-image.properties layout. [INFO] ImageClasspath Entry: io.netty:netty-common:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-common/4.1.39.Final/netty-common-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-buffer:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-buffer/4.1.39.Final/netty-buffer-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-transport:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-transport/4.1.39.Final/netty-transport-4.1.39.Final.jar) [WARNING] jar:file:///Users/maki/.m2/repository/io/netty/netty-transport/4.1.39.Final/netty-transport-4.1.39.Final.jar!/META-INF/native-image/io.netty/transport/native-image.properties does not match recommended META-INF/native-image/${groupId}/${artifactId}/native-image.properties layout. [INFO] ImageClasspath Entry: io.netty:netty-resolver:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-resolver/4.1.39.Final/netty-resolver-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-codec:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-codec/4.1.39.Final/netty-codec-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-handler:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-handler/4.1.39.Final/netty-handler-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-handler-proxy:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-handler-proxy/4.1.39.Final/netty-handler-proxy-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.netty:netty-codec-socks:jar:4.1.39.Final:compile (file:///Users/maki/.m2/repository/io/netty/netty-codec-socks/4.1.39.Final/netty-codec-socks-4.1.39.Final.jar) [INFO] ImageClasspath Entry: io.projectreactor.addons:reactor-pool:jar:0.1.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/projectreactor/addons/reactor-pool/0.1.0.BUILD-SNAPSHOT/reactor-pool-0.1.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: com.fasterxml.jackson.core:jackson-databind:jar:2.9.9.3:compile (file:///Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.9.3/jackson-databind-2.9.9.3.jar) [INFO] ImageClasspath Entry: com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile (file:///Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar) [INFO] ImageClasspath Entry: com.fasterxml.jackson.core:jackson-core:jar:2.9.9:compile (file:///Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.9/jackson-core-2.9.9.jar) [INFO] ImageClasspath Entry: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.9:compile (file:///Users/maki/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.9/jackson-datatype-jsr310-2.9.9.jar) [INFO] ImageClasspath Entry: am.ik.yavi:yavi:jar:0.2.2:compile (file:///Users/maki/.m2/repository/am/ik/yavi/yavi/0.2.2/yavi-0.2.2.jar) [INFO] ImageClasspath Entry: org.springframework.data:spring-data-r2dbc:jar:1.0.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/data/spring-data-r2dbc/1.0.0.BUILD-SNAPSHOT/spring-data-r2dbc-1.0.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework.data:spring-data-commons:jar:2.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/data/spring-data-commons/2.2.0.BUILD-SNAPSHOT/spring-data-commons-2.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework.data:spring-data-relational:jar:1.1.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/data/spring-data-relational/1.1.0.BUILD-SNAPSHOT/spring-data-relational-1.1.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-tx:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-tx/5.2.0.BUILD-SNAPSHOT/spring-tx-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: io.r2dbc:r2dbc-spi:jar:0.8.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/r2dbc/r2dbc-spi/0.8.0.BUILD-SNAPSHOT/r2dbc-spi-0.8.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: io.r2dbc:r2dbc-h2:jar:0.8.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/r2dbc/r2dbc-h2/0.8.0.BUILD-SNAPSHOT/r2dbc-h2-0.8.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: com.h2database:h2:jar:1.4.199:compile (file:///Users/maki/.m2/repository/com/h2database/h2/1.4.199/h2-1.4.199.jar) [INFO] ImageClasspath Entry: org.apache.commons:commons-lang3:jar:3.9:compile (file:///Users/maki/.m2/repository/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar) [INFO] ImageClasspath Entry: io.r2dbc:r2dbc-postgresql:jar:0.8.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/r2dbc/r2dbc-postgresql/0.8.0.BUILD-SNAPSHOT/r2dbc-postgresql-0.8.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: com.ongres.scram:client:jar:1.0.0-beta.2:compile (file:///Users/maki/.m2/repository/com/ongres/scram/client/1.0.0-beta.2/client-1.0.0-beta.2.jar) [INFO] ImageClasspath Entry: com.ongres.scram:common:jar:1.0.0-beta.2:compile (file:///Users/maki/.m2/repository/com/ongres/scram/common/1.0.0-beta.2/common-1.0.0-beta.2.jar) [INFO] ImageClasspath Entry: io.r2dbc:r2dbc-pool:jar:0.8.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/io/r2dbc/r2dbc-pool/0.8.0.BUILD-SNAPSHOT/r2dbc-pool-0.8.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: org.springframework:spring-test:jar:5.2.0.BUILD-SNAPSHOT:compile (file:///Users/maki/.m2/repository/org/springframework/spring-test/5.2.0.BUILD-SNAPSHOT/spring-test-5.2.0.BUILD-SNAPSHOT.jar) [INFO] ImageClasspath Entry: com.example:moneyger:jar:1.0.0-SNAPSHOT (file:///Users/maki/handson/moneyger/target/moneyger-1.0.0-SNAPSHOT.jar) [INFO] Executing: /Users/maki/.sdkman/candidates/java/19.2.0-grl/jre/bin/native-image -cp /Users/maki/.m2/repository/com/github/making/moneyger-ui/master-7d3a8b2729-1/moneyger-ui-master-7d3a8b2729-1.jar:/Users/maki/.m2/repository/org/springframework/spring-context/5.2.0.BUILD-SNAPSHOT/spring-context-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-aop/5.2.0.BUILD-SNAPSHOT/spring-aop-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-beans/5.2.0.BUILD-SNAPSHOT/spring-beans-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-core/5.2.0.BUILD-SNAPSHOT/spring-core-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-jcl/5.2.0.BUILD-SNAPSHOT/spring-jcl-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-expression/5.2.0.BUILD-SNAPSHOT/spring-expression-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-webflux/5.2.0.BUILD-SNAPSHOT/spring-webflux-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-web/5.2.0.BUILD-SNAPSHOT/spring-web-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/io/projectreactor/reactor-core/3.3.0.BUILD-SNAPSHOT/reactor-core-3.3.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3.jar:/Users/maki/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/maki/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/maki/.m2/repository/org/slf4j/slf4j-api/1.7.28/slf4j-api-1.7.28.jar:/Users/maki/.m2/repository/io/projectreactor/netty/reactor-netty/0.9.0.BUILD-SNAPSHOT/reactor-netty-0.9.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/io/netty/netty-codec-http/4.1.39.Final/netty-codec-http-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-common/4.1.39.Final/netty-common-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-buffer/4.1.39.Final/netty-buffer-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-transport/4.1.39.Final/netty-transport-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-resolver/4.1.39.Final/netty-resolver-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-codec/4.1.39.Final/netty-codec-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-handler/4.1.39.Final/netty-handler-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-handler-proxy/4.1.39.Final/netty-handler-proxy-4.1.39.Final.jar:/Users/maki/.m2/repository/io/netty/netty-codec-socks/4.1.39.Final/netty-codec-socks-4.1.39.Final.jar:/Users/maki/.m2/repository/io/projectreactor/addons/reactor-pool/0.1.0.BUILD-SNAPSHOT/reactor-pool-0.1.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.9.3/jackson-databind-2.9.9.3.jar:/Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/maki/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.9/jackson-core-2.9.9.jar:/Users/maki/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.9/jackson-datatype-jsr310-2.9.9.jar:/Users/maki/.m2/repository/am/ik/yavi/yavi/0.2.2/yavi-0.2.2.jar:/Users/maki/.m2/repository/org/springframework/data/spring-data-r2dbc/1.0.0.BUILD-SNAPSHOT/spring-data-r2dbc-1.0.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/data/spring-data-commons/2.2.0.BUILD-SNAPSHOT/spring-data-commons-2.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/data/spring-data-relational/1.1.0.BUILD-SNAPSHOT/spring-data-relational-1.1.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-tx/5.2.0.BUILD-SNAPSHOT/spring-tx-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/io/r2dbc/r2dbc-spi/0.8.0.BUILD-SNAPSHOT/r2dbc-spi-0.8.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/io/r2dbc/r2dbc-h2/0.8.0.BUILD-SNAPSHOT/r2dbc-h2-0.8.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/com/h2database/h2/1.4.199/h2-1.4.199.jar:/Users/maki/.m2/repository/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar:/Users/maki/.m2/repository/io/r2dbc/r2dbc-postgresql/0.8.0.BUILD-SNAPSHOT/r2dbc-postgresql-0.8.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/com/ongres/scram/client/1.0.0-beta.2/client-1.0.0-beta.2.jar:/Users/maki/.m2/repository/com/ongres/scram/common/1.0.0-beta.2/common-1.0.0-beta.2.jar:/Users/maki/.m2/repository/io/r2dbc/r2dbc-pool/0.8.0.BUILD-SNAPSHOT/r2dbc-pool-0.8.0.BUILD-SNAPSHOT.jar:/Users/maki/.m2/repository/org/springframework/spring-test/5.2.0.BUILD-SNAPSHOT/spring-test-5.2.0.BUILD-SNAPSHOT.jar:/Users/maki/handson/moneyger/target/moneyger-1.0.0-SNAPSHOT.jar -H:Class=com.example.App -H:Name=moneyger [moneyger:15419] classlist: 6,196.53 ms [moneyger:15419] (cap): 1,641.41 ms [moneyger:15419] setup: 3,431.27 ms ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated [moneyger:15419] (typeflow): 20,420.66 ms [moneyger:15419] (objects): 12,896.53 ms [moneyger:15419] (features): 1,562.57 ms [moneyger:15419] analysis: 36,596.97 ms [moneyger:15419] (clinit): 868.41 ms [moneyger:15419] universe: 1,846.08 ms [moneyger:15419] (parse): 3,072.85 ms [moneyger:15419] (inline): 6,360.03 ms [moneyger:15419] (compile): 26,694.54 ms [moneyger:15419] compile: 38,162.44 ms [moneyger:15419] image: 3,108.36 ms [moneyger:15419] write: 1,127.10 ms [moneyger:15419] [total]: 90,727.56 ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:34 min [INFO] Finished at: 2019-09-05T17:56:16+09:00 [INFO] Final Memory: 38M/357M [INFO] ------------------------------------------------------------------------ ``` `psql`で`CREATE DATABASE moneyger;`を実行してください。 Native Imageのバイナリは`./target/classes/moneyger`です。これを実行してください。 ``` $ ./target/classes/moneyger Sep 05, 2019 6:00:02 PM com.fasterxml.jackson.databind.ext.Java7Support WARNING: Unable to load JDK7 types (annotations, java.nio.file.Path): no Java7 support added 2019-09-05 18:00:02.597 INFO --- [ main] com.example.App : Started in 0.009 seconds ``` おめでとうございます🎉 Dockerでもビルドできます。 ``` chmod +x mvnw docker run --rm \ -v "$PWD":/usr/src \ -v "$HOME/.m2":/root/.m2 \ -w /usr/src \ oracle/graalvm-ce:19.2.0 \ ./mvnw clean package -Pgraal -DskipTests=true ``` ここで生成されるバイナリはLinux用です。