20201218のJavaに関する記事は8件です。

JUnit5の使い方

この記事はMDC Advent Calendar 2020 19日目の記事です。

はじめに

皆さん、単体テスト書いてますか?
(私自身も最近あまり書いてないですが...)

  • 実装はできるのにテストコードがあまり書けない
  • JUnit4までは書いたことあるけど、JUnit5はあんまり...

意外と多いそんな人たち向けに、JUnit5でテストコードを書き始めるためのお助けになればと思います。

概要

JUnit5とは

  • 言わずと知れたJava開発における最もメジャーなテストフレームワークです
  • これまでのバージョンのJUnitとは異なり、JUnit5は3つのサブプロジェクトに含まれる複数のモジュールで構成されます

    JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

    • JUnit Platform
      • JVM上でテストフレームワークを起動するための基盤となる
      • 上記のプラットフォーム上で動作するテストフレームワークを開発するためのTestEngineAPIを定義している
      • コマンドラインからプラットフォームを起動するためのConsole Launcherや、 GradleやMaven用のビルドプラグイン、JUnit4ベースのテストランナーなどを提供し、あらゆるTestEngineを実行できるようになっている
    • JUnit Jupiter
      • JUnit5でテストを書くために最低限必要な依存関係をまとめたもの
    • JUnit Vintage
    • JUnit3またはJUnit4ベースのテストを実行するためのTestEngineを提供する
  • 長くなりましたが、とりあえずJUnit5でテストを書き始めたいなら、JUnit Jupiterを依存関係に追加しとけばOKです(以下はgradleのサンプル)

build.gradle
plugins {
    id "java"
}

sourceCompatibility = 8
targetCompatibility = 8
[compileJava, compileTestJava]*.options*.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.7.0"
}

サポートされるJavaバージョン

  • JUnit5を実行するためには、Java8以上が必要です

テストの書き方

テストメソッド

package sample.junit5;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JUnit5Test {

    @Test
    void success() {
        assertEquals(12, 12);
    }

    @Test
    void failure() {
        assertEquals(5, 12);
    }
}
アノテーション 説明
@Test 付与されたメソッドがテストメソッドになる
  • アサーションメソッド(例はassertEquals(expected, actual))を使い、期待値と実測値を比較する
    • 実際には、テスト対象メソッドを呼び出して、その戻り値と期待値を比較します
  • JUnit5では、テストクラス・テストメソッドともにpublicである必要がなくなりました

前処理・後処理

package sample.junit5;

import org.junit.jupiter.api.*;

class JUnit5Test {

    @BeforeAll
    static void beforeAll() {
        System.out.println("☆ beforeAll()");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("☆☆ beforeEach()");
    }

    @AfterEach
    void afterEach() {
        System.out.println("★★ afterEach()");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("★ afterAll()");
    }

    @Test
    void test1() {
        System.out.println("~~~ test1() ~~~");
    }

    @Test
    void test2() {
        System.out.println("~~~ test2() ~~~");
    }
}
実行結果
☆ beforeAll()
☆☆ beforeEach()
~~~ test1() ~~~
★★ afterEach()
☆☆ beforeEach()
~~~ test2() ~~~
★★ afterEach()
★ afterAll()
アノテーション 説明
@BeforeAll 付与されたメソッドは、1番最初に1度だけ実行される
メソッドはstaticである必要がある
@BeforeEach 付与されたメソッドは、各テストメソッドの前に毎回実行される
@AfterAll 付与されたメソッドは、1番最後に1度だけ実行される
メソッドはstaticである必要がある
@AfterEach 付与されたメソッドは、各テストメソッドの後に毎回実行される

テストグルーピング

package sample.junit5;

import org.junit.jupiter.api.*;

class JUnit5Test {

    @Test
    void test1() {...}

    @Nested
    class group1 {
        @Test
        void test2() {...}

        @Test
        void test3() {...}
    }
}
アノテーション 説明
@Nested 非staticなクラスに付与するとテストクラスを入れ子にできる
  • テストクラスはそれなりのステップ数になることも多いので、テスト観点ごとにグルーピングするのがおすすめです

パラメータテスト

