20201223のJavaに関する記事は15件です。

ArchUnit 実践:Onion Architecture のアーキテクチャテスト

// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

レイヤーの依存関係

  • 依存の方向は外側の層から内側の層への一方通行
  • 一番外側のアダプター同士は依存しない

image.png

https://dzone.com/articles/onion-architecture-is-interesting

Java プロジェクトのパッケージ構成

image.png

アーキテクチャテストの実装

package com.example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.library.Architectures.onionArchitecture;

class ArchitectureTest {

    // 検査対象のクラス
    private static final JavaClasses CLASSES =
            new ClassFileImporter()
                    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                    .importPackages("com.example");

    @Test
    void オニオンアーキテクチャのアーキテクチャテスト() {
        onionArchitecture()
            // domain.model パッケージをドメインモデル層として定義
            .domainModels("com.example.domain.model..")
            // domain.service パッケージをドメインサービス層として定義
            .domainServices("com.example.domain.service..")
            // application パッケージをアプリケーションサービス層として定義
            .applicationServices("com.example.application..")

            // infrastructure パッケージをインフラストラクチャ・アダプターとして定義
            .adapter("infra", "com.example.infrastructure..")
            // presentation パッケージをユーザインターフェイス・アダプターとして定義
            .adapter("ui", "com.example.presentation..")

            .check(CLASSES);
    }
}

アーキテクチャテストの実行例

テスト失敗例①(ドメインサービス→アプリケーションサービスへの依存)

内側のドメインサービス層の Service クラスが、外側のアプリケーションサービス層の UseCase クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest > オニオンアーキテクチャのアーキテクチャテスト() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.domain.service.employee.EmployeeRegisterService.<init>(com.example.application.HogeUseCase)> has parameter of type <com.example.application.HogeUseCase> in (EmployeeRegisterService.java:0)
    Field <com.example.domain.service.employee.EmployeeRegisterService.hogeUseCase> has type <com.example.application.HogeUseCase> in (EmployeeRegisterService.java:0)
        at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94)
        at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82)
        at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267)
        at com.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.オニオンアーキテクチャのアーキテクチャテスト(ArchitectureTest.java:560)

1 test completed, 1 failed

テスト失敗例②(ユーザインターフェイス・アダプター→インフラストラクチャ・アダプターへの依存)

ユーザインターフェイス・アダプターの Controller クラスが、インフラストラクチャ・アダプターの Repository(Impl) クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest > オニオンアーキテクチャのアーキテクチャテスト() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.presentation.employee.EmployeeController.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.java:0)
    Field <com.example.presentation.employee.EmployeeController.employeeRepository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.java:0)
        at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94)
        at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82)
        at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267)
        at com.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.オニオンアーキテクチャのアーキテクチャテスト(ArchitectureTest.java:560)

1 test completed, 1 failed
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Spigot】目次[ぐらんのプラグイン上達日記0]

ぐらん

毎日投稿レベルで書く予定なので目次を作っておきます。

