しおしお

IntelliJ IDEAのことなんかを書いてます

Spring Boot 2.3からのCloud Native Buildpacksを試してみる

Spring Boot 2.3からのCloud Native Buildpacksを試してみました。

プロジェクトの作成

Spring Initializrを使って、Spring Bootのバージョンを2.3以降にしてプロジェクトを生成します。
※お試しプロジェクトでは、Dependenciesに Spring WebSpring Boot Actuator を選択してGradleプロジェクトとしています。

Gradleのタスク一覧から、イメージ作成用のタスク(bootBuildImage)が追加されていることが確認できます。

./gradlew tasks --group build

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
bootBuildImage - Builds an OCI image of the application using the output of the bootJar task
bootJar - Assembles an executable jar archive containing the main classes and their dependencies.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

タスクの詳細を確認してみると、オプションでイメージ名を指定できることがわかりますね。

./gradlew help --task bootBuildImage

> Task :help
Detailed task information for bootBuildImage

Path
     :bootBuildImage

Type
     BootBuildImage (org.springframework.boot.gradle.tasks.bundling.BootBuildImage)

Options
     --builder     The name of the builder image to use

     --imageName     The name of the image to generate

Description
     Builds an OCI image of the application using the output of the bootJar task

Group
     build

イメージを作って動かしてみる

bootBuildImageタスクを使うとさくっとイメージを作れます。

./gradlew bootBuildImage                                                                                                                                                                                                                                                                                                                                                                      ✘ 126 

