20210430のJavaに関する記事は12件です。

JDBC を使って WEB サーバからデータベースにアクセスする

はじめに JDBC を使って WEB サーバからデータベース(DB)サーバにアクセスする Java Servlet を作成する方法についてまとめました。JDBC とは、WEB サーバから DB サーバにアクセスするためのドライバです。 ここでは、ログインシステムのサンプルを作成します。DB サーバには、MySQL を使用します。 開発環境 Windows 10 (64bit) Eclipse Pleiades All in One(Windows 64bit Full Edition Java Version 2020-06) Tomcat 9 XAMPP 7.4.8(Windows 向け) 事前準備 『Eclipse を使って Tomcat プロジェクトを作成する』、または、『Eclipse Pleiades All in One を使って Tomcat プロジェクトを作成する』を参照して、Eclipse の環境構築と初期設定を行います。 『Windows に XAMPP 環境を構築する』を参照して、XAMPP のインストールとデータベース作成を行い、XAMPP を起動し、Apache と MySQL を起動します。 JDBC ドライバの追加 以下のサイトにアクセスし、「Connector/J」のリンクをクリックします。 https://dev.mysql.com/downloads/ 「Select Operating System」のドロップメニューから「Platform Independent」を選択し、「Platform Independent (Architecture Independent), ZIP Archive」の「Download」ボタンを押します。 「No thanks, just start my download.」のリンクをクリックします。 ⇒mysql-connector-java-8.0.21.zip がダウンロードされます。 ダウンロードが完了したら、任意のフォルダに解凍します。 ⇒「mysql-connector-java-8.0.21.jar」が解凍されます。 『Eclipse Pleiades All in One を使って Tomcat プロジェクトを作成する』で作成した sample プロジェクトの「sample\WEB-INF\lib」に「mysql-connector-java-8.0.21.jar」をコピーします。 「パッケージ・エクスプローラー」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「リフレッシュ」をクリックすると、「パッケージ・エクスプローラー」上のツリーに「mysql-connector-java-8.0.21.jar」が追加されます。 データクラス作成 データベースにアクセスするデータを扱うクラスを作成します。ここでは、ユーザ名やパスワードなどのユーザアカウント情報を扱うので、ユーザアカウントクラスを作成します。 「パッケージ・エクスプローラー」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「新規」>「クラス」をクリックします。 「名前」欄にクラス名を入力して「完了」ボタンを押します。ここでは、「名前」欄に「UserAccount」と入力し、他の設定はデフォルトのままとします。  ⇒「Package Explorer」上のツリーに「sample」>「UserAccount.java」ノードが追加されます。 UserAccount.java package sample; public class UserAccount { } UserAccount.java に、以下の private 宣言のフィールドを追加します。(カプセル化するため、private 宣言にしておきます。) UserAccount.java package sample; public class UserAccount { private String username; // ユーザ名 private String password; // パスワード private String mailaddr; // メールアドレス } 「パッケージ・エクスプローラー」上のツリーノード「UserAccount.java」を選択して、メインメニュー「ソース」>「getter および setter の生成」をクリックします。 ⇒「getter および setter の生成」ダイアログが起動します。 「生成する getter および setter の選択」欄の各フィールドをチェックして「生成」ボタンを押します。 ⇒getter および setterメソッドが自動生成されます。 UserAccount.java package sample; public class UserAccount { private String username; private String password; private String mailaddr; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getMailaddr() { return mailaddr; } public void setMailaddr(String mailaddr) { this.mailaddr = mailaddr; } } データベースアクセスクラス作成 データベースにアクセスするクラスを作成します。JDBC を使用して、MySQL に接続し、SQL 文を実行してデータベース内の検索処理を行います。 「パッケージ・エクスプローラー」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「新規」>「クラス」をクリックします。 Database.java package sample; public class Database { } 「名前」欄にクラス名を入力して「完了」ボタンを押します。ここでは、「名前」欄に「Database」と入力し、他の設定はデフォルトのままとします。 ⇒「Package Explorer」上のツリーに「sample」>「Database.java」ノードが追加されます。 Database.java のソースコードを以下の実装に変更します。コンストラクタで、JDBC を使用して、MySQL(データベース)に接続します。また、searchUser メソッドでは、入力されたユーザ名でデータベース内を検索し、検索結果を取得します。そして、検索したパスワードと入力したパスワードが一致する場合は検索結果を返し、一致しない場合は例外を発生させます。 Database.java package sample; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class Database { final static String DATABASE_CONECT = "jdbc:mysql://localhost:3306/sample?serverTimezone=JST&useUnicode=true&characterEncoding=UTF-8"; final static String SELECT_USERNAME = "SELECT * from sample.account where username=?"; Connection connect; PreparedStatement sqlUsername; // データベース接続処理 public Database() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.jdbc.Driver"); // JDBCドライバを設定する connect = DriverManager.getConnection(DATABASE_CONECT, "root", "root"); // データベースに接続する(ユーザroot/パスワードrootの場合) sqlUsername = connect.prepareStatement(SELECT_USERNAME); // ユーザ名を検索するSQL文を設定する } // データベース内検索 public UserAccount searchUser(String username, String password) throws Exception { UserAccount account = new UserAccount(); sqlUsername.setString(1, username); // ユーザ名をパラメータに設定する ResultSet result = sqlUsername.executeQuery(); // ユーザ名を検索するSQL文を実行する // フォームに入力されたパスワードとデータベースのパスワードが一致する場合、データベースの検索結果を取得する if(result.next() && result.getString("password").equals(password)){ account.setUsername(result.getString("username")); account.setPassword(result.getString("password")); account.setMailaddr(result.getString("mailaddr")); }else{ throw new Exception("ユーザ登録されていないか、パスワードが間違っています。"); } return account; } // データベース切断処理 public void close() throws Exception{ sqlUsername.close(); connect.close(); } } サーブレットクラス作成 サーブレットクラスを作成します。ここでは、ログインシステムの WEB 入力処理を作成します。「login」ボタンが押されたときに、入力された「ユーザ名」と「パスワード」を Database クラスの searchUser メソッドに渡します。このメソッドは入力されたパスワードとデータベースに登録されているパスワードが一致しなければ例外を発生させるため、例外が発生しなければログイン成功となります。 『Eclipse を使って Tomcat プロジェクトを作成する』、または、『Eclipse Pleiades All in One を使って Tomcat プロジェクトを作成する』を参照して、「SampleServlet.java」と「web.xml」を作成します。 「パッケージ・エクスプローラー」上のツリーノード「SampleServlet.java」を選択します。 SampleServlet.java のソースコードを以下の実装に変更します。 SampleServlet.java package sample; import java.io.IOException; import java.io.PrintWriter; // 追加 import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class SampleServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // ↓ここから追加 req.setCharacterEncoding("UTF-8"); // 文字エンコーディング(日本語の文字化け対策) resp.setContentType("text/html; charset=UTF-8"); // 文字エンコーディング(日本語の文字化け対策) String result = ""; try{ // 「検索」ボタンが押された場合、「ユーザ名」をデータベース内から検索し、「パスワード」が一致するかチェックする if(req.getParameter("button").equalsIgnoreCase("search")){ Database daotest = new Database(); UserAccount account = daotest.searchUser(req.getParameter("username"), req.getParameter("password")); result = "ログイン成功" + account.getUsername() + ": " + account.getMailaddr(); daotest.close(); } }catch(Exception e){ e.printStackTrace(); result = "ログイン失敗" + e.getMessage(); } PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head><title>ログイン</title></head>"); out.println("<body>"); out.println("<h2>ログイン結果</h2>"); out.println(result); out.println("</body>"); out.println("</html>"); out.close(); // ↑ここまで追加 } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // ↓ここから追加 resp.setContentType("text/html; charset=UTF-8"); // 文字エンコーディング(日本語の文字化け対策) PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head><title>ログイン</title></head>"); out.println("<body>"); out.println("<h2>ユーザ名とパスワードを入力してください</h2>"); out.println("<form action='servlet' method='POST'>"); out.println("<p>ユーザ名<input type='text' name='username'></p>"); out.println("<p>パスワード<input type='password' name='password'></p>"); out.println("<input type='submit' value='login'>"); out.println("<input type='hidden' name='button' value='search'>"); out.println("</form>"); out.println("</body>"); out.println("</html>"); out.close(); // ↑ここまで追加 } } 実行 メインメニューのアイコン「Tomcat 再起動」をクリックします。 ブラウザを起動して以下の URL にアクセスして、ユーザ名とパスワードを入力します。 http://localhost:8080/sample/servlet データベースに登録されているユーザ名とパスワードを入力するとログインに成功し、一致しないとログインに失敗します。 【ログイン成功】 【ログイン失敗】 デプロイ環境 ここから、デプロイの準備をします。ここでは、Ubuntu にデプロイします。 Ubuntu 20.04 LTS OpenJDK 11 Tomcat 9 デプロイ環境の事前準備 『Eclipse を使って Tomcat プロジェクトを作成する』、または、『Eclipse Pleiades All in One を使って Tomcat プロジェクトを作成する』を参照して、デプロイ先の PC に OpenJDK と Tomcat をインストールします。また、warファイルを作成しておきます。 『Ubuntu に LAMP 環境を構築する』を参照して、デプロイ先のPCに LAMP 環境を構築し、データベースを作成します。 または、『Ubuntu に XAMPP 環境を構築する』を参照して、デプロイ先のPCに XAMPP 環境を構築し、データベースを作成します。 デプロイ デプロイ環境の準備ができたら、war ファイルをデプロイ先に配置します。 tomcat の デフォルト設定では、root ユーザ以外のユーザに実行権限がないため、root ユーザになります。 $ sudo su 作成した WAR ファイルを TOMCAT の webapps 下のフォルダにコピーします。 # mv sample.war /opt/apache-tomcat-9.0.37/webapps TOMCAT を起動します。 # cd /opt/apache-tomcat-9.0.37/bin # sh startup.sh ブラウザを起動して以下の URL にアクセスして、ユーザ名とパスワードを入力します。以降、Windowsで実行したときと同様に、ログイン画面が表示され、同様に動作します。 http://{UbuntuのIPアドレス}:8080/sample/servlet TOMCAT を終了します。 # sh shutdown.sh
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Azure Cognitive Services】Javaで作る「オウム返しナナミ」

