20200915のJavaに関する記事は13件です。

PowerMockのWhiteBoxを使ってみる

目次

  1. PowerMockを使うための環境構築手順
  2. PowerMockにおけるモックとスパイ
  3. PowerMockでstaticメソッドをモック化する
  4. PowerMockでコンストラクタをモック化する
  5. PowerMockでprivateメソッドをモック化する
  6. PowerMockのWhiteBoxを使ってみる ←今ここ
  7. PowerMockでstaticイニシャライザを無効化する

概要

Javaではリフレクションという機能を用いて可視性を操作することで、privateなフィールドの値を取得したり、privateメソッドにアクセスしたりできます。

PowerMockではWhiteBoxクラスにてこれらの機能が提供されています。(内部ではリフレクションを使用している。)

対象クラス

SampleEm.java
public class SampleEm {

    private String field = null;

    public SampleEm(String filed) {
        this.setField(filed);
    }

    public String getField() {
        return field;
    }

    private void setField(String filed) {
        this.field = filed;
    }
}

使用例

privateフィールドの値を取得する

getInternalState(インスタンス, フィールド名文字列)

@Test
public void test_getInternalState() throws Exception {
    // 準備
    SampleEm em = new SampleEm("value");
    // privateフィールドの値を取得する
    String field = Whitebox.getInternalState(em, "field");
}

privateフィールドに値を設定する

setInternalState(インスタンス, フィールド名文字列, 設定値)

@Test
public void test_setInternalState() throws Exception {
    // 準備
    SampleEm em = new SampleEm("value");
    // privateフィールドに値を設定する
    Whitebox.setInternalState(em, "field", "newValue");
}

privateメソッドを呼び出す

invokeMethod(インスタンス, メソッド名文字列, 引数, ...)

@Test
public void test_invokeMethod() throws Exception {
    // 準備
    SampleEm em = new SampleEm("value");
    // privateメソッドを呼び出す
    Whitebox.invokeMethod(em, "setField", "newValue");
}

他にも様々なメソッドが提供されています。

リフレクションを使ったprivateフィールド/メソッドへのアクセスを自前のコードで行っても良いのですが、Whiteboxを利用することでテストコードがシンプルに保てるのがメリットだと思います。

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

PowerMockでprivateメソッドをモック化する

目次

  1. PowerMockを使うための環境構築手順
  2. PowerMockにおけるモックとスパイ
  3. PowerMockでstaticメソッドをモック化する
  4. PowerMockでコンストラクタをモック化する
  5. PowerMockでprivateメソッドをモック化する ←今ここ
  6. PowerMockのWhiteBoxを使ってみる
  7. PowerMockでstaticイニシャライザを無効化する

概要

PowerMockを使えば、privateメソッドの戻り値を任意の値に設定したり、例外を返すようにしたりできます。

UseUtilityクラス(テスト対象クラス)から呼び出すUtilityクラス(モック化クラス)のprivateメソッドをモック化する想定です。

テスト対象クラス

JUnitから呼び出すテスト対象のクラスです。

メソッド内でUtilityクラスのpublicメソッドを呼び出しています。

UseUtility.java
public class UseUtility {

    public String getMessage(String id) throws Exception {

        Utility utility = new Utility();

        Map<String, String> userInfo = utility.getUserInfo(id);

        String message = id + ":" + userInfo.get(id);

        return message;
    }
}

モック化クラス

publicメソッド内部でprivateメソッドを呼び出しており、これをモック化したい。

Utility.java
public class Utility {

    public Utility() {
        // 省略
    }

    public Map<String, String> getUserInfo(String id) throws Exception {

        this.connectDatabase("env");

        Map<String, String> userInfo = new HashMap<>();
        userInfo.put(id, this.getName(id));

        return userInfo;
    }

    private String getName(String id) throws Exception {
        // 未実装の想定
        return "";
    }

    private void connectDatabase(String env) {
        // 実行したくない処理の想定
    }
}

テストクラス

テスト対象クラスを呼び出すJUnitです。

JUnitでPowerMockを使用するために、@RunWithにはPowerMockRunnerを指定します。

@PrepareForTestには、モック化するクラスだけでなく、コンストラクタを呼び出す側のクラスも書いておく必要がある点に注意。

UseUtilityTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Utility.class, UseUtility.class })
public class UseUtilityTest {
    // 省略
}

使用例

doReturn

privateメソッドに、戻り値を設定する。

モック化するには、when(モックオブジェクト, privateメソッド名の文字列, privateメソッドの引数, ...)とする。

@Test
public void test_doReturn() throws Exception {
    // 準備
    String id = "0001";
    String expected = "0001:TestUser";

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドの戻り値を設定
    PowerMockito.doReturn("TestUser").when(spy, "getName", anyString());

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(id);

    // 結果確認
    assertEquals(expected, actual);
}

doThrow

privateメソッドで、例外を送出する。

@Test(expected = Exception.class)
public void test_doThrow() throws Exception {
    // 準備
    String id = "0001";
    Exception expected_exception = new Exception("error!");

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドで返す例外を設定
    PowerMockito.doThrow(expected_exception).when(spy, "getName", anyString());

    // 実行
    UseUtility obj = new UseUtility();
    obj.getMessage(id);
}

doCallRealMethod

privateメソッドに戻り値や例外設定せずに実物のメソッドを呼び出す。

@Test
public void test_doCallRealMethod() throws Exception {
    // 準備
    String id = "0001";
    String expected = "0001:";

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドで実物を呼び出すよう設定
    PowerMockito.doCallRealMethod().when(spy, "getName", anyString());

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(id);

    // 結果確認
    assertEquals(expected, actual);
}

doNothing

privateメソッドで何もしたくない場合に使用する。

@Test
public void test_doNothing() throws Exception {
    // 準備
    String id = "0001";
    String expected = "0001:";

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドで何もしないよう設定
    PowerMockito.doNothing().when(spy, "connectDatabase", anyString());

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(id);

    // 結果確認
    assertEquals(expected, actual);
}

thenReturn

privateメソッドに、戻り値を設定する。

@Test
public void test_thenReturn() throws Exception {
    // 準備
    String id = "0001";
    String expected = "0001:TestUser";

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドの戻り値を設定
    PowerMockito.when(spy, "getName", anyString()).thenReturn("TestUser");

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(id);

    // 結果確認
    assertEquals(expected, actual);
}

thenThrow

privateメソッドで、例外を送出する。

@Test(expected = Exception.class)
public void test_thenThrow() throws Exception {
    // 準備
    String id = "0001";
    Exception expected_exception = new Exception("error!");

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドで返す例外を設定
    PowerMockito.when(spy, "getName", anyString()).thenThrow(expected_exception);

    // 実行
    UseUtility obj = new UseUtility();
    obj.getMessage(id);
}

thenCallRealMethod

privateメソッドに戻り値や例外設定せずに実物のメソッドを呼び出す。

@Test
public void test_thenCallRealMethod() throws Exception {
    // 準備
    String id = "0001";
    String expected = "0001:";

    // スパイ化
    Utility spy = PowerMockito.spy(new Utility());
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(spy);
    // スパイ化したクラスのprivateメソッドで実物を呼び出すよう設定
    PowerMockito.when(spy, "getName", anyString()).thenCallRealMethod();

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(id);

    // 結果確認
    assertEquals(expected, actual);
}

privateメソッドの検証

PowerMockでは、モック化したprivateメソッドの検証のため、verifyPrivateが提供されています。

Mockitoのtimes,atLeast,asLeastOnce等を使用して、呼び出し回数を検証できます。

verifyPrivate([モックオブジェクト])はverifyPrivate([モックオブジェクト], times(1))と同義。

// 1回呼ばれたことを確認
PowerMockito.verifyPrivate(spy).invoke("getName", anyString());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PowerMockでコンストラクタをモック化する

目次

  1. PowerMockを使うための環境構築手順
  2. PowerMockにおけるモックとスパイ
  3. PowerMockでstaticメソッドをモック化する
  4. PowerMockでコンストラクタをモック化する ←今ここ
  5. PowerMockでprivateメソッドをモック化する
  6. PowerMockのWhiteBoxを使ってみる
  7. PowerMockでstaticイニシャライザを無効化する

概要

PowerMockを使えば、インスタンス化した際に任意のオブジェクトを返却するようにしたり、例外を返すようにしたりできます。

UseUtilityクラス(テスト対象クラス)から呼び出すUtilityクラス(モック化クラス)のコンストラクタをモック化する想定です。

テスト対象クラス

JUnitから呼び出すテスト対象のクラスです。

メソッド内でUtilityクラスをインスタンス化しており、この時返却するオブジェクトや例外を任意のものに設定したい。

UseUtility.java
public class UseUtility {

    public void createUtility() throws Exception {

        Utility utility = new Utility();

        // 以降は省略
    }
}

モック化クラス

モック化し、コンストラクタに任意の振る舞いを定義していきます。

Utility.java
public class Utility {

    public Utility() throws Exception {
        // 省略
    }
}

テストクラス

テスト対象クラスを呼び出すJUnitです。

JUnitでPowerMockを使用するために、@RunWithにはPowerMockRunnerを指定します。

@PrepareForTestには、モック化するクラスだけでなく、コンストラクタを呼び出す側のクラスも書いておく必要がある点に注意。

UseUtilityTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Utility.class, UseUtility.class })
public class UseUtilityTest {
    // 省略
}

使用例

thenReturn

モック化したクラスのコンストラクタが返すインスタンスを設定する。

ここではモックを返却するよう設定しているが、モックである必要はない。

@Test
public void test_thenReturn() throws Exception {
    // 準備
    // モック化
    Utility mock = PowerMockito.mock(Utility.class);
    // コンストラクタで返却するインスタンスを設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenReturn(mock);

    // 実行
    UseUtility obj = new UseUtility();
    obj.createUtility();
}

thenThrow

モック化したクラスのコンストラクタで発生する例外を設定する。

@Test(expected = Exception.class)
public void test_thenThrow() throws Exception {
    // 準備
    Exception exception = new Exception("error!");

    // モック化
    PowerMockito.mock(Utility.class);
    // コンストラクタで発生する例外を設定
    PowerMockito.whenNew(Utility.class).withNoArguments().thenThrow(exception);

    // 実行
    UseUtility obj = new UseUtility();
    obj.createUtility();
}

コンストラクタの検証

PowerMockでは、モック化したコンストラクタの検証のため、verifyNewが提供されています。

Mockitoのtimes,atLeast,asLeastOnce等を使用して、呼び出し回数を検証できます。

verifyNew(Utility.class)はverifyNew(Utility.class, times(1))と同義。

// 1回呼ばれたことを確認
PowerMockito.verifyNew(Utility.class).withNoArguments();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PowerMockでstaticメソッドをモック化する

目次

  1. PowerMockを使うための環境構築手順
  2. PowerMockにおけるモックとスパイ
  3. PowerMockでstaticメソッドをモック化する ←今ここ
  4. PowerMockでコンストラクタをモック化する
  5. PowerMockでprivateメソッドをモック化する
  6. PowerMockのWhiteBoxを使ってみる
  7. PowerMockでstaticイニシャライザを無効化する

概要

PowerMockを使えば、staticメソッドの戻り値を任意の値に設定したり、例外を返すようにしたりできます。

ここでは、UseUtilityクラス(テスト対象クラス)から呼び出すUtilityクラス(モック化クラス)のstaticメソッドをモック化する想定です。

テスト対象クラス

JUnitから呼び出すテスト対象のクラスです。

内部でUtilityクラスのstaticメソッドを呼び出しており、これらの戻り値や例外を設定したい。

UseUtility.java
public class UseUtility {

    public String getMessage(String name) throws Exception {

        String trimmedName = Utility.trim(name);

        return "Hello, " + trimmedName + ".";
    }

    public boolean setUp() {

        Utility.clearDatabase();

        // 省略

        return true;
    }
}

モック化クラス

モック化し、任意の振る舞いを定義していきます。

Utility.java
public class Utility {

    public static String trim(String string) throws Exception {
        // 未実装の想定
        return string;
    }

    public static void clearDatabase() {
        // 実行したくない処理の想定
    }
}

テストクラス

テスト対象クラスを呼び出すJUnitです。

JUnitでPowerMockを使用するために、@RunWithにはPowerMockRunnerを指定します。

@PrepareForTestには、モック化するクラスを指定します。staticメソッドのみモック化する場合、呼び出し元のクラス(テスト対象クラス)は記述不要です。

UseUtilityTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest(Utility.class)
public class UseUtilityTest {
    // 省略
}

使用例

doReturn

staticメソッドに、戻り値を設定する。

@Test(expected = Exception.class)
public void test_doThrow() throws Exception {
    // 準備
    String name = "John ";
    Exception expected_exception = new Exception("error!");

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // モッククラスと例外を設定
    PowerMockito.doThrow(expected_exception).when(Utility.class);
    // ↑で設定した例外を返すメソッドを設定
    Utility.trim(anyString());

    // 実行
    UseUtility obj = new UseUtility();
    obj.getMessage(name);
}

doThrow

staticメソッドで、例外を送出する。

@Test
public void test_doThrow() throws Exception {
    // 準備
    String name = "John ";
    String expected_message = "error!";
    Exception expected_exception = new IllegalArgumentException(expected_message);

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // モッククラスと例外を設定
    PowerMockito.doThrow(expected_exception).when(Utility.class);
    // ↑で設定した例外を返すメソッドを設定
    Utility.trim(anyString());

    // 実行
    try {
        UseUtility obj = new UseUtility();
        obj.getMessage(name);
        fail();
    } catch (Exception e) {
        // 結果確認
        assertEquals(expected_exception, e.getCause());
        assertEquals(expected_message, e.getMessage());
    }
}

doCallRealMethod

staticメソッドに戻り値や例外を設定せずに実物のメソッドを呼び出す。

@Test
public void test_doCallRealMethod() throws Exception {
    // 準備
    String name = "John ";
    String expected = "Hello, John .";

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // モッククラスと実物を呼びだす設定
    PowerMockito.doCallRealMethod().when(Utility.class);
    // 実物を呼ぶメソッドを設定
    Utility.trim(anyString());

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(name);

    // 結果確認
    assertEquals(expected, actual);
}

doNothing

staticメソッドで何もしたくない場合に使用する。

@Test
public void test_doNothing() throws Exception {
    // 実行準備
    // モック化
    PowerMockito.mockStatic(Utility.class);
    // モッククラスと何もしない設定
    PowerMockito.doNothing().when(Utility.class);
    // 何もしないメソッドを設定
    Utility.clearDatabase();

    // 実行
    UseUtility obj = new UseUtility();
    boolean actual = obj.setUp();

    // 結果確認
    assertTrue(actual);
}

thenReturn

staticメソッドに、戻り値を設定する。

@Test
public void test_thenReturn() throws Exception {
    // 準備
    String name = "John ";
    String expected = "Hello, John.";

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // 戻り値を設定
    PowerMockito.when(Utility.trim(anyString())).thenReturn("John");

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(name);

    // 結果確認
    assertEquals(expected, actual);
}

thenThrow

staticメソッドで、例外を送出する。

@Test(expected = Exception.class)
public void test_thenThrow() throws Exception {
    // 準備
    String name = "John ";
    Exception expected_exception = new Exception("error!");

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // 例外を設定
    PowerMockito.when(Utility.trim(anyString())).thenThrow(expected_exception);

    // 実行
    UseUtility obj = new UseUtility();
    obj.getMessage(name);
}

thenCallRealMethod

staticメソッドに戻り値や例外を設定せずに実物のメソッドを呼び出す。

@Test
public void test_thenCallRealMethod() throws Exception {
    // 準備
    String name = "John ";
    String expected = "Hello, John .";

    // モック化
    PowerMockito.mockStatic(Utility.class);
    // 実物を呼ぶ設定
    PowerMockito.when(Utility.trim(anyString())).thenCallRealMethod();

    // 実行
    UseUtility obj = new UseUtility();
    String actual = obj.getMessage(name);

    // 結果確認
    assertEquals(expected, actual);
}

staticメソッドの検証

PowerMockでは、モック化したstaticメソッドの検証のため、verifyStaticが提供されています。

Mockitoのtimes,atLeast,asLeastOnce等を使用して、呼び出し回数を検証できます。

verifyStatic(Utility.class)はverifyStatic(Utility.class, times(1))と同義。

// 1回呼ばれたことを確認
PowerMockito.verifyStatic(Utility.class);
Utility.trim(anyString());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PowerMockにおけるモックとスパイ

目次

  1. PowerMockを使うための環境構築手順
  2. PowerMockにおけるモックとスパイ ←今ここ
  3. PowerMockでstaticメソッドをモック化する
  4. PowerMockでコンストラクタをモック化する
  5. PowerMockでprivateメソッドをモック化する
  6. PowerMockのWhiteBoxを使ってみる
  7. PowerMockでstaticイニシャライザを無効化する

概要

PowerMockでstaticメソッドやprivateメソッドで任意の振る舞いを設定するために、まずは対象となるクラスのモック化/スパイ化を行う必要があります。

ここではUtilityクラスをモック化/スパイ化する想定です。

Utility.java
public class Utility {
    // 中身は省略。何でも良い。
}

モック化

PowerMockにおけるモック化とは、Mockito同様、単体テストの都合に合わせてメソッドの振る舞いを任意に定義するために使用します。また、呼び出し回数の記録や検証も可能です。

モック化直後のモックオブジェクトは、メソッドの振る舞いが定義されていない状態であるため、doReturnやdoThrow等で戻り値や例外を定義して使用します。(※振る舞いを定義していないメソッドは、戻り値としてnullを返す。)

モック化するには、PowerMockito.mockの引数にモック化したいクラスのClassを渡します。

Utility mock = PowerMockito.mock(Utility.class);

スパイ化

スパイ化はモック化と違い、一部のメソッドに対して任意の振る舞いを定義したい場合に使用します。

スパイ化した直後のUtilityクラスは、通常のUtilityクラスのインスタンスと変わらない振る舞いをします。(呼び出し回数の記録や検証は可能。)

つまり、doReturnやdoThrow等で任意の振る舞いや例外を設定したメソッド以外は、通常のメソッドと同じように動作します。

スパイ化するには、PowerMockito.spyの引数にモック化したいクラスのインスタンスを渡します。

Utility spy = PowerMockito.spy(new Utility());
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JUnitでPowerMockを使うための環境構築手順