今後書いていく予定のもののタイトルも書いておくので
楽しみに待っていてくださ~い(^^♪

01 開発環境構築

  1. IntelliJのダウンロード
  2. Spigotでサーバーを構成してみる

02 Spigot plguin

  1. 入退出メッセージの変更方法 ←←執筆中
  2. 入室時にhubに自動テレポートするようにする
  3. コマンドを実装してみる①

考え中

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】プログラムを一時停止するThread.sleepの使い方

プログラミング勉強日記

2020年12月23日
Android StudioでAndroid開発をしていて、音楽を一定時間流してその後一時停止するプログラムを書いたときにThread.sleepを使ったので、その使い方を簡単にまとめる。

スレッドとは

 スレッド(Thread)は処理の実行単位の一つで、プログラムの初めから終わりまでの一連の流れのことをいう。スレッドにはシングルスレッドとマルチスレッドがある。

 シングルスレッドは、直列処理とも呼ばれている。プログラムの始まりから終わりまでが一本で処理される。
 マルチスレッドは、並行処理とも呼ばれている。2つ以上の処理を並行して行う。マルチスレッドを利用するためには、4つのステップが必要である。

  1. Threadクラスを継承するクラスを作成
  2. Threadクラスを継承するクラスでrunメソッドを実行する
  3. Threadクラスを継承するクラスのインスタンスを生成する
  4. 生成したインスタンスに対してstartメソッドを実行する

sleepメソッドとは

 sleepメソッドは、プログラムを一時停止するときに使用する。マルチスレッドで良く用いられる。
 sleepメソッドを使用するメリットとしては、マルチスレッドで無限ループを実行していた場合、CPUの負荷が大きくなりパソコンの動作が重くなる要因になる。そういったときに、マルチスレッドの処理中にsleepメソッドを使用して、処理を一時停止すればCPUの負荷を抑えられる。

Thread.sleppの使い方

 sleepメソッドを使うにあたってポイントは2つだけ!

  • sleepメソッドの引数は、long型を使い、単位はミリ秒(1秒 = 1000ミリ秒)
  • sleepメソッドを使うときには必ず例外処理をつける
public class Sample {  
     public static void main(String[] args) {
        try {
            // 5秒間一時停止する
            Thread.sleep(5000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }      
    }    
}

マルチスレッドでsleepメソッドを実行する

public class ThreadSample {

    public static void main(String[] args) {
        MultiThread1 mt1 = new MultiThread1();
        MultiThread2 mt2 = new MultiThread2();
        mt1.start();
        mt2.start();
    }
}

class MultiThread1 extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MultiThread2 extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

参考文献

Javaでプログラムを一時停止、Thread.sleepの使い方と仕組みを解説
【Java入門】Threadをsleepメソッドで一定時間停止する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リアクティブプログラミングをNoSQLで「体験」してみる(Couchbase Reactive APIに触れる)

はじめに

プログラミング言語を巡って、常に新しいプログラミングパラダイムが生まれています。
ここでは、リアクティブプログラミングという新しいパラダイムがテーマになります(ちなみに、プログラミングパラダイムについての日本語のWikipediaでは、リアクティブプログラミングが、リストの最後に〜つまり、最も新しいものとして〜掲載されていました)。

さて、リアクティブプログラミングに関心を持った初学者が、リアクティブプログラミングを学ぶに際して、どういったアプローチが考えられるでしょうか?

手始めとして、まず概念的に理解することが考えられます。インターネットを「リアクティブプログラミング」というキーワードで検索すると、「リアクティブプログラミングとは(何か)」といった記事が、まず目につきます。

そして、実際にリアクティブプログラミングのフレームワークを使って、プログラミングすること、が次に思いつくのではないかと思います。

しかし、ここで多くの人が躓くことが想像できます。結局何を作れば良いのかがわからない、という状況になってしまいがちで、あるいは、フレームワークのチュートリアルを読み、数行のプログラムを書いてみたりはしたものの、それ以上先に進めないということもあるかもしれません。

ここでは、まずリアクティブフレームワークを用いて実装されたAPIを利用することで、リアクティブプログラミングを「体験」してみることにしたいと思います。どんな体験かといえば、(NoSQL)データベースにおけるデータ操作を題材にすることで、大規模データ取り扱い時の挙動という、リアクティブ(非ブロックAPI)と非リアクティブ(ブロックAPI)との違いを体感することを意図しています。

少し脱線になるかもしれませんが、過去には、WEBアプリケーション(やWEBブラウザ)というものが新しいものであった時代がありました。この時に、一度もWEBアプリケーションをユーザとして使ったことがない人が、WEBアプリケーションを開発したり、ましてやWEBアプリケーションの概念を学んだり、といったことがあったとしたら、それはかなり特殊な状況であることが分かるかと思います。
そういった意味でも、ここでのアプローチには、一定の意味があるのではないかと考えます。

体験のための素材の選択

リアクティブ(非ブロック)APIを提供しているデータベースであれば、何を使っても良いところですが、ここではドキュメント指向NoSQLデータベースである、Couchbase Serverを題材として用います。プログラミング言語としては、Javaを用いることにします。

体験の際の手法

リアクティブ(非ブロック)APIと、ブロック(非リアクティブ)APIの両方を使って、同じ処理を実行し、挙動の違いを観察します。

プログラム解説

基底クラス

リアクティブおよびブロックAPIの両方で共通する処理を基底クラスとして定義しました。

特にCouchbaseの知見がない人であって、データベースに接続し、RDBのテーブルに類するデータ格納先(Bucketの中のCollection)を取得していることはイメージしやすいかと思います。

この後で用いる、データの登録もこのクラスで実行しています(upsertメソッドを用い、実行時常に新規追加あるいは上書きします)。今回は、データ取得の挙動の違いを見ることにフォーカスするため、このupsertの処理については、旧来のブロックAPIを用いています。

import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.json.JsonObject;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

public abstract class Base {

    protected final Cluster cluster;
    protected final Bucket bucket;
    protected final Collection collection;

    public static final String bucketName = "default";
    public static final String userName = "Administrator";
    public static final String userPass = "password";
    public static final String seedNode = "127.0.0.1";

    public int NUM_KEY = 20000;
    protected List<String> keys = new ArrayList<String>(NUM_KEY);
    protected List<Object> listResults = new ArrayList<Object>(NUM_KEY);


    public Base() {
        cluster = Cluster.connect(seedNode, userName, userPass);
        bucket = cluster.bucket(bucketName);
        collection = bucket.defaultCollection();
        bucket.waitUntilReady(Duration.ofSeconds(30));
        prepKeys();
        prepDocuments();
    }

    private void prepKeys() {
        for (int i = 0; i < NUM_KEY; i++) {
            keys.add(String.valueOf(i));
        }
    }
    private void prepDocuments() {
        for (String key : keys) {
            JsonObject content = JsonObject.create().put("key", String.valueOf(key));
            collection.upsert(key, content);
        }
    }
    private void disconnect() {
        cluster.disconnect();
    }

    public void exec() {
        long startTime = System.currentTimeMillis();
        List<Object> listResults = get();
        long endTime = System.currentTimeMillis();

        System.out.println("先頭データ:" + listResults.get(0));
        System.out.println("末尾データ:" + listResults.get(NUM_KEY -1));
        System.out.println("取得サイズ:" + listResults.size());
        System.out.println("経過時間(ミリ秒):" + (endTime - startTime));
        disconnect();
    }

    protected abstract List<Object> get();

ブロックAPI使用クラス

基底クラスを継承し、アブストラクトメソッド(get)をブロックAPIを用いて実装しています。

import java.util.List;

import com.couchbase.client.java.kv.GetResult;

public class Sequential extends Base {

    @Override
    protected List<Object> get() {  
        for (String key : keys) {
            GetResult result = collection.get(String.valueOf(key));
            listResults.add(result);
        }
        return listResults;
    }

    public static void main(String[] args) {
        new Sequential().exec();
    }
}

リアクティブAPI使用クラス

基底クラスを継承し、アブストラクトメソッド(get)をリアクティブAPIを用いて実装しています。

import java.util.List;

import com.couchbase.client.java.ReactiveCollection;
import reactor.core.publisher.Flux;

public class Concurrent extends Base {

    @Override
    protected List<Object>  get() {
        ReactiveCollection reactiveCollection = collection.reactive();

        Flux<Object> resultFlux = Flux.fromArray(keys.toArray())
            .flatMap( k -> reactiveCollection.get(String.valueOf(k)));

        List<Object> listResults = resultFlux.collectList().block();
        return listResults;
    }

    public static void main(String[] args) {
        new Concurrent().exec();
    }
}

実行結果

以下は、それぞれのクラスの実行結果です。

同期API

先頭データ:GetResult{content={"key":"0"}, flags=0x2000000, cas=0x165349f2729e0000, expiry=Optional.empty}
末尾データ:GetResult{content={"key":"19999"}, flags=0x2000000, cas=0x165349f307a30000, expiry=Optional.empty}
取得サイズ:20000
経過時間(ミリ秒):1201

非同期API

先頭データ:GetResult{content={"key":"0"}, flags=0x2000000, cas=0x16534a84e29c0000, expiry=Optional.empty}
末尾データ:GetResult{content={"key":"19999"}, flags=0x2000000, cas=0x16534a856f4e0000, expiry=Optional.empty}
取得サイズ:20000
経過時間(ミリ秒):302

MacラップトップにCouchbase Serverをインストールし、同じくラップトップ上でプログラムを実行していますが、私の環境では、ざっと4倍の速度の違いが見られました。

今回のケースでは、取得したデータをリストに納める際にブロック処理しているので、より非ブロック処理を生かすことができるユースケースであれば、さらに高い効率化が望めると思われます。

最後に

今回は、プログラミング内容にフォーカスして説明しました。
少しでも、興味をもたれた方は、今回掲載したソースコードを実際に実行して見られたり、ご自身で、さらに進んだデータ操作に挑んでみていただければ、幸甚です。

Couchbase Serverそのものの説明や導入については、本記事の範囲ではないため、割愛せざるを得ませんでしたが、手始めに動かしてみるということであれば、Couchbase Serverのインストールや管理コンソールからの操作は、拍子抜けするほど簡単ですので、ご関心に応じ、実行していただければと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

委譲(Delegation )について

エンジニアをしていると委譲という言葉がよくでてくるかと思いますが、調べた際には理解したつもりが後々になんだっけ?となる事がしばしばあるので備忘のために纏めていければと思い記事にすることにしました。内容などに間違いなどあれば、ご指摘いただければ光栄です。

・委譲(Delegation )とは

そもそも委譲(Delegation )とは何なのか。委譲とは言葉の通り権利・権限などを他の人・機関に譲ずって任せることを意味しています。プログラムに置き換えて説明すると委譲(delegate)とは、あるオブジェクトがプログラム中でイベントが発生したとき、それに代わって、または連携して処理するオブジェクトのことを意味しています。

なぜ委譲なのか、委譲以外の方法で実装できないのか。

実際委譲を使わなくてもJavaなどのオブジェクト指向プログラミング(OOP)では標準機能として用意されている継承(既存クラスを継承することにより特性、振る舞いを引き継ぐ)を使うことで想定するプログラムを作成することは可能です。しかしながら継承のみでは問題点もあります。
クラスを継承すると、親クラスの変更に子クラスが大きな影響を受けるので変更がしづらくなることがあります。
そのほかにも菱形継承問題です。Javaでは多重継承を言語として許していません。
委譲を使った場合では、2つのクラスは完全に分離しているので、変更の影響が低くなります。また複数のインスタンスを保持することで多重継承の問題にも対応可能です。

委譲の実装サンプル

それではどのように委譲をプログラムに組み込んでいけばいいのか。
委譲は別のクラスをインスタンス変数として保持し、各インスタンスメソッドで、それぞれのメソッドに対する既存クラスのメソッドを呼び出してその結果を返すようにします。
継承で書く場合と、委譲で書く場合のサンプルが以下となります。
比較してみてみましょう。

委譲を使った実装
class RealPrinter { 
    // the "delegate" 
    void print() 
    { 
        System.out.println("The Delegate"); 
    } 
} 

class Printer { 
    // the "delegator" 
    RealPrinter p = new RealPrinter(); 

    // create the delegate 
    void print() 
    { 
        p.print(); // delegation 
    } 
} 

public class Tester { 

    // To the outside world it looks like Printer actually prints. 
    public static void main(String[] args) 
    { 
        Printer printer = new Printer(); 
        printer.print(); 
    } 
} 
継承を使った実装
class RealPrinter { 
    // base class implements method 
    void print() 
    { 
        System.out.println("Printing Data"); 
    } 
}  
    // Printer Inheriting functionality of real printer 
    class Printer extends RealPrinter { 

    void print() 
    { 
        super.print(); // inside calling method of parent 
    } 
} 

public class Tester { 

    // To the outside world it looks like Printer actually prints. 
public static void main(String[] args) 
    { 
        Printer printer = new Printer(); 
        printer.print(); 
    } 
} 
どんな時に使い分けるのか

継承を使ったほうが良いのか、委譲を使ったほうが良いのか、具体的に何を基準にして使い分ければ良いかを以下に記載しています。参考例であるため、絶対ではないと考えて頂ければと思います。
・子に親と同じ役割が期待される場合、親と同様に振る舞えるようになる継承を使うのが良い
クラスBとクラスAとの間に「is-a」関係が存在している場合にだけ適用するべきといわれています。
・子に親と同じ役割が期待されない場合、子にとって親は単なるツールである可能性が高いため委譲を使うのが良い
親クラスが子クラスを含んでいる(例えば子クラスが持つ機能を親クラスが持っている状態。 has-a関係)場合は委譲を適用するべきといわれています。

まとめ

メリット、デメリット

・継承のメリット
継承では基底クラスのメソッドを何もせずに使うことができる。
・継承のデメリット
親クラスの実装が変更された場合に、もろにその変更の影響を受ける。
・委譲のメリット
親クラスに変更があった際に、別クラスを使用するなど継承よりも柔軟性がある。
・委譲のデメリット
わざわざメソッドの呼び出しを実装しなくてはなりません。その為、継承に比べてコードの記述量が多くなることです。
委譲の方がクラス同士の相互作用に幅を持たせることが容易であるため、デザインパターンでは、クラス継承以上に「委譲」が活躍しています。
Adapterパターン、Bridgeパターン、Strategyパターンなど、その他にも色々あります。
今後機会があれば上記のような委譲を利用したデザインパターンをまとめていきたいと思います。

参考文献

Effective Java
https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming)
http://e-words.jp/w/%E3%83%87%E3%83%AA%E3%82%B2%E3%83%BC%E3%83%88.html
https://www.geeksforgeeks.org/delegation-vs-inheritance-java/
https://ikenox.info/blog/inheritance-and-delegation-and-interface/
http://4geek.net/difference-between-inheritance-and-composition/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Qiita 週間LGTM数ランキング【自動更新】

他のタグ

AWS Android Docker Git Go iOS Java JavaScript Linux Node.js PHP Python Rails React Ruby Swift TypeScript Vim Vue.js

集計について

  • 期間: 2020-12-17 ~ 2020-12-24
  • 条件: ストック数が 2 以上の記事

ソースコード:

LGTM 数ランキング

1 位: 段階的に理解する Dependency Injection

Java DI DependencyInjection

14 LGTM
@ts7i さん ( 2020-12-22 21:47 に投稿 )

2 位: よく知らないアプリケーションの性能と戦わないといけないときの防衛術(中編)

Java Linux

14 LGTM
@nfujita55a さん ( 2020-12-19 11:38 に投稿 )

3 位: Mavenでたまに役立つ小ネタ

Java Maven

8 LGTM
@backpaper0@github さん ( 2020-12-20 21:38 に投稿 )

4 位: 純粋関数型Ja婆婆

Java 関数型プログラミング 湯婆婆

5 LGTM
@BlueRayi さん ( 2020-12-20 14:24 に投稿 )

5 位: DockerでWildFlyを試す

Java Maven Wildfly JakartaEE

2 LGTM
@backpaper0@github さん ( 2020-12-20 17:32 に投稿 )

6 位: 【Java】Stream API を使ってListを操作する方法

Java 初心者

2 LGTM
@t_t238 さん ( 2020-12-17 16:26 に投稿 )

7 位: Spring Boot Mybatis SQLのスネークケースカラムとキャメルケースのマッピング

Java spring MyBatis spring-boot

1 LGTM
@HyunwookPark さん ( 2020-12-22 14:36 に投稿 )

8 位: 【Java】現在日時の取得と表示形式の指定方法

Java 初心者 新卒エンジニア

1 LGTM
@t_t238 さん ( 2020-12-21 09:16 に投稿 )

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Qiita 週間 LGTM 数ランキング【自動更新】

他のタグ

AWS Android Docker Git Go iOS Java JavaScript Linux Node.js PHP Python Rails React Ruby Swift TypeScript Vim Vue.js

集計について

  • 期間: 2020-12-17 ~ 2020-12-24
  • 条件: ストック数が 2 以上の記事

ソースコード:

LGTM 数ランキング

1 位: 段階的に理解する Dependency Injection

Java DI DependencyInjection

14 LGTM
@ts7i さん ( 2020-12-22 21:47 に投稿 )

2 位: よく知らないアプリケーションの性能と戦わないといけないときの防衛術(中編)

Java Linux

14 LGTM
@nfujita55a さん ( 2020-12-19 11:38 に投稿 )

3 位: Mavenでたまに役立つ小ネタ

Java Maven

8 LGTM
@backpaper0@github さん ( 2020-12-20 21:38 に投稿 )

4 位: 純粋関数型Ja婆婆

Java 関数型プログラミング 湯婆婆

5 LGTM
@BlueRayi さん ( 2020-12-20 14:24 に投稿 )

5 位: 「ソースないけどバグ直して」をランタイムで可能にするJavaバイトコードプログラミング

Java プログラミング Docker JVM ハンズオン

2 LGTM
@cilto さん ( 2020-12-25 06:30 に投稿 )

6 位: DockerでWildFlyを試す

Java Maven Wildfly JakartaEE

2 LGTM
@backpaper0@github さん ( 2020-12-20 17:32 に投稿 )

7 位: 【Java】Stream API を使ってListを操作する方法

Java 初心者

2 LGTM
@t_t238 さん ( 2020-12-17 16:26 に投稿 )

8 位: VSCodeで作るJava開発環境&Spring Bootアプリケーション入門

Java 開発環境 開発環境構築 SpringBoot VSCode

1 LGTM
@takumi_links さん ( 2020-12-24 00:01 に投稿 )

9 位: Spring Boot Mybatis SQLのスネークケースカラムとキャメルケースのマッピング

Java spring MyBatis spring-boot

1 LGTM
@HyunwookPark さん ( 2020-12-22 14:36 に投稿 )

10 位: 【Java】現在日時の取得と表示形式の指定方法

Java 初心者 新卒エンジニア

1 LGTM
@t_t238 さん ( 2020-12-21 09:16 に投稿 )

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

うっかりgradlew publishでReceived status code 422 from server: Unprocessable Entityを喰らう

はじめに

人生初のGithub Packagesへのデプロイに挑戦していた。

とかを読みながら

apply plugin: 'maven-publish'

publishing {
    repositories {
        maven {
            name = "GitHubPackages"
            url = 'https://maven.pkg.github.com/yumetodo/LogisticsPipes'
            credentials {
                username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")
                password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")
            }
        }
    }

    publications {
        gpr(MavenPublication) {
            artifactId = archivesBaseName
            from(components.java)
        }
    }
}

を追記してgradlew publishすると

> Task :publishGprPublicationToGitHubPackagesRepository FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':publishGprPublicationToGitHubPackagesRepository'.
> Failed to publish publication 'gpr' to repository 'GitHubPackages'
   > Could not PUT 'https://maven.pkg.github.com/yumetodo/LogisticsPipes/rs485/logisticspipes/LogisticsPipes/0.9.4.local/LogisticsPipes-0.9.4.local.jar'. Received status code 422 from server: Unprocessable Entity

Received status code 422 from server: Unprocessable Entityを喰らってしまった。どうすればいいのさ?

解決策を探す

Gradle Maven deploy failing with 422 Unprocessable Entity - Code to Cloud / GitHub Packages - GitHub Support Community

artifacId should be lowercase to be able to push to github packages

artifacIdってなんですか・・・?

第53章 Mavenプラグイン#53.6.4. MavenのPOM生成

archiveTask.baseNameをデフォルト以外の値に設定したときは、uploadTask.repositories.mavenDeployer.pom.artifactIdを同じ値に設定することを忘れないでください。 さもないと、近いうちに、同じビルド内の他プロジェクトで生成されたPOMから、プロジェクトが誤ったartifact IDで参照されてしまうかもしれません。

uploadTaskじゃないけど、そういえばarchivesBaseNameは変更している・・・

Maven Publish Plugin#Identity values in the generated POM

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'org.gradle.sample'
            artifactId = 'library'
            version = '1.1'
            from components.java
        }
    }
}