はじめに しゃべった言葉をオウム返ししてくるぬいぐるみのことは知っていますか?遊んだことありますか? 「僕の名前は太郎!」としゃべったら、ぬいぐるみも「僕の名前は太郎!」と同じ言葉を言い返してきます。ぬいぐるみの電池が切れるまで、ずっとオウム返ししてくるわけです。 今回は、Azure Cognitive ServicesのSpeech Serviceを使って、同じような仕組みを作ろうと思います。 Speech Serviceでニューラル音声というより人間らしい声を発する音声が提供されており、その中に、Nanamiという日本語で女性の声があるので、これを使って作っていきたいと思います! この記事はJava開発者のためのAzure入門というキャンペーンへの記事投稿です! 実行環境・前提 Windows 10 home IntelliJ IDEA インストール手順は以下のサイト参考 IntelliJ IDEAインストール手順<Windows向け> java sdk 11.0.2 Azureサブスクリプション所持 手順 以下の手順で進めていきます。 1. Azure portal上でCognitive Servicesのリソース作成と準備 2. IntelliJ IDEAでJavaの実行環境を構築 3. 「オウム返しナナミ」のプログラム作成 Azure portal上でCognitive Servicesのリソース作成と準備 ① Azure portalを開きます。 ② 上部にあるリソース、サービス、ドキュメントの検索で「Cognitive Services」と入力し、選択します。 ③ 作成をクリックし、Marketplaceが出てきたら「音声」と検索し、Microsoftが提供する音声サービスを選択し、作成をクリックします。 ④ 作成の内容に関しては、以下のように設定し、作成をクリックする。少し時間がかかります。 注意! 場所に関して、ニューラル音声を使いたい場合、東日本や西日本はサポートされていないので選択してはいけません!サポートされているリージョンについてはMSドキュメントで確認してください。米国東部はサポートされているので、私は米国東部を選択しています。 ⑤ 「デプロイが完了しました」と表示されたら、リソースに移動をクリックしましょう。 ⑥ 左側の欄の[ リソース管理 ] > [ キーとエンドポイント ]をクリックします。 ⑦ キー1と場所をメモ帳などにメモしておきます。これでリソースの作成と準備は完了です!お疲れ様でした! IntelliJ IDEAでJavaの実行環境を構築 ① IntelliJ IDEAを起動したら、新規プロジェクトをクリックします。 ② Mavenを選択し、プロジェクト SDKを「11 ver 11.0.2」にし、(筆者は選んだのがこのバージョンです。)次へをクリックします。 ③ 名前を「SpeechNanami」にして、完了をクリックします。 ④ pom.xmlを開き、以下のように追記します。 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>SpeechNanami</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <repositories> <repository> <id>maven-cognitiveservices-speech</id> <name>Microsoft Cognitive Services Speech Maven Repository</name> <url>https://csspeechstorage.blob.core.windows.net/maven/</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.microsoft.cognitiveservices.speech</groupId> <artifactId>client-sdk</artifactId> <version>1.12.1</version> </dependency> </dependencies> </project> <repositories>~</repositories>と<dependencies>~</dependencies>を下に追記しています。 追記後、右上あたりに出てくるアイコン「Load Maven Changes」をクリックします。もしくは、Ctrl + Shift + Oでも大丈夫です。 ⑤ [ src ] > [ main ] > [ java ]を右クリックし、[ 新規 ] > [Javaクラス]で「NanamiSpeech」という名前でクラスファイルを作成します。これで実行環境の構築は終了です!お疲れ様でした! 「オウム返しナナミ」のプログラム作成 いよいよプログラムの作成です。 先ほど作成した「NanamiSpeech」という名前のクラスファイルに以下のコードを入力します。 import com.microsoft.cognitiveservices.speech.*; import java.util.concurrent.ExecutionException; public class NanamiSpeech { static String subscriptionKey = "YourSubscriptionKey"; static String serviceRegion = "YourServiceRegion"; public static void main(String[] args) throws InterruptedException, ExecutionException{ var config = SpeechConfig.fromSubscription(subscriptionKey, serviceRegion); config.setSpeechRecognitionLanguage("ja-JP"); var synthesizer = new SpeechSynthesizer(config); var recognizer = new SpeechRecognizer(config); while(true) { var result = recognizer.recognizeOnceAsync().get(); var text = result.getText(); System.out.println("話した内容:" + text); synthesizer.SpeakSsml(CreateTextReadOut(text)); } } static String CreateTextReadOut(String text) { return "<speak version='1.0' xmlns='https://www.w3.org/2001/10/synthesis' xml:lang='ja-JP'><voice name='ja-JP-NanamiNeural'>" + text + "</voice></speak>"; } } フィールド変数にある、subscriptionKeyとserviceRegionにメモしておいたキー1と場所に書き換えれば、完成です! Ctrl + Shift + F10などで実行してみてください。何か話してみると、以下のようにコンソール上に話した内容が出力され、さらにNanamiがその内容を話してくれます!! 話した内容:こんにちは 話した内容:私の名前はななみです。 話した内容:私の好きな食べ物はりんごです。 ・・・ ・・ ・ 以上で、「オウム返しナナミ」の完成です!お疲れ様でした!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Iteratorはどういう時に使う必要があるのか