目次

  1. JUnitでPowerMockを使うための環境構築手順 ←今ここ
  2. PowerMockにおけるモックとスパイ
  3. PowerMockでstaticメソッドをモック化する
  4. PowerMockでコンストラクタをモック化する
  5. PowerMockでprivateメソッドをモック化する
  6. PowerMockのWhiteBoxを使ってみる
  7. PowerMockでstaticイニシャライザを無効化する

概要

PowerMockを導入することで、Mockitoだけではできなかったstaticメソッド/privateメソッド/コンストラクタのモック化ができるようになります。
この記事はPowerMockの初学者に向けた記事となっていますが、前提知識としてMockitoの使用方法を理解していると、スムーズに学習を進められると思います。
また、記事内で使用するサンプルコードはGitHubから取得できます。

環境

version
eclipse Eclipse IDE 2020‑03
java 1.11
junit 4.12
mockito 2.23
powermock 2.0.2

ダウンロード

PowerMock公式サイトからライブラリ一式をダウンロードする。

ページ下部のDownloadsを選択する。
download_01.png

powermock-mockito2-junit-2.0.2.zipを選択する。
download_02.png

powermock-mockito2-junit-2.0.2.zipにはpowermockが依存するライブラリが含まれるはずだがこれだけでは不十分であり、ByteBuddy公式サイトより、byte-buddy.jarをダウンロードする必要がある。(2020/09/11時点)

Downloadを選択する。
download_03.png

byte-buddy-1.10.14.jarを選択する。
download_04.png

eclipseプロジェクトの作成

workspace配下にsample_powermockプロジェクトを作成し、libフォルダに先程ダウンロードしたライブラリ一式をコピーする。
projectsetup_01.png

ビルドパスへ追加する。
projectsetup_02.png

これで準備が整いました。

リンク集

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

JavaのコードTIPS

ラムダの知識

主要な関数型インタフェース

インタフェース名 メソッド 概要
Function<T,R> R apply(T t) 引数T, 戻り値R
Consumer<T> void accept(T t) 引数T, 戻り値なし
Predicate<T> boolean test(T t) 引数T, 戻り値boolean
Supplier<T> T get() 引数なし, 戻り値T
UnaryOperator<T> T apply(T t) 引数T, 戻り値T
BinaryOperator<T> T apply(T t1, T t2) 引数T2個, 戻り値R
BiFunction<T,U,R> R apply(T t, U u) 引数TとU, 戻り値R

中間操作

メソッド名称 概要
filter 条件に一致する要素
distinct 重複を除いた要素
limit 指定された数の要素
skip 指定された数を除いた要素
map 何らかの処理を行って変換した要素
flatmap
sorted ソート
peek

List関連

ソートと結合

list.stream().sorted(Comparator.reverseOrder()).reduce("", (all,s) -> all + "\r\n" + s);

list.sort(Comparator.reverseOrder());
String.join("\r\n", list);

Listの内容を出力

list.forEach(System.out::println);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TrackingJsを使った画像と動画のトラッキングのためのコンピュータビジョンの設定方法

この記事では、画像やビデオのトラッキングのためのTrackingJsを使って、コンピュータビジョンとオブジェクトや色検出のトラッキング機能を設定する方法を見ていきます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

TrackingJsとは何ですか?

Tracking.jsはJavaScriptのフレームワークで、開発者がコーディングや複雑なアルゴリズムを必要とせずに、コンピュータビジョンやオブジェクト検出機能をウェブアプリケーションに簡単に埋め込むことができるように作られています。Tracking.jsは比較的シンプルで使いやすく、多くの点でJQueryに似ています。トラッカー、さまざまな計算操作のためのユーティリティ機能、その他のウェブコンポーネントを提供し、生活をより簡単にしてくれます。

Trackerの種類

tracking.jsのTrackerとは、画像や動画の要素を検出して追跡するJSオブジェクトのことで、この要素には顔や色、オブジェクトなどがあります。また、Tracking.jsはカスタムトラッカーを作成して好きなものを検出する機能も提供しています。具体的なトラッカーを見てみましょう。

Color Tracker

Color Trackerは、画像やビデオ内の色の特定のセットを追跡するために使用することができます。デフォルトでは、このTrackerはマゼンタ、シアン、イエローの色をサポートしていますが、他のカスタムカラーを追加する可能性があります。実際にTrackerを見てみましょう。

これはGitHubで利用可能なコードをスニップしたものです。

ここにコードの説明があります。

  • 6行目:tracking-min.js を含んでいます。
  • 14 行目: video コンポーネントを定義しています。
  • 21行目: ColorTracker のインスタンスを作成します。
var colors = new tracking.ColorTracker(['magenta', 'cyan', 'yellow']);
  • 23行目: トラックイベントが発生したときに何が起こるかを定義しています。つまり、ビデオが実行されているときに、Tracking.jsはそのイベントオブジェクトに何が含まれているかを分析し始めます。イベントオブジェクトにはdata属性が含まれており、その長さが0と異なるかどうかをテストしています。 これはデータがあることを意味しており、実際、そのデータはビデオ/イメージのどこかで見つかった色に関する情報を表しているので、データには色が見つかった位置(x軸、y軸)、それを含む領域の高さと幅、色のラベルが含まれています。これは、29行目に印刷されたものです。
  • 34 行目 DOM に配置されたビデオ コンポーネントのトラッキングを開始します。 では、カスタムカラーを追加する方法を見てみましょう。カラーラベルとコールバック関数をパラメータとして、関数 registerColor を使用しています。

GitHubで公開されているコードを抜粋してみました。

27行目と34行目で、この関数を使って2つの新しい色を登録したことがわかります。実際のところ、Tracking.jsはRGB表現を使って色を扱うので、デフォルトではマゼンタ、シアン、イエローの色しかライブラリに登録されていないのですが、もしもっと色を追加したいのであれば、この関数を使って色を検出する方法をlibに示すことができます。

私たちの場合、緑と黒を追加したのは、それぞれのRGB表現を持っているからです。(0,255,0) と (0,0,0,0) です。2番目のパラメータは、色を検出するためのロジックを含むコールバックです。

これは非常にシンプルで、Tracking.jsはトラッキング時の色を(RGB形式で)このコールバックに送ります。例えば、緑(絶対緑)の場合、RGBコードは(0,255,0)なので、受信したデータがそれに対応するかどうかを検証し、この時点でTracking.jsはその領域を考慮すべきかどうかを知っているので、true/falseを返すだけです。

tracking.ColorTracker.registerColor('green', function(r, g, b) {
  if (r == 0 && g == 250 && b == 0) {
     return true;      
  }
  return false;   
});

最後にやるべきことは、トラッカーを初期化するときにカラーリストに色のラベルを追加することです。

var colors = new tracking.ColorTracker(['magenta', 'cyan', 'yellow','green']);

Object Tracker

この部分は、一連の画像やビデオの中でトラッキングしたいものをトラッキングすることに関連しています。そのためには、トラッカーが何をトラッキングすべきかについての入力として訓練されたデータを提供するのがベストです。幸いにもTrackingJsには目、口、顔の3つのデータセットが用意されています。これらのデータセットがどのように機能するのか見てみましょう。

オブジェクト検出の例を実装してみましょう。コードはこちらです。

ここでは、顔、目、口を検出するように訓練されたデータセットをファイルツリーの先頭に含めています。デフォルトでは、これらがこのライブラリで利用できる唯一のツリーです。このリンクをクリックすると、新しいオブジェクトを検出するためのカスタムデータセットを作成する方法を見ることができます。

  • plot()関数があり、検出されたオブジェクトの周りに矩形を描くのが役割です。

結果は以下のようになります。

image.png

ユーティリティ

コンピュータを使って何かを可視化するプログラムを作るのは、さまざまな意味で非常に難しいことです。例えば、ユーザのカメラを開いて利用可能なピクセルを処理する方法を考えなければならないでしょうし、画像の曲線を検出したり、その他の重い計算処理もあります。ユーティリティは、ウェブ開発者のための軽量で使いやすいAPIです。実際、利用可能なユーティリティのいくつかは以下の通りです。機能検出、コンボリューション、Gray Scale、画像のぼかし、インテグラル画像、Sobel、Viola-Jones。

機能の検出

この機能は、画像内で利用可能なオブジェクトのコーナーを見つけることによって機能します。そのAPIは次のようになります。

var corners = tracking.Fast.findCorners(pixels, width, height);

コンボリューション(畳み込み演算)

次に、畳み込みフィルタがありますが、これは画像のエッジの検出、ぼかし、シャープネス、エンボスなどに役立ちます。画像の畳み込みには、水平方向、垂直方向、畳み込みを分離する方法の3つの方法があります。

tracking.Image.horizontalConvolve(pixels, width, height, weightsVector, opaque);
tracking.Image.verticalConvolve(pixels, width, height, weightsVector, opaque);
tracking.Image.separableConvolve(pixels, width, height, horizWeights, vertWeights, opaque);

Gray Scale

Gray Scaleは画像のGray Scaleのトーン側の輝度値を検出するために使用されます。

tracking.Image.grayscale(pixels, width, height, fillRGBA);

画像のぼかし

Tracking.jsはGaussian Blurと呼ばれるぼかし効果の一種であるぼかしアルゴリズムを実装しています。この特定のぼかし効果は主にマルチメディアソフトウェアで使用されており、画像のノイズや不要なディテールを減らすのに役立ちます。一行のコードで画像をぼかす構文は以下の通りです。