package sample.junit5;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class JUnit5Test {

    @ParameterizedTest
    @ValueSource(strings = {"foo", "bar", "baz"})
    void test(String value) {
        System.out.println("VALUE: " + value);
    }
}
実行結果
VALUE: foo
VALUE: bar
VALUE: baz
アノテーション 説明
@ParameterizedTest 付与されたメソッドは、パラメータテストになる
@ValueSource リテラル値の配列を1つ指定することができ、パラメータテスト呼び出しで1つのパラメータを提供する(例はString)
  • パラメータの分だけテストが実行されるようなイメージです
  • 以下のリテラル値が提供されています
    • short
    • byte
    • int
    • long
    • float
    • double
    • char
    • java.lang.String
    • java.lang.Class

最後に

  • すいません、時間がなさすぎて書きたいことの半分も書けてない状態です(...12/18 23時過ぎに書き始めました)
  • とにかく言いたいことは、JUnit5は便利で簡単です
  • 皆さん、テストコードはちゃんと書きましょう

参考

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

[必見!!!] オブジェクト指向についてまとめてみた!

はじめに

初学者の方にとってオブジェクト指向は参考書でも度々目にするが、そのまま調べずに終わっていませんか?
また、オブジェクト指向わかった気になっている方もいると思います。

今回はオブジェクト指向について分かりやすくまとめてみましたのでご覧ください!

この記事のターゲット

  • プログラミング初学者
  • オブジェクト指向のプログラミング言語を学習している方(Ruby,JAVA,C++など)

オブジェクト指向プログラミングとは

「モノ」を組み立てるように表現して、コンピュータに動作をさせる

現在ではオブジェクト指向言語が主流になっていますので、プログラミングをする上でオブジェクト指向の概念を押さえておくことは非常に重要です。
たとえば、以下のプログラミング言語はすべてオブジェクト指向言語です。

  • さまざまなプラットフォームで動作するJava
  • Webページに動きを与えるRubyやPHPやJavaScript
  • Mac OSやiOSのアプリ開発で使われるSwift
  • 商用のプログラミング言語として定評のあるC++
  • WordやExcelのマクロとして使用されているVBA

分かりやすく説明すると.....

オブジェクト指向プログラムとは、
モノと操作を分けて完成(アプリ)を作り上げること

例えると
オブジェクト(モノ) →リモコン
操作 →ボタン、電池

みたいな感じです!笑

メリット

  • 効率よくプログラムを設計、開発できる
    webサプリのボタンを例にすると、
    モノと定義したボタンの場合、同じようなボタンがあった場合、ボタンの色を変えれば、他の削除や編集ボタンを作ることができる。

  • 不具合の原因を特定しやすくなる
    モノと操作にオブジェクト思考で分かれているので、どこでエラーが起こったのか(モノのコード)、
    どんな操作をした時か(操作のコード)がわかりやすくエラーが特定しやすい。

  • プログラムの使用が変わっても対応しやすい
    例えば、100個のボタンがあり、ボタンの幅を400px→300pxに変えたとします。100個のボタンを修正するのはだいぶ手間になる。
    しかし、オブジェクト指向で定義した「ボタン」の横幅を変更するだけで、全てのボタンのサイズが変わる

  • オブジェクト指向を使えば、開発した後も時間をかけずに修正することができます。

覚えておきたいキーワード

1.カプセル化
→他のプログラムからできるだけ変更できないようにする仕組み
ボタンを例にすると、全ボタンで高さと横幅を変えたくないとすると、他のプログラムから簡単に設定して守るのが、カプセル化です。

2.継承
同じようなプログラムを1箇所にまとめてコードを再利用する仕組み
→似たような処理を一つずつ書いていると、例えば「データを登録する処理」のコードを修正することにより時間がかかってしまいます。
継承を使えば、登録、更新、削除などのカテゴリーとしてコードをまとめておける
カテゴリーとしてまとめておけば、新しい機能を作るときに同じような処理を使うことも可能。似たような処理がないか確認もしやすくなる。

3.抽象化
重要な要素や共通な要素を抜き出して、他は切り捨てる考え方
→車に例えると
どんなサイズ?どんなデザイン?などの聞かなければわからないことではなく、
エンジンがついている。ハンドルが4つついているなどの必ず必要な共通要素
何が言いたいかというと、「詳しいことは決まってないけど、開発するうえで必ず必要な要素は○○だな」と捉える。