> Task :bootBuildImage
Building image 'docker.io/library/buildpack-example:0.0.1-SNAPSHOT'

 > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' ..................................................
 > Pulled builder image 'gcr.io/paketo-buildpacks/builder@sha256:21a7235c03dbdfcc330e03183a85e0a36b4c0eee6904529e2376502e5e4a84a5'
 > Pulling run image 'gcr.io/paketo-buildpacks/run:base-cnb' ..................................................
 > Pulled run image 'gcr.io/paketo-buildpacks/run@sha256:fb49a85ddb10a93f194cc407b34d5c352def37693c23be5b9f9a9859e7526b78'
 > Executing lifecycle version v0.8.1
 > Using build cache volume 'pack-cache-3949d5df2001.build'

 > Running creator
    [creator]     ===> DETECTING
    [creator]     5 of 16 buildpacks participating
    [creator]     paketo-buildpacks/bellsoft-liberica 2.10.0
    [creator]     paketo-buildpacks/executable-jar    2.0.1
    [creator]     paketo-buildpacks/apache-tomcat     1.3.3
    [creator]     paketo-buildpacks/dist-zip          1.3.7
    [creator]     paketo-buildpacks/spring-boot       2.2.1
    [creator]     ===> ANALYZING
    [creator]     Previous image with name "docker.io/library/buildpack-example:0.0.1-SNAPSHOT" not found
    [creator]     ===> RESTORING
    [creator]     ===> BUILDING
    [creator]     
    [creator]     Paketo BellSoft Liberica Buildpack 2.10.0
    [creator]       https://github.com/paketo-buildpacks/bellsoft-liberica
    [creator]       Build Configuration:
    [creator]         $BP_JVM_VERSION              11.*            the Java version
    [creator]       Launch Configuration:
    [creator]         $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    [creator]         $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    [creator]       BellSoft Liberica JRE 11.0.8: Contributing to layer
    [creator]         Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.8+10/bellsoft-jre11.0.8+10-linux-amd64.tar.gz
    [creator]         Verifying checksum
    [creator]         Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    [creator]         Writing env.launch/JAVA_HOME.override
    [creator]         Writing env.launch/MALLOC_ARENA_MAX.override
    [creator]         Writing profile.d/active-processor-count.sh
    [creator]       Memory Calculator 4.1.0: Contributing to layer
    [creator]         Downloading from https://github.com/cloudfoundry/java-buildpack-memory-calculator/releases/download/v4.1.0/memory-calculator-4.1.0.tgz
    [creator]         Verifying checksum
    [creator]         Expanding to /layers/paketo-buildpacks_bellsoft-liberica/memory-calculator
    [creator]         Writing profile.d/memory-calculator.sh
    [creator]       Class Counter: Contributing to layer
    [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/class-counter
    [creator]       JVMKill Agent 1.16.0: Contributing to layer
    [creator]         Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    [creator]         Verifying checksum
    [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
    [creator]         Writing env.launch/JAVA_OPTS.append
    [creator]       Link-Local DNS: Contributing to layer
    [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/link-local-dns
    [creator]         Writing profile.d/link-local-dns.sh
    [creator]       Java Security Properties: Contributing to layer
    [creator]         Writing env.launch/JAVA_OPTS.append
    [creator]         Writing env.launch/JAVA_SECURITY_PROPERTIES.override
    [creator]       Security Providers Configurer: Contributing to layer
    [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/security-providers-configurer
    [creator]         Writing profile.d/security-providers-classpath.sh
    [creator]         Writing profile.d/security-providers-configurer.sh
    [creator]       OpenSSL Certificate Loader: Contributing to layer
    [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/openssl-security-provider
    [creator]         Writing profile.d/openssl-certificate-loader.sh
    [creator]     
    [creator]     Paketo Executable JAR Buildpack 2.0.1
    [creator]       https://github.com/paketo-buildpacks/executable-jar
    [creator]         Writing env.launch/CLASSPATH
    [creator]       Process types:
    [creator]         executable-jar: java -cp "${CLASSPATH}" ${JAVA_OPTS} org.springframework.boot.loader.JarLauncher
    [creator]         task:           java -cp "${CLASSPATH}" ${JAVA_OPTS} org.springframework.boot.loader.JarLauncher
    [creator]         web:            java -cp "${CLASSPATH}" ${JAVA_OPTS} org.springframework.boot.loader.JarLauncher
    [creator]     
    [creator]     Paketo Spring Boot Buildpack 2.2.1
    [creator]       https://github.com/paketo-buildpacks/spring-boot
    [creator]       Build Configuration:
    [creator]         $BP_BOOT_NATIVE_IMAGE                  the build to create a native image (requires GraalVM)
    [creator]         $BP_BOOT_NATIVE_IMAGE_BUILD_ARGUMENTS  the arguments to pass to the native-image command
    [creator]       Launch Configuration:
    [creator]         $BPL_SPRING_CLOUD_BINDINGS_ENABLED     whether to auto-configure Spring Boot environment properties from bindings
    [creator]       Web Application Type: Contributing to layer
    [creator]         Servlet web application detected
    [creator]         Writing env.launch/BPL_JVM_THREAD_COUNT.default
    [creator]       Spring Cloud Bindings 1.4.0: Contributing to layer
    [creator]         Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.4.0/spring-cloud-bindings-1.4.0.jar
    [creator]         Verifying checksum
    [creator]         Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
    [creator]         Writing profile.d/spring-cloud-bindings.sh
    [creator]       Image labels:
    [creator]         org.springframework.boot.spring-configuration-metadata.json
    [creator]         org.springframework.boot.version
    [creator]     ===> EXPORTING
    [creator]     Adding layer 'launcher'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:class-counter'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:link-local-dns'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:memory-calculator'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:openssl-security-provider'
    [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:security-providers-configurer'
    [creator]     Adding layer 'paketo-buildpacks/executable-jar:class-path'
    [creator]     Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
    [creator]     Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
    [creator]     Adding 1/1 app layer(s)
    [creator]     Adding layer 'config'
    [creator]     *** Images (ab7b253dc430):
    [creator]           docker.io/library/buildpack-example:0.0.1-SNAPSHOT

Successfully built image 'docker.io/library/buildpack-example:0.0.1-SNAPSHOT'

コンテナー起動後、actuator/envで確認するとJava11でアプリケーションが実行されているのが確認できますね。

curl http://localhost:8080/actuator/env
# 省略
{
  "value": "11.0.8+10-LTS"
}

Javaのバージョンは、GradleのtargetCompatibilityと連動しているっぽく、以下のようにJava14を指定すると自動的に起動時に使用するバージョンが変わるようです。

java {
    targetCompatibility = JavaVersion.VERSION_14
}

上の状態でイメージ作成後に確認してみると想定通りJava14で起動されていました。

curl http://localhost:8080/actuator/env
# 省略
{
  "value": "14.0.2+13"
}

起動時のオプションを変更してみる

コンテナ起動時に、JAVA_OPTS環境変数を設定することで起動時のJVMオプションを変更できるようです。

例えば、下のようにコンテナを起動するとhogeシステムプロパティにfugaが設定されます。

docker run -p "8080:8080" -e JAVA_OPTS="-Dhoge=fuga"  76747f99e0c7

起動後、actuator/envを叩くことで想定通りシステムプロパティが設定されていることが確認できます。

curl http://localhost:8080/actuator/env
{
  "activeProfiles": [],
  "propertySources": [
    {
      "name": "server.ports",
      "properties": {
        "local.server.port": {
          "value": 8080
        }
      }
    },
    {
      "name": "servletContextInitParams",
      "properties": {}
    },
    {
      "name": "systemProperties",
      "properties": {
        "hoge": {
          "value": "fuga"
        }
    }
  ]
}

イメージ名を指定してビルドしてみる

bootBuildImageタスクのimageNameオプションを指定することで、イメージ名を変更できます。 リモートリポジトリの名前に合わせる場合なんかに使うとよさげですかね。

./gradlew bootBuildImage --imageName siosio/sample:1.0.0

例えば、GitHub ActionsからECRにビルドしたイメージをpushする場合なんかは、こんな感じでいけるかなと思います。

    steps:
      - uses: actions/checkout@v2

      - name: Cache Gradle packages
        uses: actions/cache@v1
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle

      - name: setup java
        uses: actions/setup-java@v1
        with:
          java-version: 11
          
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: build image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
        run: |
          IMAGE_TAG=$(echo ${{ github.ref }} | sed 's/refs\/heads\///g' | sed 's/\//-/g')
          ./gradlew bootBuildImage --imageName $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

IntelliJ IDEAのElasticsearchプラグインを試してみた

IntelliJ IDEA用のElasticsearchプラグインが最近登場したらしいのでお試ししてみたよ。

使ったバージョン

  • IntelliJ IDEA 2020.2 BETA
  • Elasticsearch plugin 0.1.7

インストール

Elasticsearchプラグインの以下ページやIntelliJのplugin settings画面からインストールできます。 plugins.jetbrains.com

Elasticsearchへの接続

Elasticsearchウィンドウを開いて、上の方にある から開くウィンドウで接続先の設定を行います。 f:id:sioiri:20200710062709p:plain

Elasticsearchウィンドウからできること

ホストを選択してコンテキストメニューで表示される内容になります。 インデックスの作成やステータスの確認が簡単にできそうですね。インデックスの作成時には、マッピング情報などの指定はできないみたいですが…

f:id:sioiri:20200710062953p:plain

インデックスを選択した場合は、インデックス関連のメニューに変わりますね

f:id:sioiri:20200710070405p:plain

検索処理をしてみよう

インデックスを選択してquery editor(下の画像の赤枠アイコン)を開くと検索処理が行えるようになります。

f:id:sioiri:20200710084643p:plain

デフォルトで、↓なクエリが表示されているのでこれを編集していくかんじになりますね。 クエリのJsonの組み立てはなにかサポート機能があるわけではないので、現状自力で頑張るしかないようです…

{
  "from": 0,
  "size": 20,
  "query": {
    "match_all": {}
  }
}

検索結果の表示は、下の画像のように tesxt or table で切り替えられるようになっています。

f:id:sioiri:20200710085811p:plain

table 表示に切り替えると検索結果めっちゃ見やすくなります。

f:id:sioiri:20200710085915p:plain

まとめ

クエリの履歴機能がないのはかなり不便かなと思いますが、検索結果をテーブルで表示できるのはめっちゃ見やすくてよいですね。 IntelliJさんから移動しなくてもさくっと、Elasticsearchにアクセスできるのはなかなか便利で良いです。

僕は、Elasticsearchにアクセスするときは、HTTP client in IntelliJ IDEA code editor - Help | IntelliJ IDEAを使うんですが、 現状だと保存したクエリをさくっと実行できるIntelliJさんのhttp clientのが便利かなーと感じました。(.httpファイルをコミットしておいてチームメンバーにも共有できますし)

まだ、公開されてから1ヶ月ぐらいしか立っていないので今後の進化に期待ですね。

Spring BootでDomaのCriteria APIをつかってみる

doma-spring-boot-starter1.4.0で簡単にCriteria APIが使えるようになったので早速つかってみました。

build.gradle

build.gradleに必要なライブラリを追加します。 doma2.30.0からcoreprocessorが分離されてたのも把握できてよかったです。

    implementation("org.seasar.doma.boot:doma-spring-boot-starter:1.4.0")
    implementation("org.seasar.doma:doma-core:2.35.0")
    kapt("org.seasar.doma:doma-processor:2.35.0")

Entity

  • Criteria APIのドキュメントにどおりに、@Entitymetamodelを設定します
@Entity(immutable = true, metamodel = Metamodel())
@Table(name = "user")
data class UserEntity(

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val id: Int,

        val name: String
)

Criteria APIを使ってみる

  • Criteria APIに必要なEntityqlをコンストラクタインジェクションでうけとります
    • Entityqldoma-spring-boot-starterがBean生成してくれるので特に設定せずに簡単に使えます
  • Entityqlには、Entityから生成したmetamodel classを渡します
    • Entityの例に登場したmetamodel = Metamodel()があることで、metamodel class(クラス名の最後にアンダースコアがついたもの)がコンパイル時に生成されます
  • 今回は、Entityのクラス名からmetamodel classを雑にひっぱってきて利用しています
  • あとは、ドキュメントどおりにCriteria APIを使っていくだけですね
import org.seasar.doma.jdbc.criteria.Entityql
import org.seasar.doma.jdbc.criteria.metamodel.EntityMetamodel
import siosio.demo.domain.User
import siosio.demo.domain.UserRepository

class DomaUserRepository(private val entityql: Entityql) : UserRepository {
    override fun findAll(): List<User> {
        return entityql.from(entityType<UserEntity>())
                .fetch()
                .map { User(it.id, it.name) }
    }

    override fun addUser(user: User) {
        entityql.insert(entityType<UserEntity>(), UserEntity(-1, user.name))
                .execute()
    }
}
private inline fun <reified ENTITY> entityType(): EntityMetamodel<ENTITY> {
    return Class.forName("${ENTITY::class.qualifiedName}_").getConstructor()
            .newInstance() as EntityMetamodel<ENTITY>
}

サンプルコード

github.com

Spring BootのテストでTestcontainersを使ってみる

Spring BootのテストでTestcontainersを使って、データベースをコンテナとして起動してテストを実行してみる感じです。 これを使うことで、開発で使っているデータベースを汚染せずに簡単にデータベースまで通しのテストができそうな気がしています。

Testcontainers関連のライブラリを追加

build.gradle.ktsTestcontainers関連のライブラリを追加しています。 今回は、MySqlで試したので、org.testcontainers:mysqlを入れています。

testImplementation(platform("org.testcontainers:testcontainers-bom:1.14.1"))
testImplementation("org.testcontainers:mysql")
testImplementation("org.testcontainers:junit-jupiter")

テスト対象

エンドポイント

テスト対象は、データベースに変更を加える登録処理とテーブルの全件を返すような一覧取得用のエンドポイントとしています。

private fun myRouter(
    userHandler: UserHandler
) = router {
    "/api".nest {
        POST("/users", userHandler::addUser)
        GET("/users", userHandler::findAll)
    }
}

テーブル定義

名前だけ持つ簡単なテーブルにしています。

create table user (
    id int not null auto_increment,
    name varchar(200) not null,
    primary key (id)
);

テストの準備

テストコード

  • @Testcontainersアノテーションをテストクラスに追加します
  • @Containerアノテーションをつけて、テストで使用するコンテナを指定します
    今回は、MySqlを使うのでMySQLContainerとなります
  • @DynamicPropertySourceをつけたメソッドで、コンテナで起動したデータベースのURLなどを設定値として登録します
  • BeforeEachでテスト対象のデータベースの内容をFlywayで初期化して登録のケースなどのデータベース変更の影響を受けないようにしています
    テストで使うデータは、test配下のdb/migrationにRepeatableなやつとしておいておいてバージョンなどの影響を受けずに必ず最後に実行するようにしています

アノテーションつけるだけでさくっとコンテナでテストできるのめっちゃ便利ですね。 考えないといけないのは、テストで使うデータのセットアップなどですね。

@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
internal class UserHandlerTest(
        val mockMvc: MockMvc,
        val dataSource: DataSource,
        val flyway: Flyway
) {

    @BeforeEach
    internal fun setUp() {
        flyway.clean()
        flyway.migrate()
    }

    companion object {

        @JvmStatic
        @Container
        val container = MySQLContainer<Nothing>()

        @DynamicPropertySource
        @JvmStatic
        fun changeProperty(registry: DynamicPropertyRegistry): Unit {
            println("container.jdbcUrl = ${container.jdbcUrl}")
            registry.add("spring.datasource.url", container::getJdbcUrl)
            registry.add("spring.datasource.username", container::getUsername)
            registry.add("spring.datasource.password", container::getPassword)
        }
    }

    @Test
    internal fun ユーザが登録出来るよ() {
        val table = Table(dataSource, "user")
        val changes = Changes(table)
        changes.setStartPointNow()

        mockMvc.post("/api/users") {
            contentType = MediaType.APPLICATION_JSON
            content = """{"name": "siosio"}"""
        }.andExpect {
            status { isOk }
        }

        changes.setEndPointNow()
        Assertions.assertThat(changes)
                .hasNumberOfChanges(1)
                .changeOfCreation()
                .rowAtEndPoint().value("name").isEqualTo("siosio")
    }

    @Test
    internal fun ユーザ一覧が取得できるよ() {

        mockMvc.get("/api/users") {
        }.andExpect {
            status { isOk }
            content { contentType(MediaType.APPLICATION_JSON) }
            jsonPath("$.users.length()") { value(3) }
            jsonPath("$.users[*].name") { value(contains("user_1", "user_2", "user_3")) }
            jsonPath("$.users[*].id") { value(contains(1, 2, 3)) }
        }
    }
}

テスト用のデータベース用のデータ

Repeatableなマイグレーションファイルとしてtest/resources/db/migration/R__replace_user_table.sqlを作って、テスト対象テーブルのデータをセットアップしています。

truncate table user;
insert into user (name) values ('user_1');
insert into user (name) values ('user_2');
insert into user (name) values ('user_3');

テスト実行

こんな感じにログがでて、コンテナが起動されてテストが実行されます。 わりとさくっと、コンテナ使ってテスト実行が出来て便利な感じがあります。

07:10:33.048 [Test worker] DEBUG org.testcontainers.images.AbstractImagePullPolicy - Using locally available and not pulling image: mysql:5.7.22
07:10:33.048 [Test worker] DEBUG 🐳 [mysql:5.7.22] - Starting container: mysql:5.7.22
07:10:33.048 [Test worker] DEBUG 🐳 [mysql:5.7.22] - Trying to start container: mysql:5.7.22
07:10:33.049 [Test worker] DEBUG 🐳 [mysql:5.7.22] - Trying to start container: mysql:5.7.22 (attempt 1/3)
07:10:33.049 [Test worker] DEBUG 🐳 [mysql:5.7.22] - Starting container: mysql:5.7.22
07:10:33.049 [Test worker] INFO 🐳 [mysql:5.7.22] - Creating container for image: mysql:5.7.22

サンプルコード

サンプルプロジェクトは↓ github.com

IntelliJ IDEA系の自作プラグインでLanguage Injectionsの設定を行う方法

このメソッドのこの引数は自動的にHTMLやSQLとして認識させたいなーって思うことがあったりしますよね。 それを、自作のプラグインで自動的に設定してあげる手順的なやつです。

プラグインの設定手順

plugin.xmlの定義

org.intellij.intelliLang の拡張ポイントを使って、Language Injectionsの設定ファイルを認識させます。

plugin.xmlの内容的にはこんな感じになります。 この設定の場合は、プロジェクトのmain/resources/META-INF/domaInjection.xml(plugin.xmlと同じ場所)がLanguage Injectionsの設定ファイルとしてロードされます。 1

  <extensions defaultExtensionNs="org.intellij.intelliLang">
    <injectionConfig config="META-INF/domaInjection.xml" />
  </extensions>

Language Injectionsの設定ファイルを作る

Language Injectionsの設定ファイルを作って、plugin.xmlinjectionConfigに設定したパスに保存してあげます。

設定ファイルの内容的には、こんな感じですね。<component name="LanguageInjectionConfiguration">内にinjectionの設定をしてあげます。

<?xml version="1.0" encoding="UTF-8"?>
<component name="LanguageInjectionConfiguration">
  <injection language="SQL" injector-id="java">
    <display-name>Sql.value (org.seasar.doma.experimental)</display-name>
    <place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.seasar.doma.experimental.Sql")]]></place>
  </injection>
  <injection language="SQL" injector-id="kotlin">
    <display-name>Sql.value (org.seasar.doma.experimental)</display-name>
    <place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.seasar.doma.experimental.Sql")]]></place>
  </injection>
</component>

この設定ファイル、手で作るのはかなりしんどいのでIntelliJの画面で作ったxmlから設定内容を持ってくるのが楽で良いですね。 IntelliJの設定画面からは、↓な流れで設定できますね。

f:id:sioiri:20200226120615p:plain:w300

設定内容は、~/.IntelliJIdea2019.3/config/options/IntelliLang.xml(2020.1以降は、~/.config/JetBrains/IntelliJIdea2020.1/options/IntelliLang.xml)に保存されているので、そこから必要なinjectionタグを探して持ってくれば良いですね。

動かしてみると

↓のようにLanguage Injectionsの設定画面でプラグインで設定したやつが、Built-inスコープで登録されていれば成功ですね。

f:id:sioiri:20200226122019p:plain:w300


  1. IntelliJさんでこの設定を入れると、プラグイン開発用のプラグインorg.intellij.intelliLangに対応していないのかinjectionConfigが怒られますが気にしないで大丈夫です。

VueRouterでroute変更時にaxiosで実行中のリクエストをすべてキャンセルする

やりたいこと

VueRouterでroute変更時に、前のページで投げられていたリクエストを一括でキャンセルしたい。 これが出来ると、遷移前のページで大量にリクエストが投げられていた場合、遷移後のページのcreatedで実行するリクエストがすぐに実行出来るようになる。

サンプルコード

リクエストをキャンするするためのトークンなどを作る

リクエストをキャンするするためには、axiosでリクエストを投げる時にキャンセルトークンを設定する必要があるので、それを生成するクラスを作ってあげる。 リクエストキャンセル時には、以降のリクエストでは新しいキャンセルトークンが使えるようにしています。*1

import axios, { CancelTokenSource } from 'axios'

class RequestCanceler {
  private source: CancelTokenSource = axios.CancelToken.source()

  cancel() {
    this.source.cancel()
    this.source = axios.CancelToken.source()
  }

  token() {
    return this.source.token
  }
}
const requestCanceler = new RequestCanceler()

export {
  requestCanceler
}

axiosでリクエストを投げる

axiosでリクエストを投げるときには、configcancelTokenを設定してあげます。 キャンセルが行われると例外が上がるので、axios.isCancelを使ってキャンセルによる失敗なのかそれ以外なのかを判定してあげる必要があります。

import { requestCanceler } from './api'
import axios from 'axios'

axios.get(`http://localhost:8080/test`, {cancelToken: requestCanceler.token()}).then(res => {
  console.log(res.data)
}).catch(reason => {
  if (axios.isCancel(reason)) {
    console.log('cancel!!!!')
  } else {
    console.log('cancelじゃないよ')        
  }
})

route変更時にリクエストを一括キャンセルする

VueRouterbeforeEachでキャンセル処理を呼び出してリクエストを一括キャンセルしてあげます。

router.beforeEach((to, from, next) => {
  console.log('beforeEach')
  requestCanceler.cancel()
  next()
})

動かしてみた結果

VueRouterbeforeEachのログが出力された後に大量のcancel!!!が出力されているので、 キャンセル処理が動いてエラー処理でキャンセル処理の分岐に入っていることがわかりますね。

f:id:sioiri:20200223065955p:plain:w300

参考情報

*1:新しいトークンに変えないと、タイミングの問題なのか直後に投げるリクエストがキャンセル対象になったりしてしまいます…

Gradle Dependencies HelperプラグインをKotlin DSL対応したよ

Gradle Dependencies Helper - Plugins | JetBrainsのKotlin DSL対応したよ。 これで、今までGroovy DSLでしか出来ていなかったArtifactやVersion番号の候補表示がKotlin DSLでも出来るようになるよ。

こんな感じのことができるよ。 f:id:sioiri:20200218064403g:plain

2020.1 (eap)の対応としているので、最新のEAPを落としてくるとプラグインがインストール出来ます。