tracking.Image.blur(pixels, width, height, diameter);

インテグラル画像

この機能は、開発者がグリッドの長方形のサブセット内の値の合計を持つことができます、それはまた、summed area table(合計領域テーブル)と呼ばれています。画像処理の分野では、積分画像としても知られています。これをTracking.jsで計算するには、以下のようにします。

tracking.Image.computeIntegralImage(
  pixels, width, height,
  opt_integralImage,opt_integralImageSquare,opt_tiltedIntegralImage,
  opt_integralImageSobel
 );

Sobel

この機能は画像の垂直方向と水平方向のグラデーションを計算し、 計算された画像を組み合わせて画像のエッジを見つけ出します。Sobelフィルタの実装方法は、 まず画像をグレースケールしてから水平方向と垂直方向のグラデーションを計算し、 最後にグラデーション画像を組み合わせて最終的な画像を作成します。

APIは以下のようになっています。

tracking.Image.sobel(pixels, width, height);

Viola-Jones

Viola-Jonesオブジェクト検出フレームワークは、リアルタイムで競争力のあるオブジェクト検出率をリアルタイムで提供する最初のオブジェクト検出フレームワークです。この手法はtracking.ObjectTracker実装の内部で使用されています。

Viola-Jonesを使ってTracking.jsを使って画像ピクセルのオブジェクトを検出するには:

tracking.ViolaJones.detect(pixels, width, height, initialScale, scaleFactor, stepSize, edgesDensity, classifier);

Webコンポーネント

コンピュータビジョンプログラムを書くのは大変なことが多く、単純な作業であっても完了させるために多くの計算が必要であり、コーディングも非常に反復的なものになっていました。この優れたlibの主な目的は、ウェブ上での複雑な処理を直感的なAPIで提供することであり、Web コンポーネントが付属しています。

様々な意味でのWebコンポーネントは、現代のWeb開発における新しい概念です。それらの目的は、より簡単なロジックカプセル化モデルを持ち込むことです。実際、これにより、開発者は機能のすべてのロジックを HTML 要素にカプセル化し、DOM 要素と同じように自然に使用することができます。これは、 react や angular などのライブラリの背後にある同じコンセプトを利用しています。しかし、これらとは異なり、WebコンポーネントはWebブラウザに完全に統合されています。

そこでTracking.jsは、上で説明したようなAPIの一部を、上で説明したWebコンポーネント要素に埋め込むことで、コンピュータビジョンをWeb上で簡単に実現する方法を提供しています。これらを使用するには、まず bower パッケージを介してインストールする必要があります。以下のコマンドを入力するだけで、TrakingJsのWebコンポーネントをインストールできます。

bower install tracking-elements --save