4.ポリモーフィズム
継承したコードの一部を変更して利用するための仕組み
コードの一部のみを変更して利用できるのがポリモーフィズム
→例えばボタンの色だけを変えて登録、更新、削除のボタンを作ったことが、一部を変更し継承するという意味になります。

終わりに

もし、オブジェクト指向を理解せずにプログラミング学習していた人も、この記事を読んであの部分がそうだったのかと気づくはずです!
皆様のご参考になれば嬉しいです!

参考記事

オブジェクト指向とは?誰でもわかるようにやさしく解説

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

[必見!!!] オブジェクト指向についてまとめてみた!

はじめに

初学者の方にとってオブジェクト指向は参考書でも度々目にするが、そのまま調べずに終わっていませんか?
また、オブジェクト指向わかった気になっている方もいると思います。

今回はオブジェクト指向について分かりやすくまとめてみましたのでご覧ください!

この記事のターゲット

  • プログラミング初学者
  • オブジェクト指向のプログラミング言語を学習している方(Ruby,JAVA,C++など)

オブジェクト指向プログラミングとは

「モノ」を組み立てるように表現して、コンピュータに動作をさせる

現在ではオブジェクト指向言語が主流になっていますので、プログラミングをする上でオブジェクト指向の概念を押さえておくことは非常に重要です。
たとえば、以下のプログラミング言語はすべてオブジェクト指向言語です。

  • さまざまなプラットフォームで動作するJava
  • Webページに動きを与えるRubyやPHPやJavaScript
  • Mac OSやiOSのアプリ開発で使われるSwift
  • 商用のプログラミング言語として定評のあるC++
  • WordやExcelのマクロとして使用されているVBA

分かりやすく説明すると.....

オブジェクト指向プログラムとは、
モノと操作を分けて完成(アプリ)を作り上げること

例えると
オブジェクト(モノ) →リモコン
操作 →ボタン、電池

みたいな感じです!笑

メリット

  • 効率よくプログラムを設計、開発できる
    webアプリのボタンを例にすると、
    モノと定義したボタンの場合、同じようなボタンがあった場合、ボタンの色を変えれば、他の削除や編集ボタンを作ることができる。

  • 不具合の原因を特定しやすくなる
    モノと操作にオブジェクト思考で分かれているので、どこでエラーが起こったのか(モノのコード)、
    どんな操作をした時か(操作のコード)がわかりやすくエラーが特定しやすい。

  • プログラムの使用が変わっても対応しやすい
    例えば、100個のボタンがあり、ボタンの幅を400px→300pxに変えたとします。100個のボタンを修正するのはだいぶ手間になる。
    しかし、オブジェクト指向で定義した「ボタン」の横幅を変更するだけで、全てのボタンのサイズが変わる

  • オブジェクト指向を使えば、開発した後も時間をかけずに修正することができます。

覚えておきたいキーワード

1.カプセル化
→他のプログラムからできるだけ変更できないようにする仕組み
ボタンを例にすると、全ボタンで高さと横幅を変えたくないとすると、他のプログラムから簡単に設定して守るのが、カプセル化です。

2.継承
同じようなプログラムを1箇所にまとめてコードを再利用する仕組み
→似たような処理を一つずつ書いていると、例えば「データを登録する処理」のコードを修正することにより時間がかかってしまいます。
継承を使えば、登録、更新、削除などのカテゴリーとしてコードをまとめておける
カテゴリーとしてまとめておけば、新しい機能を作るときに同じような処理を使うことも可能。似たような処理がないか確認もしやすくなる。

3.抽象化
重要な要素や共通な要素を抜き出して、他は切り捨てる考え方
→車に例えると
どんなサイズ?どんなデザイン?などの聞かなければわからないことではなく、
エンジンがついている。ハンドルが4つついているなどの必ず必要な共通要素
何が言いたいかというと、「詳しいことは決まってないけど、開発するうえで必ず必要な要素は○○だな」と捉える。

4.ポリモーフィズム
継承したコードの一部を変更して利用するための仕組み
コードの一部のみを変更して利用できるのがポリモーフィズム
→例えばボタンの色だけを変えて登録、更新、削除のボタンを作ったことが、一部を変更し継承するという意味になります。

終わりに

