IK.AM

@making's tech note


Spring WebFlux.fnハンズオン - X. GraalVMのSubstrateVMでNative Imageにコンパイル


本ハンズオンで、次の図のような簡易家計簿のAPIサーバーをSpring WebFlux.fnを使って実装します。 あえてSpring BootもDependency Injectionも使わないシンプルなWebアプリとして実装します。

ハンズオンコンテンツ

  1. はじめに
  2. 簡易家計簿Moneygerプロジェクトの作成
  3. YAVIによるValidationの実装
  4. R2DBCによるデータベースアクセス
  5. Web UIの追加
  6. 例外ハンドリングの改善
  7. 収入APIの実装
  8. Spring Bootアプリに変換
  9. 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-blank0.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はクラスローダーからServiceLoaderio.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] ------------------------------------------------------------------------

psqlCREATE 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用です。


✒️️ Edit  ⏰ History  🗑 Delete