本ハンズオンで、次の図のような簡易家計簿のAPIサーバーをSpring WebFlux.fnを使って実装します。 あえてSpring BootもDependency Injectionも使わないシンプルなWebアプリとして実装します。
ハンズオンコンテンツ
- はじめに
- 簡易家計簿Moneygerプロジェクトの作成
- YAVIによるValidationの実装
- R2DBCによるデータベースアクセス
- Web UIの追加
- 例外ハンドリングの改善
- 収入APIの実装
- Spring Bootアプリに変換
- GraalVMのSubstrateVMでNative Imageにコンパイル 👈
この章ではSpring Bootアプリに変換前の状態で行なってください。
かなりチャレンジングですが、ここまで作ったアプリをGraalVMのSubstrateVMでNative Imageにコンパイルしてみます。
⚠️ 2019-09-05時点で、GraalVM Version 19.2.0 CEで動作確認していますが、
今度も動作する保証はありません。"とりあえず動いた"レベルの内容なのであまり信用しないでください。
後々spring-graal-featureを使う用に変更する予定です。
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
メソッドで明示的に登録します。
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配列に次を追加すれば良いです。{ "name": "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", "allDeclaredConstructors": true, "allDeclaredMethods": true }
次に、ConnectionFactory
の生成方法を変更します。ConnectionFactories.get
はクラスローダーからServiceLoader
でio.r2dbc.spi.ConnectionFactoryProvider
をロードするが、
これもNative Image上で利かないので、今回は明示的にPostgreSQL用のPostgresqlConnectionFactory
を直接作成するように変更します。
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が使われているので、ここでは明示的なマッピングを行うように修正します。
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<Row, Expenditure> 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<Expenditure> findAll() {
return this.databaseClient.execute("SELECT expenditure_id, expenditure_name, unit_price, quantity, expenditure_date FROM expenditure")
.map(this.rowExpenditureMapper)
.all();
}
@Override
public Mono<Expenditure> 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<Expenditure> 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<Void> 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配列に次のように追加します。
{
"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をインストールしてください。
JAVA_HOME
を更新した後、次のコマンドを実行して、native-image
gu install native-image
で
export DATABASE_URL=postgresql://<username>:<password>@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 <clinit>
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用です。