もし、オブジェクト指向を理解せずにプログラミング学習していた人も、この記事を読んであの部分がそうだったのかと気づくはずです!
皆様のご参考になれば嬉しいです!

参考記事

オブジェクト指向とは?誰でもわかるようにやさしく解説

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

レガシー・バージョンのインストール方法【Java】

はじめに

研究や開発をしていると、古いバージョン(Legacy Version)のJavaやJDKじゃないと動かないソフトウェアに出会すことがあると思います。私は、この記事を書くにあたってEGI社のGeoScanというデバイスがJava for macOS 2017に依存していることによるバグに遭遇しました。

この記事では、macOSで古いバージョンのJavaをインストールする方法を紹介します。

前提知識

いくつか基礎的な用語を紹介しておきたいと思います。

用語 説明
Java どんな環境でも実行することができる汎用的なプログラミング言語。
JDK Java Development Kitの略で、Javaで開発をするために必要なソフトウェアをまとめたパッケージ。
Jar Java Archiveの略。Javaのファイルをまとめることができる圧縮形式の一つ。圧縮したままで実行することができることが特徴。
Shell コンピュータに指示をするためのコマンドをコンピュータに伝えるための中継役。
sudo Superuser doやSubstitute user do の略。管理者権限でコマンドを実行したい時などに先頭につけるコマンド。指定すれば、特定ユーザーに権限で実行することもできる。
macOS (Catalina / Mojave) Appleの開発したオペレーティング・システム(OS)。最近のOSだと、High Sierra => Mojave => Catalina => Big Surという流れ。
AppleScript Appleの開発した自動化処理などを実行できるスクリプト言語。

インストール手順

以下の流れでインストールを進めていきます。
1. Java / JDKのアンインストール
2. dmgファイルのダウンロード
3. Java Versionの確認

1. Java / JDKのアンインストール

現在インストールされているJavaを以下のコマンドでアンインストールします。

$ sudo rm -fr /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin
$ sudo rm -fr /Library/PreferencesPanes/JavaControlPanel.prefPane
$ sudo rm -fr ~/Library/Application\ Support/Java

続いて、JVMのディレクトリに移動して、そこにあるxxx.jdkというファイルを以下のコマンドで削除しましょう(人それぞれjdk1.8.0_06.jdkのファイル名は変わります)。

$ cd /Library/Java/JavaVirtualMachines/
$ sudo rm -rf jdk1.8.0_06.jdk

2. dmgファイルのダウンロード

以下のサイトのようにAppleが出している過去のバージョンのdmgをダウンロードして、インストールします。

ダウンロード Java for OS X 2017-001 - Apple
https://support.apple.com/kb/dl1572?locale=ja_JP

3. Java Versionの確認

最後に、Javaがインストールされているか以下のコマンドで確認しましょう。

$ java -version
java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-468)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-468, mixed mode)

デバッグ紹介

続いていくつかインストール中に出てくる可能性のあるバグを紹介します。

A. すでにインストールされているエラー

アンインストールをおこなっても、以下のようなバグが出てくることがあります。

Java for 2015-001はこのディスクにインストールできません。このパッケージのより新しいバージョンがすでにインストールされています。

already_install_bug.png

このような時は以下のようにdmgファイルをsedコマンドで加工してModifiedJava6Install.pkgというファイルを出力します。
具体的な手法としては、script editorというアプリを起動して、以下のコードを入力・保存・実行しましょう。

2015年版のスクリプトは以下の通りです。

2015_exchange.scpt
set theDMG to choose file with prompt "Please select javaforosx.dmg:" of type {"dmg"}
do shell script "hdiutil mount " & quoted form of POSIX path of theDMG
do shell script "pkgutil --expand /Volumes/Java\\ for\\ OS\\ 2015-001/JavaForOSX.pkg ~/tmp"
do shell script "hdiutil unmount /Volumes/Java\\ for\\ OS\\ 2015-001/"
do shell script "sed -i '' 's/return false/return true/g' ~/tmp/Distribution"
do shell script "pkgutil --flatten ~/tmp ~/Desktop/ModifiedJava6Install.pkg"
do shell script "rm -rf ~/tmp"
display dialog "Modified ModifiedJava6Install.pkg saved on desktop" buttons {"Ok"}

2017年版のスクリプトは以下の通りです。