なるほど、そこにartifactIdできるのか

結論

apply plugin: 'maven-publish'

publishing {
    repositories {
        maven {
            name = "GitHubPackages"
            url = 'https://maven.pkg.github.com/yumetodo/LogisticsPipes'
            credentials {
                username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR")
                password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")
            }
        }
    }

    publications {
        gpr(MavenPublication) {
+           artifactId = archivesBaseName
            from(components.java)
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

  Java PDFをExcelで保存  

 

前にJavaでExcelをPDFに変換する方法を紹介しましたが、逆にPDFをExcelで保存したいと思うならどうしますか?今回はSpire.PDF for Javaを通して見せて差し上げましょう。

下準備

1.E-iceblueの公式サイトからFree Spire.PDF for Java無料版をダウンロードしてください。

f:id:lendoris:20201223153847p:plain

2.IDEを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire.PDF.jarを参照に追加してください。

 

f:id:lendoris:20201223153900p:plain

元のファイル

f:id:lendoris:20201223153936p:plain

import com.spire.pdf.FileFormat;
import com.spire.pdf.PdfDocument;

public class ToXLS {
    public static void main(String[] args) {
        //PdfDocumentを作成します。
        PdfDocument pdf = new PdfDocument();
        //PDFファイルをロードします。
        pdf.loadFromFile("C:\\Users\\Test1\\Desktop\\Sample.pdf");
        //Excelで保存します。
        pdf.saveToFile("output/ToXLS.xlsx", FileFormat.XLSX);
    }
}

 完成例

f:id:lendoris:20201223153959p:plain

 

 

 

 

 

 

 

 

 

 

 

 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java, Scala】ImageIOで画像のリサイズ

コード全体

import java.io.File
import java.awt.image.BufferedImage
import javax.imageio.ImageIO

object ImageUtil {
  def resize(file: File): Unit = {
    val original = ImageIO.read(file)

    // 元ファイルの縦横サイズ(pixel)を取得
    val originalWidth = original.getWidth.toDouble
    val originalHeight = original.getHeight.toDouble

    // 長辺を1とした縦横比を取得
    val (widthScale, heightScale) =
      if(originalWidth > originalHeight) (1d, originalHeight / originalWidth)
      else (originalWidth / originalHeight, 1d)

    // 長辺を200pxとして、縦横比を保ちリサイズ後の画像サイズを決定
    val width = (200d * widthScale).toInt
    val height = (200d * heightScale).toInt

    val image = new BufferedImage(width, height, original.getType)

    val scaled = original.getScaledInstance(width, height, java.awt.Image.SCALE_AREA_AVERAGING)

    image.getGraphics.drawImage(scaled, 0, 0, width, height, null)

    ImageIO.write(image, "jpeg", new File("/tmp/resized.jpeg"))
  }
}

ざっくり解説

画像サイズの計算は問題ないと思うので、画像生成部分だけ。

val image = new BufferedImage(width, height, original.getType)

ここで画像サイズが決まる。まっさらなキャンバスを作るイメージ。
例ではリサイズ後の画像の大きさに合わせているので、余白なしの画像になる。
もし余白ありの正方形にしたい場合はこんな感じ。

// 長辺の長さを1辺とした正方形を作る
val scale = Math.max(width, height)
val image = new BufferedImage(scale, scale, original.getType)

次に、元画像をリサイズした画像データを取得する。

val scaled = original.getScaledInstance(width, height, java.awt.Image.SCALE_AREA_AVERAGING)

そして最初に作ったキャンバスにリサイズ後の画像を貼り付ける。

image.getGraphics.drawImage(scaled, 0, 0, width, height, null)

0, 0はそれぞれキャンバスのx座標とy座標。指定座標を基準にリサイズ後画像をペーストするイメージ。
余白がある時は座標を調整すれば画像を左右上下に寄せたり中央にしたりできる。

最後にファイル出力して終わり。

ImageIO.write(image, "jpeg", new File("/tmp/resized.jpeg"))

おまけ: 余白の色を変える

余白がある場合、余白部分の色はデフォルトだと黒になる。
色を変える場合は、drawImageの前に全体を好きな色で塗ってやればよい。

// 余白を白にする
image.getGraphics.setColor(Color.WHITE)

// 座標(0, 0)から、画像の縦横幅いっぱいに指定色で埋める
image.getGraphics.fillRect(0, 0, image.getWidth, image.getHeight)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

  Java  PowerpointをXPSに変換

今日はJavaでPowerpointをXPSに変換する方法を紹介します。今回はSpire.Presentation for Javaというライブラリが必要となります。これは無料でMicrosoft Office系のソフトをダウンロードせずに使用できます。

では、行きましょう

下準備

1.E-iceblueの公式サイトからFree Spire. Presentation for Java無料版をダウンロードしてください。

2.IDEを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. Presentation.jarを参照に追加してください。

元のファイル

 

f:id:lendoris:20201223152051p:plain

import com.spire.presentation.*;

public class PPTtoXPS {
    public static void main(String[] args) throws Exception{

    //presentation objectを作成します。
    Presentation ppt = new Presentation();

    //ファイルをロードします。
    ppt.loadFromFile("Sample.pptx");

    //XPSで保存します。
    ppt.saveToFile("toXPS.xps", FileFormat.XPS);
ppt.dispose();

    }
}

完成例

f:id:lendoris:20201223152106p:plain

 

 

 

 

 

 

 

 

 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リクエストスコープとは何ぞや?イメージ解説

こんにちは、Takeです。

今回は、Javaの文法も一通り終えてターミナル上のみで動くコード作成から、
サーバーやブラウザを利用する、簡易的な「Webアプリケーション」の開発まで出来るようになったJava初学者(私を含む)の方々向けの記事になります。

サーブレットで書いたインスタンスをJSPに渡したい時によく使われるものが「リクエストスコープ」と呼ばれるものです。

見たことがある人も多いと思いますが、「request.setAttribute()」「request.getAttribute()」というやつです。

この「リクエストスコープ」というものについてなのですが、私は以前まで「変数のスコープ」と似た様なものだと認識していました。とはいえ、実際は似た様なものなのですが。

ちょっとだけイメージを変えた方がリクエストスコープについて理解出来やすいのではないか?と思い、この記事を書くことにしました。

まず、変数のスコープと言うのは、例えば

public class Example {
public static void main(String[] args) {

 //ここから
 int i = 1;

 //ここまでの範囲を変数「i」のスコープと呼ぶ

}
}

上記のように、「とあるコード内のここからここまでのブロック内で有効な値」というものでした。

同様に、「リクエストスコープ」についても、

public class ExampleServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
~~~~
try {

  //ここから
  ~~~~
  ~~~~
  ~~~~
  request.setAttribute("Hoge", hoge);

  ~~~~
  ~~~~
  ~~~~
  //ここまでがリクエストスコープの範囲??

}
}
}
}