3通りの方法で同じ処理を確認 配列内の値をぐるぐる確認して一致するものがあったら、、、というパターン。 例として適当に以下のサンプルコードを用意した。今回はData型の配列のidが一致した要素の時はフラグを立てるというもの。比較するときに取り上げるのは「中略」以降の内容のみ。 ①普通のfor文を扱った場合 public class Data { public int id = 1000; public int date = 20210430; public String name = "name"; public int getId() { return id; } public void setId(int id) { this.id = id; } //他、dateとnameも同様の処理があり、setメソッドはどこかで使われているとする } public class Main { private static final int DEFAULT_ID = 1000; private boolean isDefault = false; private List<Data> dataList = new List<>(); //~中略~ for (int i = 0; i < dataList.size(); i++) { Data data = dataList.get(i); if (data.getId == DEFAULT_ID) { isDefault = true; } } } これが最も初歩的に学ぶfor文の扱い方かと思われる。 ②拡張for文を扱った場合 for (Data data : dataList) { if (data.getId == DEFAULT_ID) { isDefault = true; } } dataList配列にあるData型の要素を順に1つずつ取り出してループする方法である。そして次がfor文を使わずにやる方法。 ③iteratorを扱った場合 Iterator<Data> ite = dataList.iterator(); while (ite.hasNext()) { Data data = it.next(); if (data.getId == DEFAULT_ID) { isDefault = true; } } iteratorを扱ってもできるが、ソースを比べても明らかに②拡張for文が一番シンプルでいい気がする。では③のiteratorを使わないといけない場面はどういう時なのだろうか気になった。 関わっていた案件で、iteratorが使われていて「拡張for文でもよくない?」と思ってローカルで修正してみたところ、ConcurrentModificationExceptionの例外が発生してしまった。調べてみると この例外は、オブジェクトの並行変更を検出したメソッドによって、そのような変更が許可されていない場合にスローされます。 とあるがよくわからなく、iteratorについて手元にあった参考書を読んでみると、 たとえばループをしながら、要素を削除するには、イテレーターを利用します。 例外が発生したケースでは、上で挙げたサンプルとは違って一致した時はその要素を配列から削除する実装だった。まさにこれに該当することがわかった。 Iteratorを利用しなければならないケース 1. ループの中で要素を削除する 以下の実装だとNG for (Data data : dataList) { if (data.getId == DEFAULT_ID) { dataList.remove(data); //ConcurrentModificationException発生 } } これをIteratorを扱って書き直すと例外が発生しない。 Iterator<Data> ite = dataList.iterator(); while (ite.hasNext()) { Data data = it.next(); if (data.getId == DEFAULT_ID) { ite.remove(); } } ちなみに拡張for文はNGだったが、①の普通のfor文なら試した限りだと例外が発生しなかったのでループ中に要素を削除したいなら普通のfor文は使える様子。 2. 配列を逆順に読み込む ListIterator<Data> ite = dataList.listIterator(data.size()); while (ite.hasPrevious()) { if (data.getId == DEFAULT_ID) { ite.remove(); } } for文でやる時は要素番号を工夫すればいけるだろうが、こっちの方が正確かつ簡単なのでこういうケースはIteratorを使っていくのが良い。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのコレクションの特徴〜List編〜

業務でJavaのコレクションをよく触るが、なんとなくの理解で終わってしまっていたので、本を読んで特徴をまとめてみました。 ここに書いてあるのは パーフェクトJava の内容と実際に自分が業務で使っている際の感想になります。 コレクションフレームワーク コレクションとは何なのか? モノの集まりを表現するモノであり、かつ、モノの集まりに対する操作を提供するモノ Javaのコレクションフレームワーク コレクションの共通操作をインターフェースとしてまとめ、個別のデータ構造やアルゴリズムを(インターフェースを実装した)具象クラスとして提供する。 開発者は決められた操作に従ったメソッド呼び出しをするだけで、データ構造やアルゴリズムの詳細を気にすることなく利用できる。 どんな種類があるのか? リスト マップ セット スタック、キュー、デック 今回はリストについてまとめています。 リストとは? 順序通り並んだ集まり(シーケンスと呼ぶ流儀もあるらしいです。) 主なメソッド メソッド名 意味 個人的な使用頻度 add 要素の追加 低 addAll 要素をまとめて追加 低 contains 要素の存在チェック 高 get 要素の取得 高 indexOf 要素の検索 低 lastIndexOf 要素の検索(後方から) 低 remove 要素の削除 中 set 要素の置換 低 size 要素数の取得 高 subList 部分リストの取得(要素コピーはせずに要素を共有した部分リストを返す) 低 リストの具象クラス ArrayList 内部的には配列を使ったデータ構造 List<String> fruitList = new ArrayList<>(Arrays.asList("Apple", "Grape", "Orange")); LinkedList リンクでノードを繋ぐデータ構造 List<String> fruitList = new LinkedList<>(Arrays.asList("Apple", "Grape", "Orange")); それぞれの特徴 ArrayListの特徴 良い点 インデックスを指定して要素を読み出す速度が速い[get] インデックスを指定して要素を書き換える速度が速い[set] 上記から導かれる特徴で、先頭から順に全ての要素をなめる処理が早い 悪い点 要素の挿入が遅いことがある(先頭に近い位置への挿入は遅い。末尾に近い位置への挿入は早い時もあるが、遅い時もある)[add] 要素の削除が遅いことがある(先頭に近い位置の削除ほど遅く、末尾に近い位置の削除ほど早い。最末尾の削除は高速)[remove] 条件に合致した要素を検索する処理の速度はあまり早くない(工夫によりかなりはやくもできる)[contains,indexOf,lastIndexOf] メモリ使用量という観点 ArrayListは連続したメモリ領域を使いうため、一度確保した連続メモリ領域のサイズを容易に広げられない場合、新しい連続メモリ領域を確保してそこに古い領域のデータをコピーする挙動になる。そのため、たとえ末尾への要素の挿入であっても上述のコピー処理がとても重くなる可能性があるので注意が必要となる。解決策としては、予め要素数の上限を確保しておくことが挙げられる。 ただ個人的にリストの実装時はなるべくimmutableな実装をするべきだと考えているので、そちらを意識していれば上記の問題にぶち当たることも少なくなるかと思います。 LinkedListの特徴 良い点 要素の挿入が早い(ただし多くの場合、挿入の前に検索があることに注意)[add] 要素の削除が早い(ただし多くの場合、削除の前に検索があることに注意)[remove] 悪い点 インデックスを指定して要素を読み出す速度はあまり速くない[get] インデックスを指定して要素を書き換える速度はあまり速くない[set] 条件に合致した要素を検索する処理の速度はあまり速くない[contains,indexOf,lastIndexOf] リストの具象クラスの使い分け 要素の読み込みが中心(要素全てをなめる処理や検索)の場合、ArrayListの方が効率的 要素の挿入や削除の頻度が高い場合、LinkedListの方が効率的 要素の書き換え処理が多い場合、ArrayListの方が効率的 追加調査(ソートに関して) ソートの処理はArrayListの方が早い 理由としてはLinkedListはソートの内部処理として1度Javaの配列に置換して、ソートするのに対してArrayListはそのままの状態でソートするため。(参考:ArrayList vs LinkedList: Sort, Get and Iteration) リストはなるべくimmutableで使用すべき?(ここからは個人の意見です) immutableとは? immutableとは不変という意味です。そのためimmutableなリストは作成後に変更されることがありません。 メリットとしては、immutableなリストを使用することで状態の変化によるリスクを気にする必要がなくなります。 リストの要素を変えてしまうと予想外の操作の影響を受け、バグを生じさせてしまう可能性が上がるため、set等の書き換えのメソッドはなるべく使わないようにした方が安全性という観点で良いです。 ただ一方で、要素を追加したい時もリストを作り直すことになるので、パフォーマンスに影響を与えることもあります。そのためパフォーマンスを追求しなければならない状況ではいまいちなこともあります。なので、状況に応じて使い分けは必要ではありますが、基本的にはオブジェクト同様にimmutebleなリストを扱うのが良いかと思います。 immutableなリストの作成方法は以下の記事がわかりやすかったので、ぜひ参照してください。 https://qiita.com/kageryosan/items/b1699b477a9bf325c2ce まとめ いろいろ書きましたが、なんだかんだ基本的にはimmutebleなArrayListを使う機会が一番多くなりそうだなと調べながら感じました。ただ、要素の挿入削除などが主の操作になるパターン(BEの処理だとあまりこんなケースはなさそうですが)に関してだけはLinkedListを使っていくべきみたいです。 あと、contains(存在チェック)とかは思ったより早い処理ではないなと感じたので、チェック→取得みたいなことはせずに直接取得してなかったら〜みたいな処理を書いたほうが効率的になるなぁという学びがありました。 次はMapについて書こうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(初心者向け)ほぼコピペでAWS SDK For Java2.0 Rekognitionのサンプルコード(Java)で顔認証してみた

背景 GW暇ですね。お家で手軽に遊べる技術はないか?ということでAWSの公式DOCを見ていると面白そうな題材を発見しましたので試してみました。 https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javav2/example_code/rekognition/src/main/java/com/example/rekognition つくるもの AWS RekognitonをJavaで実装した顔認証アプリ 大まかな流れ IAMユーザー作成→IAMユーザーを作成して、Rekognitionサービスにアクセスするための鍵取得→鍵を環境変数に設定→Javaソースコピペ 用意するもの EclipseでMavenプロジェクトを開発できる環境(Java8以上) AWS無料アカウント ①AWSコンソールのIAMでユーザーを作成する まずはJavaでAWSサービスを使うために必要なアクセスキーとシークレットキーを取得するため、IAMユーザー作成を行います。 IAMをAWSコンソール上で検索 メニュー:ユーザーからユーザーを追加ボタンを選択 ユーザー名を入力し、アクセスの種類で「プログラムによるアクセス」にチェックを入れ次へ。 「既存のポリシーを直接アタッチ」を選択し、Rekognitionで検索をかけAmazonRekognitionFUllAccessのチェックボックスにチェックします。 この画面は何もしなくて問題ないです。 この画面もそのまま進みます。 この画面がとても重要です。アクセスキーIDとシークレットキーをメモします。シークレットアクセスキーはこの段階でしか見れないので、忘れてしまうとまたユーザーを作り直す必要があります。 ②手順①で取得したキー2つを環境変数に設定する JAVAの環境構築時にJAVA_HOMEを設定したと思いますがそれと同様です。 ③EclipseでMavenプロジェクトを作成する Eclipseツールバーのファイル→新規→Mavenプロジェクト シンプルなプロジェクトの作成をチェックし次へ 適当なグループIdとアーティファクトID入力し完了 ④Javaファイルを作成する src/main/java上で右クリック→新規→クラス 名前を入力しどのメソッドスタブを作成しますか?はpublic static void...にチェック 作成したクラス(DetectFaces)ファイルに下記コードを入力。エラーが出まくりますが次の手順で解消します。 公式のGithubソースから変えてるのはRegionを東京(ap-north-east-1)にしているのとSysoutの一部を消しています。 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.List; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rekognition.RekognitionClient; import software.amazon.awssdk.services.rekognition.model.AgeRange; import software.amazon.awssdk.services.rekognition.model.Attribute; import software.amazon.awssdk.services.rekognition.model.DetectFacesRequest; import software.amazon.awssdk.services.rekognition.model.DetectFacesResponse; import software.amazon.awssdk.services.rekognition.model.FaceDetail; import software.amazon.awssdk.services.rekognition.model.Image; import software.amazon.awssdk.services.rekognition.model.RekognitionException; public class DetectFaces { public static void main(String[] args) { String sourceImage = args[0]; Region region = Region.AP_NORTHEAST_1; RekognitionClient rekClient = RekognitionClient.builder() .region(region) .build(); detectFacesinImage(rekClient, sourceImage ); rekClient.close(); } public static void detectFacesinImage(RekognitionClient rekClient,String sourceImage ) { try { InputStream sourceStream = new FileInputStream(new File(sourceImage)); SdkBytes sourceBytes = SdkBytes.fromInputStream(sourceStream); // Create an Image object for the source image Image souImage = Image.builder() .bytes(sourceBytes) .build(); DetectFacesRequest facesRequest = DetectFacesRequest.builder() .attributes(Attribute.ALL) .image(souImage) .build(); DetectFacesResponse facesResponse = rekClient.detectFaces(facesRequest); List<FaceDetail> faceDetails = facesResponse.faceDetails(); for (FaceDetail face : faceDetails) { AgeRange ageRange = face.ageRange(); System.out.println("The detected face is estimated to be between " + ageRange.low().toString() + " and " + ageRange.high().toString() + " years old."); System.out.println("There is a smile : "+face.smile().value().toString()); } } catch (RekognitionException | FileNotFoundException e) { System.out.println(e.getMessage()); System.exit(1); } // snippet-end:[rekognition.java2.detect_faces.main] } } ⑤pom.xmlファイルに追記する projectタグの間に追記しctrl+Sなりで保存します。保存するとほとんど前手順のエラーが消えます。 <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.16.29</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>rekognition</artifactId> </dependency> </dependencies> ⑥コンパイラ設定 コンパイラがデフォルトで1.5などになってしまっている場合今回のコードだと扱えない箇所があるので自分が使うバージョンに設定します。プロジェクト名で右クリック→ビルドパス→ビルド・パスの構成 メニューからJavaコンパイラーを選択し、Javaビルド・パス上の実行環境'J2SE-1.5'から準拠を使用のチェックを外し、プルダウンから自分か使うJavaのバージョンを選択し適用して閉じるを押す。 ⑥顔認証実行 プロジェクト名上で右クリック→実行→実行の構成 プログラムの引数に、顔認証対象の画像の絶対パスを入力 ちなみに笑顔pngの中身です。 コンソールを見てみると、画像の人は14-26歳の人で、笑顔という解析結果が出ます。 怒り顔でもやってみましょう。 コンソールを見てみると、画像の人は25-39歳の人で、笑顔ではないという解析結果が出ます。 感想 特に機械学習の知識や技術力がなくても楽しめました。今後はフロントでファイルアップロード機能なりカメラ機能(フロント?Kotlin)なり実装して、連携してもっと身近なものも作りたい所存です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(初心者向け)ほぼコピペでAWS SDK For Java2.0 Rekognitionのサンプルコード(Java)で顔認識してみた

背景 GW暇ですね。お家で手軽に遊べる技術はないか?ということでAWSの公式DOCを見ていると面白そうな題材を発見しましたので試してみました。 https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javav2/example_code/rekognition/src/main/java/com/example/rekognition つくるもの AWS RekognitonをJavaで実装した顔認識アプリ 大まかな流れ IAMユーザーを作成して、Rekognitionサービスにアクセスするための鍵取得→鍵を環境変数に設定→Javaソースコピペ 事前に用意するもの EclipseでMavenプロジェクトを開発できる環境(Java8以上) AWS無料アカウント ①AWSコンソールのIAMでユーザーを作成する まずはJavaでAWSサービスを使うために必要なアクセスキーとシークレットキーを取得するため、IAMユーザー作成を行います。 IAMをAWSコンソール上で検索 メニュー:ユーザーからユーザーを追加ボタンを選択 ユーザー名を入力し、アクセスの種類で「プログラムによるアクセス」にチェックを入れ次へ。 「既存のポリシーを直接アタッチ」を選択し、Rekognitionで検索をかけAmazonRekognitionFUllAccessのチェックボックスにチェックします。 この画面は何もしなくて問題ないです。 この画面もそのまま進みます。 この画面がとても重要です。アクセスキーIDとシークレットキーをメモします。シークレットアクセスキーはこの段階でしか見れないので、忘れてしまうとまたユーザーを作り直す必要があります。 ②手順①で取得したキー2つを環境変数に設定する JAVAの環境構築時にJAVA_HOMEを設定したと思いますがそれと同様です。 ③EclipseでMavenプロジェクトを作成する Eclipseツールバーのファイル→新規→Mavenプロジェクト シンプルなプロジェクトの作成をチェックし次へ 適当なグループIdとアーティファクトID入力し完了 ④Javaファイルを作成する src/main/java上で右クリック→新規→クラス 名前を入力しどのメソッドスタブを作成しますか?はpublic static void...にチェック 作成したクラス(DetectFaces)ファイルに下記コードを入力。エラーが出まくりますが次の手順で解消します。 公式のGithubソースから変えてるのはRegionを東京(ap-north-east-1)にしているのとSysoutの一部を消しています。 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.List; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.rekognition.RekognitionClient; import software.amazon.awssdk.services.rekognition.model.AgeRange; import software.amazon.awssdk.services.rekognition.model.Attribute; import software.amazon.awssdk.services.rekognition.model.DetectFacesRequest; import software.amazon.awssdk.services.rekognition.model.DetectFacesResponse; import software.amazon.awssdk.services.rekognition.model.FaceDetail; import software.amazon.awssdk.services.rekognition.model.Image; import software.amazon.awssdk.services.rekognition.model.RekognitionException; public class DetectFaces { public static void main(String[] args) { String sourceImage = args[0]; Region region = Region.AP_NORTHEAST_1; RekognitionClient rekClient = RekognitionClient.builder() .region(region) .build(); detectFacesinImage(rekClient, sourceImage ); rekClient.close(); } public static void detectFacesinImage(RekognitionClient rekClient,String sourceImage ) { try { InputStream sourceStream = new FileInputStream(new File(sourceImage)); SdkBytes sourceBytes = SdkBytes.fromInputStream(sourceStream); // Create an Image object for the source image Image souImage = Image.builder() .bytes(sourceBytes) .build(); DetectFacesRequest facesRequest = DetectFacesRequest.builder() .attributes(Attribute.ALL) .image(souImage) .build(); DetectFacesResponse facesResponse = rekClient.detectFaces(facesRequest); List<FaceDetail> faceDetails = facesResponse.faceDetails(); for (FaceDetail face : faceDetails) { AgeRange ageRange = face.ageRange(); System.out.println("The detected face is estimated to be between " + ageRange.low().toString() + " and " + ageRange.high().toString() + " years old."); System.out.println("There is a smile : "+face.smile().value().toString()); } } catch (RekognitionException | FileNotFoundException e) { System.out.println(e.getMessage()); System.exit(1); } // snippet-end:[rekognition.java2.detect_faces.main] } } ⑤pom.xmlファイルに追記する projectタグの間に追記しctrl+Sなりで保存します。保存するとほとんど前手順のエラーが消えます。 <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.16.29</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>rekognition</artifactId> </dependency> </dependencies> ⑥コンパイラ設定 コンパイラがデフォルトで1.5などになってしまっている場合今回のコードだと扱えない箇所があるので自分が使うバージョンに設定します。プロジェクト名で右クリック→ビルドパス→ビルド・パスの構成 メニューからJavaコンパイラーを選択し、Javaビルド・パス上の実行環境'J2SE-1.5'から準拠を使用のチェックを外し、プルダウンから自分か使うJavaのバージョンを選択し適用して閉じるを押す。 ⑥顔認識実行 プロジェクト名上で右クリック→実行→実行の構成 プログラムの引数に、顔認識対象の画像の絶対パスを入力 ちなみに笑顔pngの中身です。 コンソールを見てみると、画像の人は14-26歳の人で、笑顔という解析結果が出ます。 怒り顔でもやってみましょう。 コンソールを見てみると、画像の人は25-39歳の人で、笑顔ではないという解析結果が出ます。 感想 特に機械学習の知識や技術力がなくても楽しめました。今後はフロントでファイルアップロード機能なりカメラ機能(フロント?Kotlin)なり実装して、連携してもっと身近なものも作りたい所存です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Functions で Spring Cloud Functionを使う

Azure Functions で Spring Cloud Functionを使う Spring には、 Spring Cloud Function という関数指向なフレームワークが存在しています。これをAzure Functionsと組み合わせて使うことができるのですが、今回はそのあたりを試してみたいと思います。 Java で Azure Functions は、そこそこ書いているのですが、.NET と違いDI できなかったり、構成設定がお手軽でなかったりするのがイマイチだったりします。あとは Graceful shutdown や、Durable Functions などがサポートされてないこともですが。 今回試そうと思ったのは、 Azure Functions も Spring フレームワークに乗ってさえしまえば、D Iや構成設定の懸案が解消されるのでは無いかと思ったわけです。 Spring Cloud Function Spring Cloud Function の説明は以下を参照しましょう。 Spring Cloud Function ざっくり説明すると、規約に従って定義したBeanやクラスが、特別なことをせずに、そのままREST API になったりしてくれます。 例えば、以下のように Function な、Beanを定義しておくと、それがそのままREST APIとなります。 @Bean public Function<String, String> uppercase() { return value -> value.toUpperCase(); } 上記の例だと、String -> String の関数ですので、データをPOSTするか、繋げてパスに指定するとデータと勝手にバインドされます。 $ curl "http://localhost:8080/uppercase/abcdefg ABCDEFG また、Mono/Fluxを利用して Reactorによる非同期プログラムにも対応しています。Azure SDK も Reactorを利用している場合が多いので、相性が良いと思います。 @Bean Function<Mono<String>, Mono<String>> uppercaseAsync() { return value -> value.map(x -> x.toUpperCase()); } これ以外にも、Functionを実装したクラスを用意し、関数が置かれている場所をspring.cloud.function.scan.packages=com.example.demo.functions と設定しておくと、そのパッケージ内をスキャンして勝手にAPI化してくれます。 public class Echo implements Function<Mono<String>, Mono<String>> { @Override public Mono<String> apply(Mono<String> name) { return name.map(x -> x + x); } } 思った以上に便利なので、普通の HttpTrigger くらいなら、これをWebApps にデプロイしてもいい気がしてきました。 Azure Functions で使ってみる この Spring Cloud Function の フレームワークを Azure Functions にデプロイできるようにするライブラリが提供されています。Azure 以外にも GCP や AWS の labmda だったりもあるようです。 以下のドキュメントに詳細が書かれています(ちょっとドキュメントが書かれた日が古くてあまり頑張ってないのではないか疑惑が) Azure での Spring Cloud Function の概要 | Microsoft Docs HttpTriggerを定義するには、以下のように AzureSpringBootRequestHandler<T,R> を実装したクラスを用意します。以下の場合 POJOとして、User を入力としてバインドし、handleRequest メソッドの返却値としtGreeting を受け取るようなクラスを定義しています。簡単にいえば、単純な 入力 User 、出力 Greeting という関数なわけです。 import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler; public class HelloHandler extends AzureSpringBootRequestHandler<User, Greeting> { public HttpResponseMessage execute(@HttpTrigger(name = "request", methods = { HttpMethod.GET, HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request, @FunctionName("hello") ExecutionContext context) { context.getLogger().info("hello api is invoked."); User user = request.getBody() .filter((u -> u.getName() != null)) .orElseGet(() -> new User( request.getQueryParameters() .getOrDefault("name", "world"))); context.getLogger().info(greeting.toString()); context.getLogger().info("Greeting user name: " + user.getName()); return request .createResponseBuilder(HttpStatus.OK) .body(handleRequest(user, context)) .header("Content-Type", "application/json") .build(); } } handleRequest によって呼び出されるのは、別に定義 Function<T,R> を実装したクラスとなります。Mono で囲っても良いし、Userそのものでも受け取ることもできます。同じシグネチャの関数が複数あると例外が出ると思います。 @Component public class HelloFunction implements Function<Mono<User>, Mono<Greeting>> { public Mono<Greeting> apply(Mono<User> mono) { return mono.map(user -> new Greeting("Hello, " + user.getName() + "!")); } } 実行してリクエストを投げると、期待通りの結果が得られるでしょう。 $ curl http://localhost:7071/api/hello -d "{\"name\":\"statemachine\"}" { "message": "Hello, statemachine!" } DI してみる 正しくDIが動作するか、構成クラスを定義して、DIしてみましょう。 @Configuration @ConfigurationProperties("myconfig") public class MyConfig { private String prefix; public String getPrefix() { return this.prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } } コンストラクタインジェクションは使えないぽいので(例外がでる)、@Autowired でDIします。 @Component public class HelloFunction implements Function<Mono<User>, Mono<Greeting>> { @Autowired private MyConfig config; public Mono<Greeting> apply(Mono<User> mono) { return mono.map(user -> new Greeting(config.getPrefix() + ", " + user.getName() + "!")); } } 環境変数に、set myconfig.prefix=Good morning とでもして、実行すると、ちゃんと MyConfig クラスから値が取得できました。 $ curl http://localhost:7071/api/hello -d "{\"name\":\"statemachine\"}" { "message": "Good morning, statemachine!" } 試した限りでは、リポジトリやサービスもDIできそうでした。 まとめ Azure Functions + Spring Cloud Functionは、 Bean を定義しただけでREST API になるわけではないので、そのあたりの簡易性は失われてしまいますが、Azure Functions for Java にはない、Spring Boot 側のフレームワークが使えるようになるのはとても良い感じに使えそうです。 ただ、他のトリガとかでも使えるのかは分りません。HttpTriggerに特化しているので、時間が合ったら深掘りしてみたい感じです。 追伸 現バージョンですと AzureSpringBootRequestHandler は、 @Deprecated になっており FunctionInvoker を使えと Javadoc に書かれているのですが、返却値側の Mono を正しく処理できていなくて、上のサンプル例ですと、 Greeting が返るべきところ、 Mono<Greeting> そのものが返ってきてしまいます。ちょっとバグっているのか仕様なのか分りませんが、時間があればIssueでも投げておきたいところです。 あまり使われてないのかなと思わざるを得ないバグでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AliViewをUbuntuでコンパイルする時のエラーメッセージを回避する

AliViewという名前のfasta等の配列を閲覧できる素敵なソフトウェアがあります。 このソフトウェアをソースコードからコンパイルしようとすると、以下のメッセージが出ることがあります。 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.0.2:compile (default-compile) on project AliView: Compilation failure [ERROR] Failure executing javac, but could not parse the error: [ERROR] エラー: ソース・オプション5は現在サポートされていません。6以降を使用してください。 [ERROR] エラー: ターゲット・オプション1.5は現在サポートされていません。1.6以降を使用してください。 このメッセージは、pom.xml の設定で、OpenJDKのバージョンを変更することで回避することができます。 具体的には、下記のように <source> と <target> を 1.6 以降に変更します。 --- a/pom.xml +++ b/pom.xml @@ -58,8 +58,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> - <source>1.5</source> - <target>1.5</target> + <source>1.6</source> + <target>1.6</target> </configuration> </plugin> <plugin> これにより、無事に最後までコンパイルが成功します。 jdkのバージョンを1.6にするプルリクエストを出してみました。もしもそのプルリクエストがマージされたら、このページのtipsは要らなくなるかも知れません。 この記事は以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

主な関数型インターフェースとラムダ式での記述方法について

主な関数型インターフェースとラムダ式での記述方法について ラムダ式と関数型インターフェースについて学んだので記事にしてみようと思います。 ラムダ式を利用する理由として、関数型インターフェースを簡潔に利用できるというメリットが大きいので、まずは主な関数型インターフェイスを紹介しておきます。 インターフェイス名 抽象メソッド名 処理 Function< T , U > R apply(T t) 実装するメソッドは引数としてTを受け取り、Uを返す Consumer< T > void accept(T t) 実装するメソッドは引数としてTを受け取り、何も返さない Predicate< T > boolean test(T t) 実装するメソッドは引数としてTを受け取り、boolean値を返す Supplier< T > T get() 実装するメソッドは引数を受け取らず、Tを返す ラムダ式の使い方はこちらになります。 ( 実装するメソッドの引数 ) -> { 処理 } 左辺にはメソッドの引数を記述し、"->"を記載したあとにメソッドの処理内容を記述します。 まずはラムダ式を利用せずに関数型インターフェースを利用したコードを書いてみます。 public class Main{ public static void main (String[] args){ int u = new Function<Integer,Integer>(){ public Integer apply(Integer i) { return i + 10 ; } }.apply(5); } } 上記のコードをラムダ式を利用して、コードを記載してみます public class Main{ public static void main (String[] args){ Function<Integer,Integer> foo = (Integer i) -> { return i + 10 ; }; Integer u = foo.apply(5); } 3行目でFunctionインターフェイスの実装クラスfooを宣言し、抽象メソッドapplyの処理を定義しています。 一つ目のコードと比べてコードが簡潔になっていますが、ラムダ式では更に記述を省略できます。 (Integer i) 引数が一つの場合は()が省略でき、引数の型はインターフェースクラスの宣言時に決定しているため、データ型の宣言も省略できます。 { return i + 10 ; } 処理が一行の場合は、{}が省略でき、{}が省略されている場合はreturnも省略できます。 これらの省略を行ったコードがこちらになります。 public class Main{ public static void main (String[] args){ Function<Integer,Integer> foo = i -> i + 10 ; Integer u = foo.apply(5); } } まとめ 関数型インターフェースをラムダ式で実装する事でコードを簡潔に記述できる。 ( 実装するメソッドの引数 ) -> { 処理 }でラムダ式を記述できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring MVCにおけるCoroutine(Kotlin)をきちんと理解する

Spring MVCでコルーチン(Kotlin)を使うこととなったため、調査しました。なかなか難しいなぁと思いました・・。 Coroutineとは何か? 途中で中断できる「処理のまとまり」のことです。中断すると制御が他の処理に移りますが、処理(routine)同士が制御を互いに移し合う様から、接頭辞「Co」がついています。 中断(suspend)とは何か? Kotlinにおける「中断(suspend)」というのは、かなり乱暴な表現をすると、ある処理を実行しているスレッドが、通信処理など「待ち」となるような処理を開始した後で、実際に待つことをせず、待っている間に別の処理を実行する、ということを指します。待ちが終わると、同じ or 別のスレッドが続きの処理を再開します。待ちの間にボケーっと待っているわけではなく、他の仕事をやるということですね。いわゆるノンブロッキングというやつです。 逆に、「中断」しない場合というのは、待ちが終わるまでスレッドも待ち状態となり、待ちが終わったら同じスレッドが続きの処理を実行します。待ちの間にボケーっと待っているわけです。いわゆるブロッキングというやつです。 Spring MVCによるサーバサイド処理はこうなっている 公式サイトによると、以下の通り、Spring MVCではCoroutineがサポートされています。 Spring MVC および WebFlux アノテーション付き @Controller での中断機能のサポート では、具体的にはどのような仕組みになっているのでしょうか? Spring MVCにおける処理シーケンスは以下の通りです。 Spring側は、Webサーバからリクエストを受け付けると、コールバックを指定してMonoと呼ばれるものを生成します。Monoとは、Reactive Streamsという仕様に出てくる「Publisher」という役割を実装したものです。Publisher(Mono)の中では非同期に処理が実行され、Spring側はPublisherをsubscribeします。Publisherの処理が終わると、SubscriberであるSpring側に通知され、それを受けてSpring側は呼び出し元にレスポンスしていきます。 このコールバックの中でコルーチンが起動され、コルーチン内でControllerのsuspend functionが実行されます。コルーチン起動時にはDispatchers.Unconfinedが指定されていますので、Controllerのsuspend function内で起動する子コルーチンも、特段の指定がない限りは親コルーチンの設定であるDispatchers.Unconfinedを引き継ぎます。 Controllerのsuspend functionでは、並列処理を行うためにasyncを呼び出し、子コルーチンを起動することが多いと思います。async呼び出しについては、以下の理解が大事と思います。 asyncは定義をみると分かるように、CoroutinScopeの拡張関数です。コルーチンの処理ブロックにはレシーバ(this)としてCoroutineScopeオブジェクトが設定されていますので、実際にはthis.async()と呼び出せます。thisは省略できるので、async()と直接呼び出せるのです。 先述のとおり、asyncに特段の指定がない限り、子コルーチンは親コルーチンの設定であるDispatchers.Unconfinedを引き継ぎます。 Dispachers.DefaultやDispatchers.IOを指定するといった選択肢もあり、それぞれ高CPU負荷の処理やIO待ちの発生する処理に適していると一般的には言われます。しかし、実際には無闇に指定すべきではありません。それらに最適なスレッド数には限りがあり、それらが枯渇した時点でブロッキングが発生してしまうためです。詳しくはこちらを参照ください。時期尚早な最適化はダメだよ、ということですね。 asyncで起動した子コルーチンでは、親コルーチンを処理するスレッドがsuspendCoroutine or suspendCancellableCoroutineの呼び出しまでを実行し、続きの処理を別スレッドに任せます。自スレッドはasyncの戻り値であるDeferredを受け取り、async呼び出しの次の処理を実行していきます。DeferredはJobのサブクラスですから、つまるところJobの一種です。Deferredを操作することで、処理結果を受け取ったり、asyncで起動するコルーチンをキャンセルすっることができます。 このようにControllerのスレッドはどんどんasyncでコルーチンを起動していき、どこかでそれらが終わるのをDeferred.await()で待ち、それらの処理結果を取得します。 Controllerの処理が完了すると、Monoを介してSpring側に通知がいきます。先述した通り、通知を受けたSpring側は、呼び出し元にレスポンスしていきます(画面ならThymeleafなどに処理が移ります)。 何が嬉しいのか? Spring MVCの場合、以下のメリットがあります。 Spring側では、Contollerからの応答を待つためにスレッドがブロックされる、といったことは起こりません。Spring側はController側からのコールバックによる通知を受けてから、レスポンス処理にスレッドを回せば良いのです。Spring MVCとはいえ、Spring側からContollerを呼び出す部分だけはリアクティブな動きをするということです。 Controller以降でasyncを呼び出す際は、親コルーチンを処理するスレッドがsuspendCoroutine or suspendCancellableCoroutineの呼び出しまでを実行し、続きの処理を別スレッドに任せるわけですから、Controllerを処理するメインともいえるスレッドはブロックされることなく、async以降の処理を継続できます。どんどんasyncで別コルーチンを起動していくことで、それらをシーケンシャルに呼び出すよりも明らかに性能が向上します。 このような並列処理は、もちろんスレッドを多数起動することでも実現できますが、スレッドは1本あたり約2MBのメモリを消費するため、メモリ消費量が大変なことになります。一方、コルーチンでは各スレッドが上記のような「中断」を繰り返しながら細切れに処理をしていくため、少ないスレッドで同じことを実現できます。 また、スレッドを多数起動した場合と比較したもう1つのメリットは、DBアクセスや通信処理などで「待ち」が発生する場合に、スレッドがブロックされず別の処理をできるため、CPUを余すところなく有効に活用できる、ということです。 おわりに 新たに分かったことがあれば追記していこうと思います。できれば図でのご説明も追加したいところですね・・。 間違っているところがあれば、是非是非ご指摘いただきたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipse+Forgeでマイクラのmod開発~デバッグ環境構築まで

デバッグができるところまでできたのでいったんまとめ。今後も何かあれば追記予定。あ、ちなみに決まり文句ですが、modの導入その他ここに書かれていることは自己責任でお願いしますね。 環境 OS: Windows10 Jdk: openjdk version "15.0.2" 2021-01-19 ※16.xでもいいかもだけどgradleやらなにやら色々変わってしまって心配だったので今回は15で。 IDE: Eclipse IDE for Java Developers (Version 4.19.0) Buildship(Eclipseのgradleプラグイン): 3.0 Minecraft: Java版 1.16.5 Forge: 1.16.5 ※Java,Eclipseやプラグイン関連のセットアップはこの記事の対象外なので別途調べてくださいませ ダウンロードからEclipseへのインポートまで Minecraftのダウンロード 公式サイトからJava版を購入してくださいませ。 https://www.minecraft.net/ja-jp/ 必ずJava版を購入してください。それ以外ではmodは使えないので。。間違えて買うと後悔します。 forgeのインストール こちらも公式サイトから。 https://files.minecraftforge.net/net/minecraftforge/forge/ バージョンを選んで「Installer」を選んでダウンロード。今回は1.16.5。「Latest」を選ぶこともできるけど、Mdkは「Latest」を選んだことで色々エラーを吐きまくって困ったので「Recommended」を選ぶのが無難。変なサイトが開くけどこれは広告で、しばらく待つと矢印のところに「Skip」というボタンが現れるのでそこからダウンロードできます。 マイクラを起動して「起動構成」を開く。 「forge」を選んで「プレイ」。 「Mods」トップ画面に表示されていればOK。 Mdk(Mode Development Kit)のダウンロード 先ほどと同じforgeの公式サイトから、同じバージョンのMdkをダウンロードする。 ダウンロードされたzipファイルを解凍すると、build.gradleやLICENSE.txtといったファイルが出入っているのがわかるはず。 MdkのEclipseへのインポート 回答されたMdkのフォルダをどこか適当なところにコピりる。コピった場所がmodの開発フォルダになるので、まぁ、Eclipseのワークスペースとして使っているフォルダ配下が無難だろうね。 好きなフォルダ名に変える。これがのちにEclipse上のプロジェクト名になります コピったフォルダにコマンドプロンプトで移動して、以下のコマンドでEclipse用の環境ファイルを吐き出させる。これが結構時間がかかった。。。(私の環境で初回だと10分ぐらい。。) C:\eclipse-workspace\munecraftmod>gradlew genEclipseRuns BUILD SUCCESSFUL in xxxxxが表示されればインポート準備完了。Eclipseにインポートします。メニューのImportから「Existing Gradle Project」でインポート。Buildshipが入っていないとこのメニューは出ないっす。(私の環境は日本語化していないのでこの記事もメニューの説明は英語。。) forge1.16.5はgradleが6.8.1より新しくないとビルドできません。ただ、gradle 7.0はJDK自体も16にしないとダメだったりするので、6.8.1とJDk15が無難かなぁ、と思ってその組み合わせにしました。 念のためRefresh Gradle Projectをしてインポートは完了。お疲れ様です。 環境整備 ここから、modのビルドやデバッグができるように環境を整備していきます。まずはサンプルのmodをそのままビルドしてマイクラに登録できるか確認してみよう。 まずはインポートしたプロジェクトのGradle Tasksからbuildを実行。 こちらもBUILD SUCCESSFUL in xxxxxが出れば成功。build\libs\modid-1.0.jarが生成されました。buildフォルダはデフォルトのフィルタで表示されなくなっているのでフィルタの設定を変えるかExplorer等で確認してね。 一度マイクラを起動します。gradleのrunClientタスクをたたくことで起動できます。 トップ画面で「Mods」を選んで表示された画面の左下の「Open mods folder」ボタンを押すとExplorerが起動。ここがbuildしたmodファイルを置く場所。毎回開くのは面倒なのでEclipseのプロジェクトの中にショートカットとかおいておくと楽。 開いたExplorerの場所にbuildの下にできてたbuild\libs\modid-1.0.jarをコピー。マイクラを一度終了してもっかいrunClientをするとmodを読み込んでマイクラが起動。EclipseのConsoleにログが出ますが、その中にサンプルのmodが出力したログを見つけられればおめでとうございます。無事にビルドしたサンプルのmodが読み込まれました。 下の例だと20:46:08にHELLO FROM PREINITのログが出力されてます。これって今回ビルドしたmodが出力してるものなので無事にロードされた模様。おめでとう! (省略) [20:46:08] [Thread-0/DEBUG] [ne.mi.co.ForgeConfig/FORGEMOD]: Forge config just got changed on the file system! [20:46:08] [Thread-0/DEBUG] [ne.mi.fm.co.ConfigFileTypeHandler/CONFIG]: Config file forge-common.toml changed, sending notifies [20:46:08] [Thread-0/DEBUG] [ne.mi.co.ForgeConfig/FORGEMOD]: Forge config just got changed on the file system! [20:46:08] [Worker-Main-5/INFO] [co.ex.ex.ExampleMod/]: HELLO FROM PREINIT</font> [20:46:08] [Worker-Main-5/INFO] [co.ex.ex.ExampleMod/]: DIRT BLOCK >> minecraft:dirt [20:46:08] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Starting version check at https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json [20:46:11] [Forge Version Check/DEBUG] [ne.mi.fm.VersionChecker/]: [forge] Received version check data: (省略) デバッグ環境を整える デバッグ環境を整えるために、gradleのタスク経由ではなくEclipseのRunからマイクラを起動できるようにする。FileメニューのImportを選び「Run/Debug」>「Launch Configurations」を選ぶ。 「Browse...」で作ったプロジェクトのルートフォルダを選択。出てきたリストにすべてチェックを入れてFinish。 Runメニューにある「Run Configurations...」を選ぶ。 Java Applicationのところに「runClient」ができているのでそれを選択してRun。無事にマイクラが起動すればOK。コンソールはまたログが出ているはず。 サンプルのmodにBreak Pointをつけて、上で作ったRun Configurationをデバッグモードで起動。ワクワク。。 っしゃあああぁぁああぁぁぁぁ。ちゃんとBreak Pointで止まったね。これでデバッグの準備も完了!!!(ここまでたどり着くのに相当苦労した。。。) リンク集 必要ソフトウェア modの導入、開発にほぼ必須となるForgeの公式サイト リファレンス Mod開発のイロハをよくまとめてくださっているサイト。かなり参考にさせてもらってます。 こちらも少し古いですがmod開発関連の記事を掲載されていて参考にしています。 Forgeの導入方法やデバッグ方法が書いてあるのでいずれ参照。情報がだいぶ古そうなので、 今の環境での内容を整理していずれ記事にしたい。。 英語だけど環境構築の方法が書かれているブログ。最初からこれが見つけられれば早かったかも。。 英語で読める人はこちらもどうぞ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Eclipse+Forgeでマイクラのmod開発

デバッグができるところまでできたのでいったんまとめ。今後も何かあれば追記予定。あ、ちなみに決まり文句ですが、modの導入その他ここに書かれていることは自己責任でお願いしますね。 環境 OS: Windows10 Jdk: openjdk version "15.0.2" 2021-01-19 ※16.xでもいいかもだけどgradleやらなにやら色々変わってしまって心配だったので今回は15で。 IDE: Eclipse IDE for Java Developers (Version 4.19.0) Buildship(Eclipseのgradleプラグイン): 3.0 Minecraft: Java版 1.16.5 Forge: 1.16.5 ※Java,Eclipseやプラグイン関連のセットアップはこの記事の対象外なので別途調べてくださいませ ダウンロードからEclipseへのインポートまで Minecraftのダウンロード 公式サイトからJava版を購入してくださいませ。 https://www.minecraft.net/ja-jp/ 必ずJava版を購入してください。それ以外ではmodは使えないので。。間違えて買うと後悔します。 forgeのインストール こちらも公式サイトから。 https://files.minecraftforge.net/net/minecraftforge/forge/ バージョンを選んで「Installer」を選んでダウンロード。今回は1.16.5。「Latest」を選ぶこともできるけど、Mdkは「Latest」を選んだことで色々エラーを吐きまくって困ったので「Recommended」を選ぶのが無難。変なサイトが開くけどこれは広告で、しばらく待つと矢印のところに「Skip」というボタンが現れるのでそこからダウンロードできます。 マイクラを起動して「起動構成」を開く。 「forge」を選んで「プレイ」。 「Mods」トップ画面に表示されていればOK。 Mdk(Mode Development Kit)のダウンロード 先ほどと同じforgeの公式サイトから、同じバージョンのMdkをダウンロードする。 ダウンロードされたzipファイルを解凍すると、build.gradleやLICENSE.txtといったファイルが出入っているのがわかるはず。 MdkのEclipseへのインポート 回答されたMdkのフォルダをどこか適当なところにコピりる。コピった場所がmodの開発フォルダになるので、まぁ、Eclipseのワークスペースとして使っているフォルダ配下が無難だろうね。 好きなフォルダ名に変える。これがのちにEclipse上のプロジェクト名になります コピったフォルダにコマンドプロンプトで移動して、以下のコマンドでEclipse用の環境ファイルを吐き出させる。これが結構時間がかかった。。。(私の環境で初回だと10分ぐらい。。) C:\eclipse-workspace\munecraftmod>gradlew genEclipseRuns BUILD SUCCESSFUL in xxxxxが表示されればインポート準備完了。Eclipseにインポートします。メニューのImportから「Existing Gradle Project」でインポート。Buildshipが入っていないとこのメニューは出ないっす。(私の環境は日本語化していないのでこの記事もメニューの説明は英語。。) forge1.16.5はgradleが6.8.1より新しくないとビルドできません。ただ、gradle 7.0はJDK自体も16にしないとダメだったりするので、6.8.1とJDk15が無難かなぁ、と思ってその組み合わせにしました。 念のためRefresh Gradle Projectをしてインポートは完了。お疲れ様です。 環境整備 ここから、modのビルドやデバッグができるように環境を整備していきます。まずはサンプルのmodをそのままビルドしてマイクラに登録できるか確認してみよう。 まずはインポートしたプロジェクトのGradle Tasksからbuildを実行。 こちらもBUILD SUCCESSFUL in xxxxxが出れば成功。build\libs\modid-1.0.jarが生成されました。buildフォルダはデフォルトのフィルタで表示されなくなっているのでフィルタの設定を変えるかExplorer等で確認してね。 一度マイクラを起動します。gradleのrunClientタスクをたたくことで起動できます。 トップ画面で「Mods」を選んで表示された画面の左下の「Open mods folder」ボタンを押すとExplorerが起動。ここがbuildしたmodファイルを置く場所。毎回開くのは面倒なのでEclipseのプロジェクトの中にショートカットとかおいておくと楽。 開いたExplorerの場所にbuildの下にできてたbuild\libs\modid-1.0.jarをコピー。マイクラを一度終了してもっかいrunClientをするとmodを読み込んでマイクラが起動。EclipseのConsoleにログが出ますが、その中にサンプルのmodが出力したログを見つけられればおめでとうございます。無事にビルドしたサンプルのmodが読み込まれました。 下の例だと20:46:08にHELLO FROM PREINITのログが出力されてます。これって今回ビルドしたmodが出力してるものなので無事にロードされた模様。おめでとう! (省略) [20:46:08] [Thread-0/DEBUG] [ne.mi.co.ForgeConfig/FORGEMOD]: Forge config just got changed on the file system! [20:46:08] [Thread-0/DEBUG] [ne.mi.fm.co.ConfigFileTypeHandler/CONFIG]: Config file forge-common.toml changed, sending notifies [20:46:08] [Thread-0/DEBUG] [ne.mi.co.ForgeConfig/FORGEMOD]: Forge config just got changed on the file system! [20:46:08] [Worker-Main-5/INFO] [co.ex.ex.ExampleMod/]: HELLO FROM PREINIT</font> [20:46:08] [Worker-Main-5/INFO] [co.ex.ex.ExampleMod/]: DIRT BLOCK >> minecraft:dirt [20:46:08] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Starting version check at https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json [20:46:11] [Forge Version Check/DEBUG] [ne.mi.fm.VersionChecker/]: [forge] Received version check data: (省略) デバッグ環境を整える デバッグ環境を整えるために、gradleのタスク経由ではなくEclipseのRunからマイクラを起動できるようにする。FileメニューのImportを選び「Run/Debug」>「Launch Configurations」を選ぶ。 「Browse...」で作ったプロジェクトのルートフォルダを選択。出てきたリストにすべてチェックを入れてFinish。 Runメニューにある「Run Configurations...」を選ぶ。 Java Applicationのところに「runClient」ができているのでそれを選択してRun。無事にマイクラが起動すればOK。コンソールはまたログが出ているはず。 サンプルのmodにBreak Pointをつけて、上で作ったRun Configurationをデバッグモードで起動。ワクワク。。 っしゃあああぁぁああぁぁぁぁ。ちゃんとBreak Pointで止まったね。これでデバッグの準備も完了!!!(ここまでたどり着くのに相当苦労した。。。) リンク集 必要ソフトウェア modの導入、開発にほぼ必須となるForgeの公式サイト リファレンス Mod開発のイロハをよくまとめてくださっているサイト。かなり参考にさせてもらってます。 こちらも少し古いですがmod開発関連の記事を掲載されていて参考にしています。 Forgeの導入方法やデバッグ方法が書いてあるのでいずれ参照。情報がだいぶ古そうなので、 今の環境での内容を整理していずれ記事にしたい。。 英語だけど環境構築の方法が書かれているブログ。最初からこれが見つけられれば早かったかも。。 英語で読める人はこちらもどうぞ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む