2017_exchange.scpt
set theDMG to choose file with prompt "Please select javaforosx.dmg:" of type {"dmg"}
do shell script "hdiutil mount " & quoted form of POSIX path of theDMG
do shell script "pkgutil --expand /Volumes/Java\\ for\\ macOS\\ 2017-001/JavaForOSX.pkg ~/tmp"
do shell script "hdiutil unmount /Volumes/Java\\ for\\ macOS\\ 2017-001/"
do shell script "sed -i '' 's/return false/return true/g' ~/tmp/Distribution"
do shell script "pkgutil --flatten ~/tmp ~/Desktop/ModifiedJava6Install.pkg"
do shell script "rm -rf ~/tmp"
display dialog "Modified ModifiedJava6Install.pkg saved on desktop" buttons {"Ok"}

already_install_debug_1.png
already_install_debug_2.png

B. 2015年版のエラー

一見インストールに成功したように思えても、以下のようなエラーが出ることがあります。

This package is incompatible with this version of macOS.

bug_2015.png

色々と模索したのですが、Mojave以降には対応していない模様でした、、、2017年版だとまだ対応しているため、そちらをインストールすることを推奨します。

参考文献

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

「textareaにはvalue属性がない」とはどういうことか?

こんにちは、Takeです。

JSP,Java,Servlet,MySQL,Tomcatを使って掲示板を作っていたのですが

下記のJSPの一部

<td>名前<td>

<td>input type="text" name="name" value="<%= request.getParameter("name") %> size="40">
</td>

<td>件名</td>
<td>
<input type="text" name="subject" value="<%= request.getParameter("subject") %> size="40">
</td>

<td>メッセージ</td>
<td>
<textarea rows="5" name="content" value="<%= request.getParameter("content") %> cols="40">
</td>

ブラウザ上の掲示板から上記の「名前、件名、メッセージ」を入力して、エラー条件(例えば、名前が文字数を超過している、メッセージが書かれていないなど)に該当した場合に投稿できないようにコードを書いていました。

そしてエラーだった場合に、フォームに入力した値(名前、件名、メッセージ)がそのまま保持されるようにしたかったのです。

「名前」と「件名」はしっかり保持されていました。

しかし、「メッセージ」だけがどうしても保持出来なかったのです。

色々調べていると、「textareaはinputタグと違ってvalue属性がない」という結論に至りましたが、「value属性がない」とはどういうこと?とずっと悩んでいました。

難しい話は無しにして、結論から書きます。

<td>名前<td>

<td>input type="text" name="name" value="<%= request.getParameter("name") %> size="40">
</td>

<td>件名</td>
<td>
<input type="text" name="subject" value="<%= request.getParameter("subject") %> size="40">
</td>

<td>メッセージ</td>
<td>
<textarea rows="5" name="content" cols="40"><%= request.getParameter("content") %></textarea>
</td>

最初に載せたコードと少し違う部分がありますが、分かりましたか?

(↓最初のコード「メッセージ」の部分)

<td>メッセージ</td>
<td>
<textarea rows="5" name="content" value="<%= request.getParameter("content") %> cols="40">
</td>

(↓改善したコード「メッセージ」の部分)

<td>メッセージ</td>
<td>
<textarea rows="5" name="content" cols="40"><%= request.getParameter("content") %></textarea>
</td>

いかがだったでしょうか?
私自身まだ完璧には理解できていないのですが、「textareaにはvalue属性がない」というのは、つまりtextareaタグの要素にvalue="~~~"という値を入れること自体ができないのです。

めんどくさ、全部value属性つけても良いんじゃないの? と思うんですけど、そうすると何かダメな理由でもあるんでしょうね。

てかクロスサイトスクリプティングだるすぎ

以上です。

参照元URL: https://shgam.hatenadiary.jp/entry/2014/12/06/185627

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

ArchUnit 実践:使用範囲が限定されたメソッドの可視性をパッケージプライベートまたはプライベートに強制する

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

アーキテクチャテストのモチベーション