上記の認識(全く違う)でいると、なぜ上のスコープ内なのに、JSPという別のソースコードから取得出来るんだ?と思う人たちもいるかもしれません。

なので、私なりに考えたリクエストスコープのイメージをお伝えします。

リクエストスコープは、JSPやサーブレットとは別の、新しい「箱」です。

新しい「箱」に、getAttribute()やsetAttribute()メソッドを使って設定したり取得したりするのです。

「サーブレット」からリクエストスコープ内に

request.setAttribute("Hoge", hoge); というコードで格納


リクエストスコープ内に

"Hoge"という属性名でhogeインスタンスを格納できた


「JSP」から
request.getAttribute("Hoge"); のコードで取得

文章が煩雑で分かりにくいと思いますが、

「サーブレットからsetAttribute()メソッドを使って、

リクエストスコープという新しい『箱』にインスタンスを格納、

JSPからgetAttribute()メソッドを使って、その『箱』からインスタンスを取り出す」

というイメージで宜しいのではないでしょうか?

今回は以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

多項式補間を使用して強震モニタ画像から数値データを決定する

この記事は 地震界隈 Advent Calendar 2020 20日目の記事です。個人的な事項のために数日遅れています。お詫び申し上げます。
長い記事ですが、喜ばれることを願っています :relaxed:

前書き

強震モニタデータを使用するサードパーティのソフトウェアでは、特定の数の機能がGIF画像から得られた数値の活用に依存しています。 実際、情報は特定のピクセル位置で特定の色にエンコードされるため、ソフトウェアには、ピクセルを観測点に関連付けるための観測点データベースだけでなく、任意の色を数値に変換する方法も必要になります。

適用可能な使用法は、計測震度値の表示、または地震検出アルゴリズムですらあります。
リアルタイム震度、最大加速度、最大速度など、データのいくつかのフィードを見つけることができ、それぞれが異なるスケールを使用しています。
これまで、防災科学技術研究所(NIED)は、値を見つけるための公式の方法や、データチャネルの数値を特徴とするAPI(JSON形式など)を開示していなかったため、他の方法に依存する必要があります。

値を赤、緑、青 (R、G、B)の値の組み合わせに直接関連付けるテーブルを使用するなど、値を取得する方法はいくつかあります (ingen084の記事をお勧めします) 。
この記事では、区分的多項式補間を使用して、任意の色を任意のスケールの任意の値に変換する方法について説明します。

目的

EEW Event Viewerの開発時(JQuakeの話を参照)、次の画像形式からデータを抽出するための統一された方法が必要でした:

  • リアルタイムデータのGIF
  • サーバーの古いデータのPNG(当時はデータがまだ利用可能だったため、東日大震災など)
  • このリンク からアクセスできる強震モニタウェブサイトのビデオからフレームごとにPNGに変換されたAVIフィルム

