- 投稿日:2020-12-18T23:47:25+09:00
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上でテストフレームワークを起動するための基盤となる
- 上記のプラットフォーム上で動作するテストフレームワークを開発するための
TestEngine
APIを定義している- コマンドラインからプラットフォームを起動するための
Console Launcher
や、 GradleやMaven用のビルドプラグイン、JUnit4ベースのテストランナーなどを提供し、あらゆるTestEngine
を実行できるようになっている- JUnit Jupiter
- JUnit5でテストを書くために最低限必要な依存関係をまとめたもの
- JUnit Vintage
- JUnit3またはJUnit4ベースのテストを実行するための
TestEngine
を提供する長くなりましたが、とりあえずJUnit5でテストを書き始めたいなら、JUnit Jupiterを依存関係に追加しとけばOKです(以下はgradleのサンプル)
build.gradleplugins { 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は便利で簡単です
- 皆さん、テストコードはちゃんと書きましょう
参考
- 投稿日:2020-12-18T23:16:54+09:00
[必見!!!] オブジェクト指向についてまとめてみた!
はじめに
初学者の方にとってオブジェクト指向は参考書でも度々目にするが、そのまま調べずに終わっていませんか?
また、オブジェクト指向わかった気になっている方もいると思います。今回はオブジェクト指向について分かりやすくまとめてみましたのでご覧ください!
この記事のターゲット
- プログラミング初学者
- オブジェクト指向のプログラミング言語を学習している方(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.ポリモーフィズム
継承したコードの一部を変更して利用するための仕組み
コードの一部のみを変更して利用できるのがポリモーフィズム
→例えばボタンの色だけを変えて登録、更新、削除のボタンを作ったことが、一部を変更し継承するという意味になります。終わりに
もし、オブジェクト指向を理解せずにプログラミング学習していた人も、この記事を読んであの部分がそうだったのかと気づくはずです!
皆様のご参考になれば嬉しいです!参考記事
- 投稿日:2020-12-18T23:16:54+09:00
[必見!!!] オブジェクト指向についてまとめてみた!
はじめに
初学者の方にとってオブジェクト指向は参考書でも度々目にするが、そのまま調べずに終わっていませんか?
また、オブジェクト指向わかった気になっている方もいると思います。今回はオブジェクト指向について分かりやすくまとめてみましたのでご覧ください!
この記事のターゲット
- プログラミング初学者
- オブジェクト指向のプログラミング言語を学習している方(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.ポリモーフィズム
継承したコードの一部を変更して利用するための仕組み
コードの一部のみを変更して利用できるのがポリモーフィズム
→例えばボタンの色だけを変えて登録、更新、削除のボタンを作ったことが、一部を変更し継承するという意味になります。終わりに
もし、オブジェクト指向を理解せずにプログラミング学習していた人も、この記事を読んであの部分がそうだったのかと気づくはずです!
皆様のご参考になれば嬉しいです!参考記事
- 投稿日:2020-12-18T22:53:07+09:00
レガシー・バージョンのインストール方法【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.jdk2. dmgファイルのダウンロード
以下のサイトのようにAppleが出している過去のバージョンのdmgをダウンロードして、インストールします。
ダウンロード Java for OS X 2017-001 - Apple
https://support.apple.com/kb/dl1572?locale=ja_JP3. 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はこのディスクにインストールできません。このパッケージのより新しいバージョンがすでにインストールされています。
このような時は以下のようにdmgファイルをsedコマンドで加工して
ModifiedJava6Install.pkg
というファイルを出力します。
具体的な手法としては、script editorというアプリを起動して、以下のコードを入力・保存・実行しましょう。2015年版のスクリプトは以下の通りです。
2015_exchange.scptset 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.scptset 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"}B. 2015年版のエラー
一見インストールに成功したように思えても、以下のようなエラーが出ることがあります。
This package is incompatible with this version of macOS.
色々と模索したのですが、Mojave以降には対応していない模様でした、、、2017年版だとまだ対応しているため、そちらをインストールすることを推奨します。
参考文献
- 投稿日:2020-12-18T21:04:35+09:00
「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
- 投稿日:2020-12-18T21:01:44+09:00
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())); } } }
- 投稿日:2020-12-18T15:18:51+09:00
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のみ追加しています。
アプリケーションを作成する
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-core
とspring-actuator
、micrometer-registry-prometheus
のdependencyも追加しました。前者はSubscription実装のため、後者はMetrics取得のためです。build.gradleplugins { 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,prometheusschema.graphqls
schema.graphqls
を作成して、GraphQLのスキーマを定義します。
schema.graphqls
はsrc/main/resources/graphsql
以下に登録します。schema.graphqlstype 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-streams
のPublisher
である必要があります。
DataProvider
とIBookProcessor
は独自に作成したクラスです。以下の処理を行います。
- DataProvider : データ提供クラス。すべての
Book
のList、または指定されたbookId
のBook
を返す。- 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."); } }アプリケーションを実行する
作成したアプリケーションを実行します。SpringBootのアプリケーションを起動し、GraphiQLのエンドポイント(
http://localhost:8080/graphiql
)にブラウザでアクセスし、Queryを実行します。
Subscription: 新たに登録された本を通知する。
ちょっとわかりづらいですが、上のイメージは以下の操作を行った後のイメージです。
- 上記Subscriptionを実行する。
- 別ブラウザを開いて、Mutationを実行する。
- Subscriptionの結果に、登録された本の情報が表示される。
Metricsも正しく収集されていました。グラフ化には以下のサイトにあるMetricatというツールを使っています。
Metricatの
Prometheus exporter URL
にspring-actuatorのprometheusエンドポイント(http://localhost:8080/actuator/prometheus
)を設定し、その後、GraphiQLでクエリーを実行すると以下のようなグラフが表示されます。まとめ
以上、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上に登録しています。参考にしていただければ幸いです。
- 投稿日:2020-12-18T10:08:37+09:00
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(); } } }