これを利用することで、Color要素(色を検出するために使われる)やObject要素(要素を検出するために使われる)のようないくつかの基本的な要素を提供することができます。TrackingJsのWebコンポーネントの大原則は、ネイティブのWeb要素(<video />, <img />などを拡張することと、フレームワークに新しい機能を追加することです。これは新しい属性 id=“"で行われ、検出する要素(色やオブジェクトなど)は target=“"属性で定義されます。

Color要素

いくつかの色を検出してみましょう:マゼンタ、シアン、イエロー:

<!-- detect colors into an image -->
<img is="image-color-tracking" target="magenta cyan yellow" />
<canvas is="canvas-color-tracking" target="magenta cyan yellow"></canvas>
<video is="video-color-tracking" target="magenta cyan yellow"></video>

上記のコードでは、ネイティブの <img/> 要素を拡張していることを指定するために is="image-color-tracking” を使用し、ターゲットとしている色をトラッカーに知らせるために target="magenta cyan yellow” を使用しています。

Object要素

実践でやってみましょう。

ここにコードスニペットがあります。

  • 5行目 :ウェブコンポーネントの機能を有効にするためにポリフィルをインポートします。
  • 7行目 :ウェブコンポーネントをインポートします。
  • 11行目: ネイティブのものをis属性で拡張し、target属性で追跡対象のオブジェクトを定義することで、Webコンポーネント自体を使用しています。
  • 18 行目: 要素にイベント・リスナーを追加し、顔が検出されたときに発生するトラック・イベントをリッスンします。
  • 20 行目: plotRectagle()関数を呼び出して、検出された領域の周りに矩形を描画します。

結論

今回の記事では、Web上にコンピュータビジョンをインストールして適用し、TrackingJsを使っていくつかのオブジェクトや色検出機能を実行する方法を見てきました。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

ubuntuでjava appletを動かす

はじめに

過去にjavaの勉強の為にappletを使った簡単な制作物を作っていたのですが、最近になってJDK11で過去の制作物を動かそうとコンパイルすると

注意:Test.javaは推奨されないAPIを使用またはオーバーライドしています。
注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。`

と出るので、-Xlint:deprecationをつけて再コンパイルすると

Test.java:3: 警告:[deprecation] javax.swingのJAppletは推奨されません
public class Test extends javax.swing.JApplet {
                                       ^
警告1個

という文が出て、そのままappletviewerで開こうとすると色々とエラーが出てきまして、基本情報技術者試験を受験するために少しjavaを勉強しただけの自分にとってはよくわからないエラーだったので調べてみて、そこで初めてjdk11ではappletが動かないということを知りました…

どうしても過去の制作物を動かしたかったので、取り急ぎappletを動かすためにjdk11をアンインストールしてjdk8をインストールし直しました。その手順を備忘録としてまとめておきます。

方法

まずopenjdk-11-jdkをアンインストールするために、次を実行する。sudo apt-get purge openjdk-\* icedtea-\* icedtea6-\*
完了したらjava -versionそのようなファイルやディレクトリはありませんというのを確認してから、sudo apt install openjdk-8-jdkを実行しopenjdk-8-jdkのインストールを行う。
完了したらjava -versionで確認して、インストール完了。
再度コンパイルしてもエラーは出ないはず。

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

Java基礎

自分の復習用に

「0から2入力」があれば「0から2で入力してください」と表示
「文字列の入力」があれば「整数値ではありません」と表示

try-catchを使用するのではなくScannerのhasNextが使えたので
こういうやり方があると記録しておく。

hello.java
      void getJudgeHand() {
        while (true) {
            System.out.println("コマンド? 0:グー 1:パー 2:チョキ");
            if (scanner.hasNextInt()) {
                myHand = scanner.nextInt();
                if (myHand >= 0 && myHand <= 2) break;
            }
            else {
                scanner.next();
                System.out.println("整数値ではありません");
            }
            System.out.println("0から2で入力してください");
        }
    }

try-catchの方法も

hello.java

void getJudgeHand() {
    while (true) {
        try {
            System.out.println("コマンド? 0:グー 1:パー 2:チョキ");

            this.myHand = scanner.nextInt();
            if (this.myHand <= 2 && this.myHand >= 0) { // 2以下 かつ、0以上ならbreak
                break;
            }
        } catch (InputMismatchException e) {
            System.out.println("整数値ではありません");
            scanner.next();    // 入力を捨てる
        }
        System.out.println("0から2で入力してください");
    }
}



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

オブジェクト指向歴25年のオブジェクト指向おじさんが語るオブジェクト指向設計の処方箋

この記事のターゲット

この記事は以下の人々を対象としています。

  • オブジェクト指向を一通りわかっている人。
  • オブジェクト指向の設計力を高めたい人。
  • オブジェクト指向を使っているのに、設計が綺麗にならず悩んでいる人。
  • プログラムが大きくなるとオブジェクト指向設計が破綻する人。
  • オブジェクト指向に限界を感じている人。
  • 共同開発メンバーの設計力に差があって困っている人。

以下の人は対象外です。

  • オブジェクト指向が何なのかわからない人。
  • オブジェクト指向を極めている人。

オブジェクトは責任ベースで考える

オブジェクト指向といえは、やれインターフェイスだメッセージだ隠蔽だカプセル化だ、みたいな用語がたくさん出て来て、どれも関連があるようでないようで意味が分からないですよね。気取ったこと言ってんじゃねぇよと。

日本で社会人経験があれば、こんなものは一言あれば一発で理解できます。それは責任です。

仕事をするとき、責任者だとか担当者だとかを職場で設定しますよね。こうした責任者というのは、責任の範囲が決まっています。任されたことについてはしっかり責任を持って対応する。そして他の人が担当している職域には踏み込まない。「その件に関しましては私の方では対応いたしかねますが、鈴木の方が担当となっておりますのでご紹介いたしましょうか」的な奴です。

各オブジェクトには責任の範囲というものがあります。クラスの設計が美しくないなと思ったら、そのクラスの責任範囲が適切かどうかを考えるのが基本です。本当にその仕事はそのクラスが担当するべきなのか。他のクラスが担当するはずじゃなかったのか。または別途新しいクラスに担当させるべきか。クラスを職種に、オブジェクトを個人に当てはめ、擬人化して考えると上手くいきます。頭の中でオブジェクトが「その件に関しては対応しかねますので…」と言い始めたらヤバいです。

人間もやることが増えすぎると仕事が手に負えなくなるように、オブジェクトも責任を抱え込みすぎると手に負えなくなります。

責任を越える仕事は、やらない

営業担当の一職員である男性が、ロビーが汚いからといって掃除を始めてしまえば、外回りに行く時間が遅れるかもしれませんし、背広が汚れて業務に支障が出るかもしれません。もしすでに総務が清掃員を午後に手配済みであれば、その作業は無駄になります。

このくらいの掃除なら自分でできるなと思っても、そこはあえて手を出さず、総務に連絡して清掃員を手配してもらいます。総務は清掃員の手配状況について知っているでしょうから、無駄な重複作業がないよううまくやってくれるでしょう。

これはロビーというグローバル変数に営業クラスの男性オブジェクトがアクセスしてしまったことによるミスです。

責任とは何か

ここまでで言いたいことを改めて要約すると、オブジェクト指向設計を考えるときに、オブジェクトを擬人化して、そいつに責任について語らせると、結構うまくハマる、ということになります。

なおメンバ関数やメンバ変数が増えてきたら、そのクラスは責任過多になっている可能性が高いです。逆に少なすぎる場合は仕事をサボっている不要なオブジェクトかもしれません。適度な粒度に保ちましょう。

オブジェクト間の関連を設計する

よし、オブジェクトの責任範囲を適切に保てば上手くいくんだな。なんかうまく作れそうな気がしてきた…。と思ったのもつかの間、プログラムが大きくなってくるとそれでもグチャグチャになってきます。なぜだ、これがオブジェクト指向の限界なのか…。

いえいえ、オブジェクトの責任範囲を適切な粒度に保つ、というのはオブジェクト指向の基本に過ぎません。そこから先は「関連」が大切になってきます。関連とはオブジェクトとオブジェクトの間の繋がりのことです。会社で言えば組織図とか連絡窓口とかです。

デザインパターン

オブジェクト指向で言う所の関連はどのように考えればよいのか。方法の1つとしてデザインパターンを参照できます。デザインパターンとはオブジェクト指向で作られたソフトウェア設計のカタログです。ググれば色々出て来るでしょうから詳細は割愛します。

このデザインパターン、GoFってやつが有名ですが、はっきり言ってそんなに綺麗に体系化されていません。頻繁に使われる奴もあれば、こんなの使わねーよって奴もあります。またGoF以外にも様々なパターンが当然考えられるわけですが、業界全体で研究してまとめて体系化しようという流れにはなっていません。

それでもGoFを一通り見てみることで、美しいオブジェクト指向設計とは何かということが掴めるようになります。

UML

もう1つの方法は図示化することです。UMLでなくても何でもよいと言えばよいのですが、いちおう規格化されているのでUMLを使いましょう。色々な図がありますが、ことオブジェクト指向設計においてはクラス図が重要です。

ざっくりいえば、設計が美しいとクラス図も美しくなり、設計が汚いとクラス図も汚くなります。関連線がぐちゃぐちゃになったら、それを綺麗に直していくだけで、設計も半自動的に美しくなります。また図にするとレビュー(他の人からのアドバイス)を受けやすくなります。

UMLの作画は専用ツールもありますが、私はVisioか手書きです。ソフトの詳細設計をVisioで書こうとすると死ねますが、それはUMLの使い方がそもそも間違っています。図を書くときは、検討したいポイントに絞って書くべきです。ソフト全体ではなく、ライブラリだけとか、特定の機能だけとか。また造りが難しくなっちゃった所だけとか。stringクラスへの関連線を全クラスから引くとか愚の骨頂です。

ポイントを絞って図を描けば、設計を考えやすくなりますし、レビューもしやすくなりますし、ドキュメントも読みやすくなります。VisioでA4を超えるような図を描いているうちは、適度な粒度感覚が身についているとは言えません。

継承とか多態とか

継承・多態が分からないとオブジェクト指向が分かったとは言えません。しかし継承や多態とは、オブジェクト間の関連の一種に過ぎません。この記事を読んでいる皆さんは優秀な人でしょうから、適当なオブジェクト指向言語をしばらくいじっていれば理解できるでしょう。というわけで説明は省略。

関連とは何か

オブジェクトの責任範囲という基本を踏まえたうえで、各オブジェクト間の関連を考えていると、結局のところオブジェクト指向設計とは、各オブジェクト間の関連を設計するということに他ならないということが解ってくるはずです。

オブジェクトの責任を超える裁量を認める

よし、オブジェクトの責任範囲をコンパクトに保ち、デザインパターンやUMLなんかを使って各オブジェクト間の関連をよく考えれば、綺麗なプログラムを組めるのだな…。

ところがプログラムがいよいよ巨大になってくると、なかなかそうもいきません。一番よくある失敗は伝言ゲームというメッセージのリレーです。

伝言ゲーム

「御社のパーツを購入したいのですが、お見積りをいただけますでしょうか」「少々お待ちください。」「A社があのパーツを買いたいと言ってますが」「B社から卸してもらわないと売れないじゃないか、B社に確認してみろ」「御社のパーツを購入したいのですが、お見積りをいただけますでしょうか」…。

こんな伝言ゲームの登場人物が2~3人までだったらまだ許されますが、4人5人と階層が深くなるようであれば要注意です。特にデータを取得するためだけに2度3度と呼ぶのはヤバいです。

伝言板

この問題を解決するにはデータベースを置きます。データベースとは伝言板のようなものです。B社のウェブページに価格が掲載されていれば、A社に問い合わせなくてもよかったわけです。

ここで言うデータベースとは、単なる共通データ置き場という広い意味で使っています。本当のリレーショナルDBやインメモリDBでもよいですし、ファイルでもよいですし、グローバルにアクセスできるkey-valueペアのメモリストレージでもよいです。

データベースがプロセスのメモリ外にあれば、プロセスを分割することも可能になります。小さなプロセスに分割して全体のアプリケーションを実装することが可能であれば、各プロセスの粒度を押さえられます。

グローバル変数の逆襲

このデータベースとは、グローバル変数そのものです。せっかくオブジェクト指向で責任範囲を区別したのに、また全ての責任を抱え込んでどうするのか。

データベースはグローバル変数なので、やはり導入しない方が望ましいです。小さなプログラムは極力オブジェクト指向の範囲内でなんとかなるはずです。プログラムが巨大になり、伝言ゲームが過ぎると感じ始めたら、そこで初めてデータベースの導入を考えてみましょう。

結局銀の弾丸はない?

グローバル変数はしばしば悲惨な結果を招きます。ちょっとでもコード量が増えると簡単に破綻します。オブジェクト指向を導入すれば、かなりコード量が増えても耐えられます。しかしプログラムが極端に大きくなりすぎると、伝言ゲームの嵐になって別の破滅を招きます。

スキルの低いプログラマが扱うデータベースは悲惨なことになります。しかし、オブジェクト指向をある程度まで極めた猛者たちが集まり、各クラスの責任範囲を逸脱しない範囲でデータベースにアクセスするのであれば、オブジェクト指向とデータベースの高度なレベルでの両立は可能でしょう。そのバランスのとり方は、高い志を持って頑張るしかないです。

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

Apache Cayenne の ORM (Object-Relational Mapping) 機能を使った MySQL データベースとの対話

この記事では、Apache Cayenneのオブジェクトリレーショナルマッピング(ORM)機能を使用して、Mavenを使用して構築した小さなJavaプロジェクトからMySQLデータベースを操作します。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

プロジェクトの設定

MavenはJavaアプリケーションを構築するために最もよく使われているツールの一つです。このセクションでは、プロジェクトの依存関係を設定します(これはあなたのシステムが空であることを前提としています)。

最初のステップとして、Apache CayenneとMYSQLコネクタ(具体的にはJDBCドライバ)を追加するために、以下の依存関係を追加してみましょう。

<dependency>
   <groupId>org.apache.cayenne</groupId>
   <artifactId>cayenne-server</artifactId>
   <version>4.0.1</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.14</version>
   <scope>runtime</scope>
</dependency>

モデラープラグインは pom.xml にも定義されている必要があります。これは、現在のプロジェクトのマッピングファイルを開いたときに、コマンドラインからApache Cayenne Modelerプラグインを起動するために使われるmavenコマンドです。

<plugin>
   <groupId>org.apache.cayenne.plugins</groupId>
   <artifactId>cayenne-modeler-maven-plugin</artifactId>
   <version>4.0.1</version>

   <configuration>
      <modelFile>${project.basedir}/src/main/resources/cayenne-blog.xml</modelFile>
   </configuration>
</plugin>

一般的にモデラーは、データベースと実際のJavaモデルクラスとの間のマッピングを設計し、設定するための推奨される方法です。

このページからダウンロードできます。あなたの特定のOS用に作られたバージョンをダウンロードする必要があるでしょうし、代わりに、Mavenプラグインとして含まれているクロスプラットフォームバージョン(JAR)を使用することもできます。この記事を書いている時点での最新の安定版はバージョン4.1です。このバージョンはJava 1.8以降が必要です。

次のステップとして、mvn clean installコマンドでプロジェクトをビルドし、mvn cayenne-modeler:runコマンドでモデラーGUIを起動して、この画面を出力してみましょう。

image.png

MySQL 以外のものを使用している場合は、JDBC ドライバも変更する必要があるため、設定はデータベースに依存します。ここでは、対応するドライバと一緒に完全なリストを示します。

マッピングとデータベースの設計

例えば、2つのテーブル間の1対多の関係を示すcayenne_blogという既存のデータベースがあり、その中で以下のようなパラメータが定義されているとします。

  • author(著者): id (PK) と name
  • article(記事):id (PK)、タイトル、内容、author_id (FK) では、この例のDBを参照したSQLコマンドを考えてみましょう。
CREATE TABLE `author` (
  `id` int(11) NOT NULL,
  `name` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for table `article`
--
ALTER TABLE `article`
  ADD PRIMARY KEY (`id`),
  ADD KEY `author_id` (`author_id`);

--
-- Indexes for table `author`
--
ALTER TABLE `author`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `article`
--
ALTER TABLE `article`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `author`
--
ALTER TABLE `author`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- Constraints for table `article`
--
ALTER TABLE `article`
  ADD CONSTRAINT `article_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`);

db.sql](https://github.com/dassiorleando/apache-cayenne/blob/master/db.sql)ファイルをphpMyAdminにインポートするか、ターミナルのMYSQLサーバーから以下のコマンドを実行します: mysql < db.sql.

では、モデラープラグインの設定であるcayenne-maven-pluginというプラグインをpom.xmlに追加してみましょう。

<plugin>
   <groupId>org.apache.cayenne.plugins</groupId>
   <artifactId>cayenne-maven-plugin</artifactId>
   <version>4.0.1</version>

   <configuration>
      <map>${project.basedir}/src/main/resources/blog.map.xml</map>
      <dataSource>
         <driver>com.mysql.jdbc.Driver</driver>
         <url>jdbc:mysql://localhost:3306/cayenne_blog</url>
         <username>root</username>
         <password>root</password>
      </dataSource>
      <dbImport>
         <defaultPackage>com.dassiorleando.apachecayenne.models</defaultPackage>
      </dbImport>
   </configuration>

   <dependencies>
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.44</version>
         <scope>runtime</scope>
      </dependency>
   </dependencies>
</plugin>

ここではORMがデータを保存する場所(<data source>)とマッピングファイルを保存する場所(<map>)を指定します。この設定から、データベース名はcayenne_blog、ユーザデータベースの認証情報はroot:root(MYSQLサーバのものに合わせて更新してください)、デフォルトパッケージはプロジェクト構造体のもの(モデルクラスを作成するパッケージ)を使用していることがわかります。

最後に、プロジェクトのコマンドラインからcbimportコマンドを使用します: mvn cayenne:cdbimportcdbimportはXMLマップファイルを既存のデータベースと同期させるので、以下のようなログが得られるはずです。

INFO] +++ Connecting: SUCCESS.
[INFO] Detected and installed adapter: org.apache.cayenne.dba.mysql.MySQLAdapter
[INFO]   Table: cayenne_blog.AUTO_PK_SUPPORT
[INFO]   Table: cayenne_blog.article
[INFO]   Table: cayenne_blog.author
[INFO]     Db Relationship : toOne  (article.author_id, author.id)
[INFO]     Db Relationship : toMany (author.id, article.author_id)
[INFO] 
[INFO] Map file does not exist. Loaded db model will be saved into '/Users/dassiorleando/projects/opensource/apache-cayenne/src/main/resources/blog.map.xml'
[INFO] 
[INFO] Detected changes: 
[INFO]     Create Table         article
[INFO]     Create Table         author
[INFO]     Create Table         AUTO_PK_SUPPORT
[INFO] 
[WARNING] Can't find ObjEntity for author
[WARNING] Db Relationship (Db Relationship : toOne  (article.author_id, author.id)) will have GUESSED Obj Relationship reflection. 
[INFO] Migration Complete Successfully.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.165 s
[INFO] Finished at: 2019-07-22T17:40:36+01:00
[INFO] Final Memory: 10M/164M
[INFO] ------------------------------------------------------------------------

Javaクラスを生成してみましょう: mvn cayenne:cgen

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building apache-cayenne 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- cayenne-maven-plugin:4.0.1:cgen (default-cli) @ apache-cayenne ---
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_Article.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/Article.java
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_Author.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/Author.java
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_AutoPkSupport.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/AutoPkSupport.java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

これで、プロジェクトの構造が変更されているのがすぐにわかるはずです。ここでは、_Article.java_Author.javaの話をしています(どちらもCayenneDataObjectを拡張したファイルです)。同じ設定(XML形式)がresources/blog.map.xmlファイルに入れられていると見ることができます。

次に、mvn cayenne-modeler:runというコマンドを入力してモデラーを起動します。New Projectをクリックし、次のページでマッピングファイルのData Domain Nam (blog)を指定します。ここでは、作成したマップファイルと同じフォルダに保存します。

image.png

次に、File > Import Datamapをクリックして、データマップへのリンクを許可するUIにアクセスします。

cayenne-blog.xmlblog.map.xmlがリンクされたら、モデラーからモデルを更新してクラスに反映させることができます。以下のようになります。

image.png

Apache Cayenne は 3 種類の主要な鍵戦略をサポートしています。

  • Cayenne-Generated: PK生成を管理します。
  • データベース生成: PK はデータベースエンジンによって管理されます。
  • カスタムシーケンス:ここにカスタムロジックを実装する必要があります。 次のスクリーンショットでは、著者テーブルには、データベースで管理されているオートインクリメントされた整数であるidが設定されています。

image.png

注: 次の図に示すように、当社のデータベース構成と正確に一致するDataNodeを作成してください。

image.png

Mavenプロジェクトのresourcesディレクトリに、以下の内容のcayenne-blogという特別なXMLファイルを作成します。

<?xml version="1.0" encoding="utf-8"?>
<domain project-version="9">
   <map name="blog"/>

   <node name="datanode"
       factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
       schema-update-strategy="org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy">
      <map-ref name="blog"/>
      <data-source>
         <driver value="com.mysql.jdbc.Driver"/>
         <url value="jdbc:mysql://localhost:3306/cayenne_blog"/>
         <connectionPool min="1" max="1"/>
         <login userName="root" password="root"/>
      </data-source>
   </node>
</domain>

XMLPoolingDataSourceFactoryは、DataNodeDescriptorに関連付けられたXMLリソースからJDBC接続情報をロードする役割を担う。

マッピング構造

Apache Cayenne はモデルを記述するための独自の構文を持っています。

  • DataNode(<node>): データベースのモデル。データベース名、ドライバ、データベースユーザの認証情報など、データベースに接続するために必要なすべての情報が含まれています。
  • DataMap(<data-map>):関係を持つ永続的なエンティティのコンテナです。
  • DbAttribute(<db-attribute>): データベーステーブルのカラムを表します。
  • DbEntity(<db-entity>): 単一のデータベーステーブルまたはビューのモデル。DbAttributesとリレーションシップを持つことができます。

  • ObjEntity(<obj-entity>): 単一の永続的なJavaクラスのモデルで、エンティティクラスのプロパティに対応するObjAttributesと、別のエンティティの型を持つプロパティであるObjRelationshipsで構成されています。

  • Embeddable(<embeddable>): ObjEntityのプロパティとして機能するJavaクラスのモデルですが、データベースの複数の列に対応しています。

  • procedure(<procedure>): データベースにストアドプロシージャを登録します。マッピングする際に使用します。

詳細は以下のガイドを参照してください。

モデルへのCRUDの適用

このセクションでは、モデル(ArticleAuthor)にいくつかの基本的な操作を適用します。ObjectSelectクラスはデータベースへの問い合わせに便利ないくつかの静的メソッドを持っていますが、挿入と更新のために、変更をコミットするために使用されるserver's context(ObjectContext)を使用する必要があります。

ここでは、私たちのプロジェクトに関連するサーバーのコンテキストを取得する方法を説明します。

ServerRuntime cayenneRuntime = ServerRuntime.builder()
        .addConfig("cayenne-blog.xml")
        .build();

ObjectContext context = cayenneRuntime.newContext();

注: cayenne-blog.xmlファイルはプロジェクトのリソースフォルダにあります。

オブジェクトの作成

以下のクエリでオブジェクトを作成することができます。

/**
 * Save an author
 * @param name
 */
public void save(String name) {
    // Save a single author
    Author author = this.context.newObject(Author.class);
    author.setName(name);

    context.commitChanges();
}

オブジェクトの読み取り

以下のクエリでオブジェクトを読み込むことができます。

/**
 * Find an author by its ID
 * @param id    the author's ID
 * @return      the matched author or null if not existing
 */
public Author findById(int id) {
    Author author = Cayenne.objectForPK(context, Author.class, id);
    return author;
}

/**
 * Looking for an author by name
 * @param name  the name to look up with
 * @return      the first matched author or null if not existing
 */
public Author findByName(String name) {
    Author foundAuthor = ObjectSelect.query(Author.class)
            .where(Author.NAME.eq(name))
            .selectOne(this.context);

    return foundAuthor;
}

/**
 * Find authors by name starting with(like%)
 * @param partName expected name part
 * @return         list of authors
 */
public List<Author> findByNameLike(String partName) {
    // Let's apply a case-insensitive LIKE on the Author's name column
    // We get all the authors with their name starting with "partName"
    List<Author> authorsLike = ObjectSelect.query(Author.class)
            .where(Author.NAME.likeIgnoreCase(partName + "%"))
            .select(context);

    return authorsLike;
}

/**
 * Find authors by name ending with
 * @param partName expected name part
 * @return         list of authors
 */
public List<Author> findByNameEndWith(String partName) {
    // All authors with names ending with "partName"
    List<Author> authorsEnd = ObjectSelect.query(Author.class)
            .where(Author.NAME.endsWith(partName))
            .select(context);

    return authorsEnd;
}

クラスのすべてのレコードを検索する

これまでに保存されたすべての著者に以下のクエリを使用してクエリを実行することができます。

public List<Author> findAll() {
    // Looking for all authors
    List<Author> authors = ObjectSelect
            .query(Author.class)
            .select(this.context);
    return authors;
}

オブジェクトの更新

以下のクエリでオブジェクトを更新することができます。

/**
 * Update an author
 * @param id        the author's ID
 * @param newName   the new name to set
 * @return          true for a successful operation and false unknown author
 */
public boolean update(int id, String newName) {
    if (StringUtils.isEmpty(newName)) return false;

    // Get the author to update
    Author author = this.findById(id);

    if (author == null) return false;

    // Set its name
    author.setName(newName);
    context.commitChanges();
    return true;
}

オブジェクトへの関係付け

ここでは、著者が書いた記事と著者をリンクさせる方法を紹介します。

/**
 * Attach a fake article to the author
 * @param id    the author's ID
 * @return      true for a successful operation and false unknown author
 */
public boolean attachArticle(int id) {
    // Get the author to link with
    Author author = this.findById(id);

    if (author == null) return false;

    // Create a fake article and link it to the current author
    Article article = context.newObject(Article.class);
    article.setTitle("My post title");
    article.setContent("The content");
    article.setAuthor(author);

    context.commitChanges();

    // Get author's linked data (articles)
    List<Article> articles = author.getArticles();

    return true;
}

オブジェクトの削除

以下のクエリでオブジェクトを削除することができます。

/**
 * Delete an author
 * @param id author's ID
 * @return   true for a successful operation and false unknown author
 */
public boolean delete(int id) {
    // Get the author to delete
    Author author = this.findById(id);

    if (author != null) {
        context.deleteObjects(author);
        context.commitChanges();
        return true;
    } else {
        return false;
    }
}

クラスのすべてのレコードを削除する

Apache CayenneのAPIではSQLTemplateを使ってテーブルの全レコードを削除することができますが、ここでは基本的なSQL削除クエリをターゲットクラスと一緒に提供するだけです。

// SQL delete queries for Author and Article classes
SQLTemplate deleteArticles = new SQLTemplate(Article.class, "delete from article");
SQLTemplate deleteAuthors = new SQLTemplate(Author.class, "delete from author");

// Applying the deletion queries
context.performGenericQuery(deleteArticles);
context.performGenericQuery(deleteAuthors);

ExpressionとExpressionFactory

Apache Cayenne を使って高度なクエリを構築するには多くの可能性があります。しかし、ほとんどの場合はExpressionExpressionFactoryクラスを使っていますが、ここではその例をいくつか紹介します。

  • likeExp: LIKE 式を構築するために使用します。
  • likeIgnoreCaseExp: LIKE_IGNORE_CASE 式を構築するために使用します。
  • containsExp: 文字列内の任意の場所にマッチするパターンを持つ LIKE クエリに使用される式。
  • containsIgnoreCaseExp: containsExpに似ていますが、大文字小文字を区別しないアプローチを使用します。
  • startsWithExp: パターンは文字列の先頭にマッチします。
  • startsWithIgnoreCaseExp: startsWithExp に似ていますが、大文字小文字を区別しないアプローチを使用します。
  • endsWithExp: 文字列の末尾にマッチする式。
  • endsWithIgnoreCaseExp: 大文字小文字を区別しないアプローチを使用する文字列の最後にマッチする式。
  • expTrue: Trueの式に使用します。
  • expFalse: ブール値のFalseの式に使用します。
  • andExp: and演算子を使用して2つの式を連結する際に使用します。
  • orExp: or 演算子を使用して 2 つの式を連結する場合に使用します。

結論

このチュートリアルでは、Apache Cayenne のオブジェクトリレーショナルマッピング (ORM) 機能を MySQL データベース用に設定する方法と、基本的な CRUD 操作を一対多のリレーションシップを持つクエリの例として学習しました。この記事の完全なソースコードはGithubにあります。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

アリババDragonwell 8 入門編

このブログでは、新しく発表されたDragonwell 8、インストール方法や使い方、開発コミュニティへの参加方法などを探ります。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Alibaba Dragonwell 8とは

Alibaba Dragonwell 8はOpenJDKの無料配布版で、パフォーマンスの向上やセキュリティの修正など長期的なサポートをAlibaba Cloudから提供します。Alibaba Dragonwell 8は現在、X86-64/Linuxプラットフォームをサポートしています。Dragonwell 8の追加により、データセンターにおける大規模なJavaアプリケーションの展開の安定性、効率性、パフォーマンスを大幅に向上させることができます。Alibaba Dragonwell 8は、OpenJDKプロジェクトと同じライセンス条項に基づく「フレンドリーフォーク」であり、Java SE Standardと互換性があります。ユーザーは、Alibaba Dragonwell 8を使用してJavaアプリケーションを開発し、実行することができます。オープンソースのAlibaba Dragonwell 8は、アリババグループで内部的に使用されているカスタマイズされたバージョンのOpenJDKであるAJDKのオープンソース版です。AJDKは、ビジネスシナリオに基づいて電子商取引、金融、物流に特化して最適化されており、10万台以上のサーバーを収容する超大規模なアリババデータセンターで稼働しています。

Alibaba Dragonwell 8のインストール方法

現在、Alibaba Dragonwell 8はLinux x86-64プラットフォームのみをサポートし、コンパイル済みのバイナリJDKパッケージを提供しています。Alibaba Dragonwell 8をインストールするには、以下の2つのステップを実行します。

1、GitHubのAlibaba Dragonwell 8の下のダウンロードページからコンパイル済みのバイナリJDKパッケージをダウンロードします。
2、ダウンロードしたtarパッケージをインストールディレクトリに解凍します。

インストールが完了したら、アプリケーションが参照しているJAVA_HOMEをAlibaba Dragonwell 8のインストールディレクトリに向けるだけです。例えばTomcat 8.5.39を考えてみると、Alibaba Dragonwell 8上でTomcatを動作させるには、Tomcat起動時に以下のコマンドを実行するだけです。

JAVA_HOME=/path/to/dragonwell8/installation  sh tomcat/bin/catalina.sh start

Tomcatが実際にAlibaba Dragonwell 8上で動作していることを確認するには、そのJavaコマンドに-showversionパラメータを追加して、JDKのバージョン情報を表示することができます。

JAVA_HOME=/path/to/dragonwell8/installation JAVA_OPTS="-showversion" sh tomcat/bin/catalina.sh start

Tomcatを起動すると、tomcat/logs/catalina.outファイルの先頭にAlibaba Dragonwell 8のバージョン情報が表示されます。

image.png

Alibaba Dragonwell 8の使い方

本項では、Alibaba Cloud Dragonwell 8 のさまざまな機能を利用する方法について説明します。現在、Dragonwell 8のプレビュービューでは、アリババグループで内部的にフル稼働モードになっている2つの機能を提供しています。JWarmUpとJava Flight Recorderです。どちらの機能についても、OpenJDKコミュニティにJEPまたはパッチが提出されています。上流のマージが完了する前に、Alibaba Dragonwell 8のユーザーがこの2つの機能を事前に利用できることを期待しています。

JWarmUpを使用してJavaアプリケーションを素早くウォームアップ

OpenJDKでは、実行効率を向上させるために、Javaのバイトコードを動的に最適化されたマシンコードにコンパイルするJIT(ジャストインタイム)コンパイル技術を採用しています。しかし、このコンパイルが完了する前に、Javaコードはインタプリタモードで実行されるため、比較的効率が悪いです。

アプリケーションが起動してビジネストラフィックが入ってきたばかりの時には、多くのJavaメソッドがJITを使ってコンパイルを開始し、ビジネスリクエストが遅いインタプリタによって実行されている可能性が高いです。この現象の最終的な結果として、システム負荷が非常に高くなり、多くのユーザリクエストのタイムアウトが発生します。この問題に対するこれまでの多くの解決策は、アプリケーションをウォームアップするためにシミュレートされたトラフィックを使用することです。JWarmUp機能は、この問題を解決するための新しいオプション、すなわち、Java仮想マシンの前回の実行とコンパイルの記録を使用して、実行する現在のアプリケーションをウォームアップすることを提供します。

次の図は、JWarmUpがどのように機能するかを示しています。

image.png

JWarmUpの典型的なアプリケーションシナリオは、新しいアプリケーションのバージョンをリリースすることです。

1、JWarmUpはまず、ベータ環境の1台のマシン上でJavaアプリケーションを短時間実行し、その間にJITコンパイラが行ったアクションのメタデータを記録して収集します。
2、次に、JWarmUpは、取得したメタデータを、新しいバージョンのコードを含む本番環境の各マシン/コンテナにコピーします。
3、最後に、ベータ環境で生成されたメタデータは、JWarmUpパラメータを使用して、アプリケーションを起動する過程でJITウォームアップを完了するように本番環境のマシンを誘導することにより、本番環境のマシンにロードされます。

このようにすることで、アプリケーションはユーザーからのリクエストがあったときに最高のパフォーマンスを発揮します。

ウォームアップデータの収集

ここでもTomcatを例に考えてみると、JITコンパイル中にベータ環境で生成されたメタデータを収集するために以下のコマンドラインパラメータを追加することができ、-XX:CompilationWarmUpLogfile=パラメータは生成されたJWarmUpファイルのパスを指定します。

JAVA_HOME=/path/to/dragonwell8/installation JAVA_OPTS="-XX:ReservedCodeCacheSize=512m -XX:CompilationWarmUpLogfile=$PWD/jwarmup.log -XX:+CompilationWarmUpRecording -XX:+CompilationWarmUp -XX:-TieredCompilation -XX:+DeoptimizeBeforeWarmUp -XX:CompilationWarmUpDeoptTime=30 -XX:+PrintCompilationWarmUpDetail" sh bin/catalina.sh start

これに続いて、この生成されたファイルは、OSS、SFTP、または他の方法を使用して、生産中のマシンに送信することができます。

記録されたデータを使用してJavaアプリケーションをウォームアップ

本番環境のマシンでは、以下のパラメータを使用するだけで、以前のウォームアップデータを使用してTomcatの新しいインスタンスを起動することができます。xx:CompilationWarmUpLogfile=パラメータは、ロードするJWarmUpファイルのパスを指定します。このファイルは、ウォームアップデータを収集するために、前のステップでベータ環境からコピーしておく必要があります。

JAVA_HOME=/path/to/dragonwell8/installation JAVA_OPTS="-XX:ReservedCodeCacheSize=512m -XX:CompilationWarmUpLogfile=$PWD/jwarmup.log -XX:+CompilationWarmUp -XX:-TieredCompilation -XX:+DeoptimizeBeforeWarmUp   -XX:CompilationWarmUpDeoptTime=30 -XX:+PrintCompilationWarmUpDetail" sh bin/catalina.sh start

Javaアプリケーションのパフォーマンスを分析するためにJavaフライトレコーダーを使用

JFR(Java Flight Recorder)は、JVMに組み込まれたイベントベースのパフォーマンス分析機能です。この商用機能はOracle JDK7u4から利用できるようになった。この機能は2018年にJDK11でオープンソース化されました。ただし、JDK8ではサポートされていません。

AlibabaはRed HatやAzul、Amazonなどの企業と連携して、この機能のサポートをJDK8に追加しています。しかし、現在のところ、このパッチはOpenJDK8uにマージされていません。この機能のサポートを事前に取得できるように、Alibaba Dragonwell 8ではAlibabaが移植したJFR版を提供しています。

JFRの使い方は非常に簡単です。コマンドラインパラメータやjcmdコマンドを使ってHotSpotを制御し、パフォーマンスデータをファイルに生成するだけです。その後、オープンソースのJMCツールを使用して、生成されたファイルをグラフィックインターフェイスで開いて分析することができます。

JFRを使用してパフォーマンスデータを収集

デフォルトでは、Alibaba Dragonwell 8ではJFR機能は無効になっています。JFR機能を有効にするには、コマンドラインパラメータ-XX:+EnableJFRを追加する必要があります。Alibaba Dragonwell 8では、JFRを使用してパフォーマンスデータを収集するためのいくつかのオプションが用意されています。

アプリケーションでコマンドラインパラメータを使用して、アプリケーションが起動した直後にJFRがパフォーマンスデータの収集を開始するように指定することができます。これは、起動時の問題を診断するのに役立ちます。次の例のコードは、Java プロセスの JFR モジュールが初期化されると、1 分間 JFR データを収集し、そのデータを rec.jfr という名前のファイルに返します。

JAVA_HOME=/path/to/dragonwell8/installation JAVA_OPTS="-XX:+EnableJFR -XX:StartFlightRecording=duration=1m,filename=rec.jfr" sh bin/catalina.sh start

また、-XX:+EnableJFRだけでもアプリケーションに追加して、アプリケーション起動後の任意のタイミングでjcmdコマンドを使ってデータを収集することができます。

例えば、以下のようなコマンドでTomcatを起動することができます。

JAVA_HOME=/path/to/dragonwell8/installation JAVA_OPTS="-XX:+EnableJFR" sh bin/catalina.sh start

解析用のデータを収集するには、解析先のTomcatプロセスのPIDを使用して、JFRの対応するjcmdコマンドを実行するだけです。例えば、Tomcatでは、以下のコマンドを使用して、特定の時点から10秒間のデータを収集します。

$ ps ax | grep tomcat
 77522 pts/18   Sl+    0:08 /home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39/../j2sdk-image/bin/java -Djava.util.logging.config.file=/home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -XX:+EnableJFR -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39/bin/bootstrap.jar:/home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39/bin/tomcat-juli.jar -Dcatalina.base=/home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39 -Dcatalina.home=/home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39 -Djava.io.tmpdir=/home/chuansheng.lcs/dw_test/apache-tomcat-8.5.39/temp org.apache.catalina.startup.Bootstrap start
 98451 pts/22   S+     0:00 grep --color=auto tomcat

$ dragonwell8_home/bin/jcmd 77522 JFR.start duration=10s filename=$PWD/rec3.jfr
77522:
Started recording 3. The result will be written to:

/home/my/workdir/rec3.jfr

10秒後、/home/my/workdir/rec3.jfr JFRファイルが生成されます。そのファイルをJMCで解析することができます。

また、JFRを直接起動してデータ収集期間を指定せずにデータを収集し、必要に応じて生成されたデータをすべて手動でファイルにダンプすることもできます。

$ dragonwell8_home/bin/jcmd 2823 JFR.start filename=$PWD/rec4.jfr
2823:
Started recording 4. No limit specified, using maxsize=250MB as default.

Use JFR.dump name=4 to copy recording data to file.

$ dragonwell8_home/bin/jcmd 2823 JFR.dump name=4 filename=rec4.jfr
2823:
Dumped recording "Recording-4", 332.7 kB written to:

/path/to/my/workdir/rec4.jfr

パフォーマンス分析にJMCを使用

JFRは、Javaアプリケーションのパフォーマンスデータをバイナリファイルに記録します。JMC (Java Mission Control) を使用すると、グラフィックインターフェースで特定のパフォーマンスデータのセットを分析することができます。JMCはオープンソースのツールです。Alibaba Dragonwell 8には含まれていません。このツールを使用するには、OpenJDK公式サイト https://jdk.java.net/jmc/ からダウンロードしてください。

なお、Alibaba Dragonwell 8を使用して生成されたJFRデータファイルを解析するには、JMC 7.0以降が必要です。

JMCを開いた後、左側の特定の項目をクリックすると、サンプリング中に発生したイベントを詳細に解析することができます。

image.png

診断とデバッグのサポート

Alibaba Dragonwell 8には、後述するいくつかの便利な診断機能も組み込まれています。

ラージオブジェクトの割り当てに関するアラート機能

新しいJVMパラメータ-XX:ArrayAllocationWarningSize=は、この目的のために使用することができます。例えば、以下のコードでは、比較的大きな配列を割り当てています。

public static void main(String[] args) {
    doAlloc(32 * 1024 * 1024 + 1);
}
private static Object doAlloc(int size) {
    return new byte[size];
}

コード実行時にArrayAllocationWarningSizeオプションを追加すると、この配列が割り当てられている間に実行されているJavaスタックを表示します。

image.png

ParNew GCログの詳細サポート

デフォルトでは、Alibaba Dragonwell 8ではCMS(Concurrent Mark Sweep)アルゴリズムが使用されており、young generationではParNewアルゴリズムが使用されています。そのため、ParNew GCログには2つの組み込み強化機能が用意されています。

  • 次のYoung GCの最後にyoung generation用のオブジェクト型ヒストグラムを印刷できるように、jinfoツールを使用してPrintYoungGenHistoAfterParNewGCオプションを設定することができます。これを行うには、以下のコマンドを使用します。
jinfo -flag +PrintYoungGenHistoAfterParNewGC <pid>

この印刷操作が完了した後、出力が多すぎるのを防ぐために、このオプションは false に戻されます。以下の内容は、典型的な出力例です。

image.png

  • -XX:+PrintGCRootsTraceTime を使用すると、GC ルートセットの種類ごとの処理にかかる CPU 時間の詳細を印刷することができます。以下の内容は出力例です。

image.png

HeapDumpsの合理化をサポート

Alibaba Dragonwell 8のjmapツールでは、新しいダンプオプションminiがサポートされており、HeapDumpの生成時に元の型のすべての配列の内容をスキップできるようになっています。このオプションを使用すると、生成されるHeapDumpファイルのサイズが大幅に削減され、型とオブジェクトの関係性のチェックだけが必要なシナリオで特に役立ちます。

次の図にコードサンプルを示します。

image.png

アリババ・ドラゴンウェルコミュニティの構築に参加

Alibaba Dragonwell コミュニティでは、JDK バージョンの長期サポートを提供しています。以下のチャンネルでサポートを得たり、ディスカッションに参加したり、意見を出し合ったりすることができます。

GitHubのAlibaba DragonwellプロジェクトのIssuesページ

参考文献

[1] Oracle Java 8公式ドキュメント:https://docs.oracle.com/javase/8/
[2] OpenJDK 8 プロジェクトのホームページ: https://openjdk.java.net/projects/jdk8u/
[3]アリババDragonwell 8プロジェクト:https://github.com/alibaba/dragonwell8
[4] Alibaba Dragonwell 8 Developer Guide: https://github.com/alibaba/dragonwell8/wiki/Developer-Guide

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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