Capture d’écran 2020-12-20 181512.png

圧縮により色がフォーマットごとにわずかに異なるという事実を考慮して、色から値への変換にテーブルを使用せず、多項式補間を使用することを選択しました。

多項式補間とは?

関数式が不明で、入力と出力の観測が可能な場合、関数式を近似する1つの方法は、多項式関数を使用することです。 . 目標は、未知の関数への将来の入力に対して、出力を提供できるようにすることです。

たとえば、次の図では、特定の関数に整数入力(赤)からの7つの既知の出力があります。 ただし、出力値3.55(緑色)を知りたい。 このケースは、わずかにずれた色の値を取得する場合(たとえば、ファイル形式や圧縮アルゴリズムを変更する場合)に満たすことができます:

Capture d’écran 2020-12-21 0136252.png

赤い点のセットに対して多項式補間を実行すると、最適な多項式関数の係数$ \{ a_n,\dots,a_0 \} $が得られ、次のように表されます。 :

f(x) = a_n x^n + a_{n-1}x^{n-1} + \dots + a_1 x + a_0

ここで、$n$は多項式の次数です。 ここでは、次数7の多項式が使用され、その曲線は青色で示されています。

Capture d’écran 2020-12-21 013625.png

これで、見つかった多項式に3.55を入力することで、3.55の出力を知ることができます。 結果は-0.4です。

Capture d’écran 2020-12-21 013625 - Copie.png

提示された例は、多項式と小さな点のセットの間で完全に一致する場合でした。 ただし、はるかに多くのポイントが満たされる場合(> 50)、非常に高度な多項式を使用することは過度に高いになりますので、通常、次数を10未満に保ち、許容誤差を受け入れます。 不明関数の側面が大幅に変化したときに入力を別々の間隔に分割することも良い解決策です。

問題の定式化

問題を振り返ると、どのチャネルでも受信したすべてのデータに1つのポイントが共通していることがわかります。つまり、カラースケールは常に同じです。したがって、私たちの目的は、色(R、G、B)をスケール上の位置(たとえば0から1まで)に変換する関数を知ることです。この位置から、スケールの位置を受信データのタイプに変換する式を知って、目的のデータチャネルの任意の値を計算できます。

Diagram.png

多項式補間ステップ

前に述べたように、色を位置に変換する関数を見つける必要があります。 この目的のために、強震モニタで見つかったカラースケール画像の分析が行われます。この目的のためにMatlabを使用しました。
ピクセル(6,18)から(6,296)にまたがる垂直線が抽出されます。 次に、補間用に279の異なる色があり、ピクセル(6,296)は位置0.0に対応し、(6,18)は位置1.0に対応します。

R、G、Bを使用すると

抽出されたピクセルラインの赤、緑、青の成分の関数で位置をプロットすると、次の図が得られます:

rgb.png