16 日目の ArchUnit 実践:同一パッケージからのみ呼び出されるメソッドの可視性をパッケージプライベートまたはプライベートに強制する と 17 日目の ArchUnit 実践:自クラスからのみ呼び出されるメソッドの可視性をプライベートに強制する の 2 つのアーキテクチャテストの対象となるメソッド群は包含関係(自クラスからのみ呼び出されるメソッド ⊆ 同一パッケージからのみ呼び出されるメソッド)にあります。
2 つのテストを同時に実行すると両方のテストで同じメソッドが違反として検出されテスト結果の精査が手間になる可能性があるため、1 つのテストにまとめてみます。

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

package com.example;
 
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.Test;

import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;

class ArchitectureTest {

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

    @Test
    void 使用範囲が限定されたメソッドの可視性をパッケージプライベートまたはプライベートに強制する() {
        methods()
            .that(new DescribedPredicate<>("are public or are package private") {
                @Override
                public boolean apply(final JavaMethod method) {
                    return ! method.getModifiers().contains(JavaModifier.PRIVATE)
                        && ! method.getModifiers().contains(JavaModifier.PROTECTED);
                }
            })
            .should(new ArchCondition<>("be package private or be private, if the scope of use is limited") {
                @Override
                public void check(final JavaMethod input, final ConditionEvents events) {
                    Method method = new Method(input);

                    if (method.isOnlyCalledInDeclaredClass()) {
                        // 自クラスからのみ呼ばれるメソッドはプライベートであるべき
                        events.add(SimpleConditionEvent.violated(input,
                            String.format("`%s` should be private.", input.getFullName())));

                    } else if (method.isOnlyCalledFromClassesInSamePackage()) {
                        // 同一パッケージからのみ呼ばれるメソッドはパッケージプライベートであるべき
                        events.add(SimpleConditionEvent.violated(input,
                            String.format("`%s` should be package private.", input.getFullName())));
                    }
                }
            })
            .check(CLASSES);
    }

    private class Method {
        private final JavaClass ownerClass;
        private final Set<JavaClass> callerClasses;

        private Method(final JavaMethod method) {
            this.ownerClass = method.getOwner();
            this.callerClasses = method.getAccessesToSelf()
                .stream()
                .map(JavaAccess::getOriginOwner)
                .collect(Collectors.toSet());
        }

        private boolean isOnlyCalledInDeclaredClass() {
            return callerClasses
                .stream()
                .allMatch(callerClass -> callerClass.isEquivalentTo(ownerClass.reflect()));
        }