多項式関数を当てはめるには、横軸の1つの値が最大で1つの縦座標の値を与えることが重要です。そうでない場合、それを関数と呼ぶことはできません。 緑の成分は関数ではなく、赤の関数は約180の値(0.22から0.4までの位置をカバー)まで明確に定義されていることがわかります。青のコンポーネント(0.15から0.4の位置をカバー)の場合は210までです。 また、180前後の外れ値は目盛りによるものであることに注意してください。
結論として、位置を取得するためにR、G、B値に対して多項式補間を実行することは不可能です。

H、S、Vを使用すると

スケールの色が虹の色のように(青から赤に)直線的に進化していることに気づいたかもしれません。実際、それは厳密に減少する色相で進化しています。秘訣は、RGBをHSV色空間に変換して、悪用可能な関数を取得することです。

hsv.png
1を超えると、H成分(色相)が明確に定義され、0から0.9までの位置値をカバーしていることがわかります。 幸い、H成分が1未満の場合、V成分は明確に定義され、0.9から1の位置をカバーします。

お知らせ:記事の残りの部分では、H、S、Vの値は0から255ではなく0から1の間にあると見なします。
JavaとMatlabではそのような値を返すという事実によって説明されます。 以下の各補間は、これらの値を考慮して実行されます。
まず、0から0.6までの位置をカバーするH成分の部分を使用した補間の結果です:

Capture d’écran 2020-12-22 012723.png
図の左側には、計算された係数があります(前の式の$ a_6 $から$ a_0 $に対応するp_1からp_7)。 右上のサブ図は、近似された多項式を持つ点のセットを示しています。 次数6の多項式が選択され、赤の外れ値が除外されています。 残差(右下の図)は-0.0075から0.008の間に含まれていることがわかります。

次に、0.6から0.9までの位置をカバーするH成分の部分が補間されます:

Capture d’écran 2020-12-22 012950.png

次数4の多項式は、絶対値で0.004未満の残余誤差を持つのに十分であることがわかります。

最後に、0.9から1.0の間の位置をカバーするV値が、3番目の補間で考慮されます:

Capture d’écran 2020-12-22 020427.png

外れ値を除外し、多項式の次数を2にすると、残差は非常に小さくなります(0.001未満)。

その後の作業では、NIEDサイトの過去の地震データで測定された値との不一致が見られました。 多項式係数は、これらの観測値に一致するように調整されています。
JQuakeで使用されるコードは次のとおりです:

public static double color2position(float hsv[]){

    // Input : color in hsv space, float values between 0 and 1

    double p = 0;
    float h = hsv[0];
    float s = hsv[1];
    float v = hsv[2];

    // Check if the color belongs to the scale

    if (v > 0.1 && s > 0.75){

        if (h > 0.1476){
            p = 280.31*Math.pow(h,6) - 916.05*Math.pow(h,5) + 1142.6*Math.pow(h,4) - 709.95*Math.pow(h,3) + 234.65*Math.pow(h,2) - 40.27*h + 3.2217;
        }

        if (h <= 0.1476 && h > 0.001){
            p = 151.4*Math.pow(h,4) -49.32*Math.pow(h,3) + 6.753*Math.pow(h,2) -2.481*h + 0.9033;
        }

        if (h <= 0.001){
            p =   -0.005171*Math.pow(v,2) - 0.3282*v + 1.2236;
        }
    }

    if (p < 0){
        p = 0;
    }

    return p;
}

これで3つの補間ができたので、位置から数値への変換を処理できます。

位置から数値への変換

このステップは、取得した位置 $p$ を任意のデータチャネルの数値に変換することで構成されます。 そうするための方程式を与えます:

震度

I = 10p-3

最大加速度

PGA = 10^{5p-2}

最大速度

PGV = 10^{5p-3}

最大変位

PGD = 10^{5p-4}

対数目盛に関する補足

対数目盛(PGA、PGV ...)に向けた変換式の作成中に、 10の累乗ごとに1、2、5の間の目盛りが等間隔に配置されていることに驚きました。 私は値をそのように一貫して投影するための数学的に有効な方法がわからないことがわかりました。 値は、表示のみを目的として等間隔で表示されていると思います。 とにかく、上で見たように、10進法の対数目盛を使用しました(1を参照)。

正しく表示されている場合、スケールは次のようになります:

scales.png

2と5の間のスペースは、1と2の間のスペースよりも大きいことがわかります。
それほど重要ではないことは承知していますが、そうしようとした人々の考えに答えたいと思います :wink:

結論

この記事では、強震モニタからGIFまたはPNG画像にエンコードされたデータフィードの数値を取得する方法を説明しました。 この目標を達成するために、多項式補間を使用しました。 テーブルルックアップなどの他の方法もあります。必要なCPUは少なくなりますが、画像形式の変更による色のわずかな変化に対する堅牢性は低くなります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

V-forとV-showで特定の要素だけclassをつける

やりたいこと

DBからひっぱってきたデータをvueのコンポーネントの中のdataにセットし、それをv-forで表示させているとします。
DBからひっぱってきたデータのいずれかに真偽値を設定するなどしておけばv-forの中で動きの変化はつけることができますが、
「そのコンポーネントの中だけのdataを追加し、v-forの中の特定のコンテンツだけclassをつける」などの変化をつけたい場合にやる方法。

記述内容

//描写部分

<template>
 <div>
  <div v-for="task in tasks" :key="task.id">
   <div class="task__title">{{task.title}}</div>
  //例えばこのtask.bodyだけ、条件に応じてis-activeclassをつけたい場合
   <div class="task__body" v-bind:class="{'is-active':active === task}" >{{task.body}}
 </div>
 <button v-on:click="addActive(task)">bodyにclassをつける</button>

 </div>
</template>


<script>
export default {
 data(){
         return{
                tasks:[ ],//この中にdbからひっぱってきたデータが入っている
                active:null   
            }
        },
 methods:{
          addActive(task){
           if(this.active === task){
             this.active = null;
           }else{
            this.active = task;
           }
          }
        }
}
</script>

こんな感じで、v-forの中の値(task)を引数として渡すことで、そのactiveをtaskにすることができる。
これにより特定のtaskだけにclassを適用できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Web Apps 性能系トラブルシューティング入門

はじめに

本記事は Microsoft Azure Tech Advent Calendar 2020 の投稿です。

Web サイトを運用する上でトラブルシューティングは切っても切り離せないものだと思います。そのため、トラブルシューティングが容易なサイトを作ることも、運用を踏まえますと大切になってきます。Web Apps では「高度なツール」ブレードから kudu を起動することによって、Web コンソールからコマンドを入力したり、プロセスエクスプローラを開いたりすることができます。これらを活用することによってトラブルシューティングが容易なサイト運用ができると思っています。

本記事では Java Web Apps を運用するうえで便利なトラブルシューティングの準備や方法を紹介したいと思います。なお、Java Web Apps の Windows 環境をターゲットとさせていただきますが、ここで紹介した方法は Linux 環境でも応用ができますので、ご利用いただけると嬉しいです。

サンプルアプリのデプロイ

せっかくなので、以下の公式ドキュメントのクイックスタートにある Java アプリの作成手順を使ってみましょう。

クイック スタート:Azure App Service で Java アプリを作成する
https://docs.microsoft.com/ja-jp/azure/app-service/quickstart-java?tabs=javase&pivots=platform-windows

以下のコマンドでリポジトリをクローンし、azure-webapp-maven-pluginconfigで、Windows, Java11, Java SEを選択します。

git clone https://github.com/spring-guides/gs-spring-boot
cd gs-spring-boot/complete
.\mvnw com.microsoft.azure:azure-webapp-maven-plugin:1.12.0:config

以下のコマンドでビルド & Web Apps へのデプロイを実施します。

az login
.\mvnw package azure-webapp:deploy

Web サイトにアクセスします。

image.png

たったこれだけの手間でサイトを用意することができました。

トラブルが起きることを踏まえた設定

スタックトレースがアプリケーションのログに出ている場合は、エラー原因を絞り込めるため解析が比較的容易です。

その一方で、性能が一時的に低下したり、Out of Memory (以下、OoM) が発生したりした際に、事象が発生した時点の情報がなければ、原因を特定することは極めて難しいです。
他のプログラミング言語でもあるとは思いますが、Java ではメモリにプロファイリングデータを蓄積し手動や終了時にファイル出力する機能 (JFR: Java Flight Recorder) や、Out of Memory 時にヒープダンプをする機能があり、事象が発生した時点の情報を取得することができます。
なお、メモリに蓄積されたプロファイリングデータは、指定されたメモリサイズの上限を超えると失われたるためご留意ください。

Web Apps のアプリケーション設定におきまして JAVA_OPTS から Java の起動引数を追加することができます。
JFRを有効化し、OoM 時にヒープダンプを実施する JAVA_OPTS の設定値は以下になります。

-XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=C:\home\LogFiles\catalina.hprof -XX:StartFlightRecording=dumponexit=true

「高度なツール」ブレードから kudu を起動し、以下のスクリーンショットのように Debug Consoleで jcmd を発行し、実際に上記起動引数が設定されているか見てみましょう。
なお、jcmd を使うと java プロセスの様々な情報を取得できるので、トラブルシューティングにご活用いただければと思います。
また、java プロセスの PID はProcess exploreから確認可能です。

image.png

無事設定も終わりましたので、無理やりトラブルを起こして解析してみましょう。

Out of Memory エラーの解析

ヒープダンプを取得

サンプルアプリに以下のクラスを追加し、再度デプロイしましょう。
/oomにアクセスをし続けると、そのうち無事? OoM が発生します。

package com.example.springboot;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class OoMController {

    private static final List<String> POOL = new ArrayList<>();

    @RequestMapping("/oom")
    public String feed() {
        char[] tmp = new char[200*1024*1024];
        Arrays.fill(tmp, 'a');
        POOL.add(new String(tmp));

        return "Feed Me!";
    }

}

前述の設定のおかげで、OoM 時のヒープダンプが以下のようにC:\home\LogFiles\catalina.hprofに存在します。

image.png

catalina.hprof のダウンロードボタンを押してヒープダンプファイルを取得しましょう。

ヒープダンプの解析

Zulu Mission Control や VisualVM といったツールを使ってヒープダンプを解析してみましょう。私が VisualVM が好きなので、VisualVM で解析したいと思います。

catalina.hprofを開くとDominators by Retained Size で明らかに大量にヒープを消費しているクラスがあると思います。右クリックで Open in New Tab で開いてみましょう。
image.png

以下の画像のように大量にヒープを使っている ArrayList インスタンスのフィールドやそれを参照しているクラス (OoMController) を確認できました。
image.png

これで問題が特定できたので、あとはメモリを使わないようにソースを修正する流れになります。

このように OoM が発生してしまった時でも、ヒープダンプがあればスムーズにヒープに関するトラブルシューティングが可能です。

リクエストの応答が遅い

JFR データの取得

性能が出ないときには JFR を使うとボトルネックとなっている処理が分かります。
サンプルアプリに以下のクラスを追加し、OoM の時のように無理やり問題を発生させましょう。
/sleepにアクセスすると嫌になるくらい遅く応答がかかります。

package com.example.springboot;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class SleepController {

    @RequestMapping("/sleep")
    public String sleep() {
        try {
            Thread.currentThread().sleep(10 * 1000);
        } catch (InterruptedException ex) {
            // Nothing to do.
        }

        return "I'm not lazy..";
    }

}

kudu から以下のコマンドでメモリにある JFR データをダンプし、ヒープダンプの時のようにJFR ファイルをダウンロードしましょう。
image.png

JFR データの解析

VisualVMでプロファイリングデータを解析しましょう。
JFRファイルを開いたら、Samplerタブを表示し、CPU ボタンを押下してください。
スレッドアイコンで http-nio--exec- のように Tomcat のスレッドをフィルタするとポイントが絞れます。
いくつかプラスボタンを押して、コールグラフを表示してみましょう。波動拳みたいに深い呼び出し階層ですが、Total Timeが多い sleep まで探し当てることができました。
image.png
image.png

このように、JFRデータを使うとボトルネックとなっている処理を見つけ出すことができます。

さいごに

アプリケーションが遅かったり、突然ダウンしたりした際にフライトレコーダーの情報やヒープダンプは重宝します。
単純なサンプルの紹介となってしまいましたが、今回紹介したトラブルシューティングの準備や方法が深刻な状況での助けになれば私も嬉しく思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む