        private boolean isOnlyCalledFromClassesInSamePackage() {
            return callerClasses
                .stream()
                .allMatch(callerClass -> callerClass.getPackageName().equals(ownerClass.getPackageName()));
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQLサーバーをJavaで実装してみる

こんにちは。個人的に、JavaでGraphQLサーバーを実装する機会があったので、今回はその知見について書きたいと思います。
この記事はGraphQL Advent Calendar 2020の18日目の記事です。

はじめに

この記事では、GraphQLの概要や実装の中で使用しているSpring Bootおよびその周辺ライブラリについての説明は記述していません。それらをある程度知っている方が対象になると思いますが、そこまで難しいことは書いていないのでおそらく雰囲気で理解できると思います。

ライブラリを探す

まずは、GraphQLサーバーを実装する上で必要なライブラリを探します。

の2つが見つかりました。どちらもSpring Bootと組み合わせて使用できるようです。
GraphQLのResolverの実装例を見ると、GraphQL Java Kickstartの方がよりシンプルに書けそうだったので、今回はこちらを使用することにします。

GraphQL Java KickstartのREADMEを見ると、

This project wraps the Java implementation of GraphQL provided by GraphQL Java.

GraphQL Java の方をラップしている。と書いています。なるほど。

実装する機能を決める

次にGraphQLを使って実装する機能を決めます。今回は基本となるQuery、Mutation、そしてSubscriptionを使った機能を実装することにしました。機能のユースケースはざっくり以下の通り。

  • Query: 本の一覧を取得する。
  • Query: 本のIDを指定して、特定の本を取得する。
  • Mutation: 新しい本を登録する。
  • Subscription: 新たに登録された本を通知する。

プロジェクトを構成する

それでは、アプリケーションを作成していきます。プロジェクトはひな型はSpring Initializerで適当に作成します。必要なdependenciesは後から追加するので、ここではLombokのみ追加しています。
spring_initializer.png

アプリケーションを作成する

build.gradle

はじめに、build.gradleを修正します。

com.graphql-java-kickstart:graphql-spring-boot-starterをdependencyに追加します。
また、com.graphql-java-kickstart:graphiql-spring-boot-starterも追加して、GraphiQL上でAPIのテストを行います。
その他、io.projectreactor:reactor-corespring-actuatormicrometer-registry-prometheusのdependencyも追加しました。前者はSubscription実装のため、後者はMetrics取得のためです。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.4.1'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:8.0.0'
    runtimeOnly 'com.graphql-java-kickstart:graphiql-spring-boot-starter:8.0.0'
    // To embed GraphiQL tool
    implementation 'com.graphql-java-kickstart:graphiql-spring-boot-starter:8.0.0'
    // For subscripion
    implementation 'io.projectreactor:reactor-core:3.4.1'
    // For metrics
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'io.micrometer:micrometer-registry-prometheus:1.6.0'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

application.yml

次に、application.ymlを作成します。

  • 1. To enable graphiql以下の設定で、GraphiQLのURLエイリアスとGraphQLのアクエスポイントURLを指定します。
  • 2. To enable graphql metricsは、GraphQLに関するMetricsを取得するための設定です。
  • 3. To enable to get metrics..は、spring-actuatorの設定です。/actuator/prometheusにアクセスすると、各MetricsがPrometheusのフォーマットで取得できます。
application.yml
# 1. To enable graphiql
graphiql:
  mapping: /graphiql
  endpoint:
    graphql: /graphql
# 2. To enable graphql metrics
graphql:
  servlet:
    actuator-metrics: true
# 3. To enable to get metrics of spring-actuator from /actuator/prometheus
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus

schema.graphqls

schema.graphqlsを作成して、GraphQLのスキーマを定義します。
schema.graphqlssrc/main/resources/graphsql以下に登録します。

schema.graphqls
type Query {
    bookById(id: ID): Book
    books: [Book]!
}

type Book {
    id: ID
    name: String
    pageCount: Int
}

type Mutation {
    registerBook (
        id: ID
        name: String
        pageCount: Int
    ): Book
}

type Subscription {
    subscribeBooks: Book!
}

Java Model

GraphQL Javaでは、スキーマに定義したJavaのクラスが必要です。まず、Book TypeのJava Modelを作成します。

Book.java
@AllArgsConstructor
@Data
public class Book {
    private String id;
    private String name;
    private int pageCount;
}

Resolver

次に、Query、Mutation、そしてSubscriptionを実装したResolverを作成します。
それぞれ以下の実装クラスの作成が必要で、スキーマに定義したクエリー名と実装したメソッド名は同一にします。
また、実装クラスはSpringのBeanとして登録します。

  • Query : GraphQLQueryResolverを実装したクラスを作成し、スキーマに定義したクエリー名と引数を持つメソッドを追加します。
  • Mutation : GraphQLMutationResolverを実装したクラスを作成し、スキーマに定義したMutation名と引数を持つメソッドを追加します。
  • Subscription : GraphQLSubscriptionResolverを実装したクラスを作成し、スキーマに定義したSubscription名と引数を持つメソッドを実装します。Return値はreactive-streamsPublisherである必要があります。

DataProviderIBookProcessorは独自に作成したクラスです。以下の処理を行います。

  • DataProvider : データ提供クラス。すべてのBookのList、または指定されたbookIdBookを返す。
  • IBookProcessor : 本の登録をイベントとして登録(emit)し、イベントのPublisherを発行(publish)する。
BookResolver.java
@Slf4j
@AllArgsConstructor
@Component
public class BookResolver implements GraphQLQueryResolver,
                                     GraphQLMutationResolver,
                                     GraphQLSubscriptionResolver {
    private final DataProvider dataProvider;
    private final IBookProcessor bookProcessor;

    /**
     * Query: Get all books.
     */
    public List<Book> books() {
        return dataProvider.books();
    }

    /**
     * Query: Retrieve a book by id.
     */
    public Book bookById(String bookId) {
        return dataProvider.bookById(bookId);
    }

    /**
     * Mutation: Register a book.
     */
    public Book registerBook(String id, String name, int pageCount) {
        final Book book = new Book(id, name, pageCount);
        dataProvider.books().add(book);

        // Emit an event for subscription.
        bookProcessor.emit(book);
        return book;
    }

    /**
     * Subscription: Publish an event that a book is registered.
     * Need to return Publisher on reactive-streams.
     */
    public Publisher<Book> subscribeBooks() {
        return bookProcessor.publish();
    }

    /**
     * Error handler. can handle an throwable that occurs in resolver execution.
     */
    @ExceptionHandler(Throwable.class)
    GraphQLError handle(Throwable e) {
        log.error("Failed to execute resolver.", e);
        return new ThrowableGraphQLError(e, "Failed to execute resolver.");
    }
}

わかりやすく図にしてみました。
service_image.png

アプリケーションを実行する

作成したアプリケーションを実行します。SpringBootのアプリケーションを起動し、GraphiQLのエンドポイント(http://localhost:8080/graphiql)にブラウザでアクセスし、Queryを実行します。

  • Query: 本の一覧を取得する。
    query1.png

  • Query: 本のIDを指定して、特定の本を取得する。
    query2.png

  • Mutation: 新しい本を登録する。
    mutation.png

  • Subscription: 新たに登録された本を通知する。
    subscription.png
    ちょっとわかりづらいですが、上のイメージは以下の操作を行った後のイメージです。

    1. 上記Subscriptionを実行する。
    2. 別ブラウザを開いて、Mutationを実行する。
    3. Subscriptionの結果に、登録された本の情報が表示される。

Metricsも正しく収集されていました。グラフ化には以下のサイトにあるMetricatというツールを使っています。

MetricatのPrometheus exporter URLにspring-actuatorのprometheusエンドポイント(http://localhost:8080/actuator/prometheus)を設定し、その後、GraphiQLでクエリーを実行すると以下のようなグラフが表示されます。

metrics.png

まとめ

以上、JavaでGraphQLサーバーを実装する方法について記述しました。実装したコードはあくまでサンプルコードレベルですが、Resolverなど簡単に開発でき、使い勝手は悪くない印象でした。

実務で使用する予定はまだないですが、実務で使うことを想像して気になる点をいくつかあげてみました。

  • Resolverのasyncで実装したい。
    CompletionStage(実装クラス:CompletableFuture)がreturnできるようです。Async Resolvers #1
  • 分散Tracingの導入
    → 一応、ここTracing and Metricsに記述がありますが、Apollo style tracing ? 謎なので後で調べる。
  • MetricにResolverのレイテンシを追加したい。
    Micrometerを使って、実装する必要がある感じ?Resolverの中にハードコードしたくないので、Spring AOPなどで実装したい。後で調べる。
  • Spring WebFlux対応
    → なにかあるので後で調べる。graphql-kickstart-spring-webflux

使用したソースコードは、以下のGitHub上に登録しています。参考にしていただければ幸いです。

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

AndroidでOne Touch(確認無し)で電話する方法(Java)

ネットで、確認なしで電話をかける方法をいくら調べても、

Intent intent = new Intent(Intent.ACTION_DIAL, "tel:0123345678);
のACTION_DIALのところを
Intent intent = new Intent(Intent.ACTION_CALL, "tel:012345678");
のようにACTION_CALLにするだけでOKと書いてあるのに、実行するとなぜか落ちる(?_?)

もちろん、AbdroidManifest.xmlに、
uses-permission android:name="android.permission.CALL_PHONE"
を入れるのも忘れてないし、なぜだろうと悩んだところ、
アプリにCALL_PHONEへのアクセス権を与えてやらなければいけないことが判明。

アプリ起動時に、以下のようにアクセス権を与える許可ダイアログを表示してやらなければならなかった。
CALL_PHONEはdangerousパーミッションだから、ユーザにいちいち許可をもらわなければならないのね(^^;)

public class MainActivity extends AppCompatActivity {
static final int REQUEST_CODE = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)==PackageManager.PERMISSION_DENIED) {
        ActivityCompat.requestPermissions(
                this, new String[] { Manifest.permission.CALL_PHONE }, REQUEST_CODE);
    }

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    if (requestCode == REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Dangerous パーミッションのリクエストが許可
        } else {
            Toast toast = Toast.makeText(getApplicationContext(), "電話機能のアクセス権限を追加しないと使えません", Toast.LENGTH_LONG);
            toast.show();
            finish();
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む