- 投稿日:2020-09-19T21:18:43+09:00
EclipseでAmaterasUMLを使う
はじめに
Eclipsを久しぶりに使う際に,以前UML図を自動で生成してくれるAmaterasUMLをどうやってインストールするのか忘れてしまい,いろいろし調べたのですが,少しハマったポイントがありましたので,そちらを残して置きたいと思います.
Eclipse側の設定
- 上部のバーの
ヘルプ -> 新規ソフトウェアのインストール
- 作業対象に
http://download.eclipse.org/releases/luna
を打ち込み,フィルターにGEF
を指定グラフィカル編集フレームワーク GEF SDK
を選択し,インストール以降手順に従ってインストールを完了させて,再起動してください.
AmaterasUMLのインストール
公式ホームページからAmaterasUML_1.3.4.zipをダウンロードし,解凍します.そうすると,以下の3つのjarファイルが入っています.
net.java.amateras.umleditor.java_1.3.4.jar net.java.amateras.umleditor_1.3.4.jar net.java.amateras.xstream_1.3.4.jarこれらを
/Applications/Eclipse_{version}.app/Contents/Eclipse/dropins/AmaterasModeler/eclipse/plugins
へコピーします.
ただ,私の場合こちらのディレクトリには以下のファイルが予め含まれていました.net.java.amateras.db_1.0.9.jar net.java.amateras.umleditor.java_1.3.5.jar net.java.amateras.umleditor_1.3.5.jar net.java.amateras.xstream_1.3.4.jar
umleditor
に関するファイルが同じであることと,バージョンが上位であることから,もともとAmaterasUMLは使えるのではと思い,コピーする前にUML図を作成しようと試みてみました.
しかし,cldファイルの作成はできましたがjavaファイルのD&Dでの反映がうまくいきませんでした.そのため,
umleditor
に関するファイルのみ上書きしました.(一応バックアップはしたほうがいいのかなと思ってしてます.この違いを知っている方がおられましたら教えていただきたいです)
そうすると,無事にcldファイルの作成をjavaファイルのD&Dで作成することができました.UML図の作成方法
UML図の作成する場合は,作成したいディレクトリやパッケージなどの中で
新規 -> その他 -> AmaterasUML -> クラス図
でcldファイルの作成した後,任意のjavaファイルをcldファイルのウィンドウへD&Dするとクラス図が自動で生成されます.
複数のjavaファイルを選択してD&Dすれば委譲関係などもわかるようになっています.便利ですね.さいごに
いくつかのサイトで書かれていた
/Applications/Eclipse_{version}.app/Contents/Eclipse/dropins
内へ解凍したjarファイルを入れるという点で少し詰まってしまいました.
いつからAmaterasModelerディレクトリ
があったのか確認していなかったため,最新版のEclipseをインストールした方にはあるかもしれません.その場合,AmaterasModelerディレクトリ
内にもともとあるjarファイルと置き換えないとUML図が正しく生成できなかったため,注意が必要かもしれません.
久しくUML図を書いていないため,AmaterasUMLを参考に復習したいと思っています.
- 投稿日:2020-09-19T20:32:27+09:00
JPAで複数のOneToManyでjoin fetchするとMultipleBagFetchException
JPAで
@OneToMany
を複数定義して両者をjoin fetch
で取得しようとすると、下記のような実行時例外になることがある。Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [jpa.sample2.Tweet.reTweets, jpa.sample2.Tweet.favorites] at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:76) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final] at org.hibernate.loader.hql.QueryLoader.<init>(QueryLoader.java:110) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]状況
説明用のスキーマとして、一つのtweetに複数のReTweetと複数のFavorite、というテーブルを想定する。javaのentityとしては以下のような感じ。
@NoArgsConstructor @AllArgsConstructor @Entity @Data public class Tweet { @Id Long id; String value; @OneToMany @JoinColumn(name = "tweetId") List<ReTweet> reTweets; @OneToMany @JoinColumn(name = "tweetId") List<Favorite> favorites; }@NoArgsConstructor @AllArgsConstructor @Entity @Data public class ReTweet { @Id Long id; Long tweetId; String value; }@NoArgsConstructor @AllArgsConstructor @Entity @Data public class Favorite { @Id Long id; Long tweetId; String value; }JPQLは以下のように
join fetch
を2回使用する。@Repository public interface TweetRepository extends JpaRepository<Tweet, Long> { @Query("select distinct t from Tweet t " + " left join fetch t.reTweets " + " left join fetch t.favorites" + " order by t.id") List<Tweet> list2(); }こうすると上記の実行時例外となる。
解決策
このケースの場合は
List
をSet
に変えればよい。@OneToMany @JoinColumn(name = "tweetId") Set<ReTweet> reTweets; @OneToMany @JoinColumn(name = "tweetId") Set<Favorite> favorites;または
Map
でも良い。@OneToMany @JoinColumn(name = "tweetId") Map<Long, ReTweet> reTweets; @OneToMany @JoinColumn(name = "tweetId") Map<Long, Favorite> favorites;もしくは、単一のJPQLを諦めて複数に分割する、というやり方も考えられる。
@Query("select distinct t from Tweet t " + " left join fetch t.reTweets " + " order by t.id") List<Tweet> list3(); @Query("select distinct t from Tweet t " + " left join fetch t.favorites" + " order by t.id") List<Tweet> list4();
- 投稿日:2020-09-19T18:11:20+09:00
try-catch-finally 例外処理 使い方 java
環境:Windows10, Eclipse, java8
javaにおけるtry catch finallyを使った例外処理について記述します。
例外処理の書き方
try{ 処理 }catch(例外の型 引数){ 例外発生時の処理 }finally{ 最後に実行される処理(例外の有無関係なし) }使用例
public static void main(String[] args) { double a = 99999; //----------例外処理が発生するケース---------- System.out.println("-----例外発生-----"); try { a = 30/0; }catch(ArithmeticException e) { System.out.println("例外処理:0では割れません"); System.out.println(e); }finally { System.out.println("finally "+ "a="+a); } //----------例外処理が発生しないケース---------- System.out.println("-----正常-----"); try { a = 30/3; }catch(ArithmeticException e) { System.out.println("例外処理:0では割れません"); System.out.println(e); }finally { System.out.println("finally "+ "a="+a); } }実行結果
-----例外発生----- 例外処理:0では割れません java.lang.ArithmeticException: / by zero finally a=99999.0 -----正常----- finally a=10.0解説
以上のプログラムでは、a=30/0;の行にて、
ゼロで除算をしているため(解なしになってしまう)、例外(ArithmeticException)が発生しています。
そのため、以下の処理が実行されます。}catch(ArithmeticException e) { System.out.println("例外処理:0では割れません"); }そのため、実行結果として、「例外処理:0では割れません」と表示されました。
catch(例外の型 引数)について
catchの中身はどのように書けばよいのか。
上記での例
}catch(ArithmeticException e){上記の例での、ArithmeticExceptionというのは、ゼロで除算すると投げられてくる例外です。
他の例外も、以下の形で書けますので、解説していきます。
}catch(例外の型 引数){例外の型(例:ArithmeticException)
例外が発生した際に投げられてくる型を書く。
try内に書く処理に対応した例外処理を、リファレンスで調べて使う。
java11の場合:https://docs.oracle.com/javase/jp/11/docs/api/index-files/index-1.htmlimportを伴う場合
また、ファイル入出力操作等で例外処理をしたい場合に、
IOExceptionという例外を使う場合はimport java.io.IOException;でパッケージをインポートする必要があることもあります。
今回のArithmeticExceptionという例外についても、
import java.lang.ArithmeticExceptionと記述してimportの必要がありそうです。
しかし、java.langはコンパイラ内で暗黙的にimportされるため、記述の必要はありません。
ちなみにSystem.out.printlnがimportなしで使えるのもこのためです。引数(例:e)
命名は自由だが。「e」が使われることが多い。
今回の例では
「System.out.println(e);」を記述しているため、
実行結果に「java.lang.ArithmeticException: / by zero」
と表示され、例外を受け取っていることが確認できる。
- 投稿日:2020-09-19T17:29:25+09:00
Spring Data JPAの非named queryなのでNo property list found for type
以下のように、named queryではないのにうっかり
name
にJPQLを指定しまうと、一見不可解な実行時エラーが発生する。@Repository public interface SampleRepository extends JpaRepository<Sample, Long> { @Query(name = "select s from Sample s") List<Sample> list(); }Caused by: org.springframework.data.mapping.PropertyReferenceException: No property list found for type Sample! at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:94) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE] at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:382) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE] at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:358) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE]落ち着いてみれば、単にそんなnameのnamed queryは無いですよ、というエラーなのだが、正しくJPQLを指定してるつもりなのに実行時エラーになって焦ってしまった……のでメモを残しておく。
正しくは
value
にJPQLを指定すればよい。@Query("select s from Sample s") List<Sample> list();
- 投稿日:2020-09-19T16:56:48+09:00
【Java】【Kotlin】Enum の valueOf と values をジェネリックに呼び出す
Enum 派生クラスに暗黙的に宣言されるメソッド/メンバ関数である
valueOf
およびvalues
を
ジェネリックに呼び出す方法。Java
次のような enum 型が定義されているとする。
public enum PrimaryColor { RED, GREEN, BLUE }具象型が分かっているときの呼び出し
public static void main(String... args) { PrimaryColor red = PrimaryColor.valueOf("RED"); // RED PrimaryColor[] values = PrimaryColor.values(); // [RED, GREEN, BLUE] }ジェネリックな呼び出し
public static <E extends Enum<E>> E valueOf(Class<E> enumType, String name) { return Enum.valueOf(enumType, name); } public static <E extends Enum<E>> E[] values(Class<E> enumType) { return enumType.getEnumConstants(); } public static void main(String... args) { PrimaryColor red = valueOf(PrimaryColor.class, "RED"); // RED PrimaryColor[] values = values(PrimaryColor.class); // [RED, GREEN, BLUE] }Kotlin
次のような enum 型が定義されているとする。
enum class PrimaryColor { RED, GREEN, BLUE }具象型が分かっているときの呼び出し
fun main() { val red: PrimaryColor = PrimaryColor.valueOf("RED") // RED val values: Array<PrimaryColor> = PrimaryColor.values() // [RED, GREEN, BLUE] }ジェネリックな呼び出し
inline fun <reified E : Enum<E>> valueOf(name: String): E = enumValueOf<E>(name) // `<E>` は今回の場合は省略(型推論)可能 inline fun <reified E : Enum<E>> values(): Array<E> = enumValues<E>() // `<E>` は今回の場合は省略(型推論)可能 fun main() { val red: PrimaryColor = valueOf("RED") val values: Array<PrimaryColor> = values() }/以上
- 投稿日:2020-09-19T16:55:39+09:00
キャストを含むStreamと拡張for文の処理速度の比較
はじめに
配列やコレクションに含まれる特定の型の要素に対して操作を行うにあたり、Streamと拡張for文のどちらが早いのか、処理速度を検証しました。
事前に用意するコード
要素数1億の配列Object[] objects = new Object[100_000_000]; for (int i = 0; i < 50_000_000; i++) objects[i] = new Object(); for (int i = 50_000_000; i < 100_000_000; i++) objects[i] = "";要素数1億のArrayListList<Object> objects = new ArrayList<>(); for (int i = 0; i < 50_000_000; i++) objects.add(new Object()); for (int i = 0; i < 50_000_000; i++) objects.add("");今回は配列とArrayListを使って検証します。
Object型、String型の要素をそれぞれ5千万個ずつ格納しています。検証
Streamlong start = System.currentTimeMillis(); objects.stream() // Arrays.stream(objects) .filter(String.class::isInstance) .map(String.class::cast) .forEach(String::toUpperCase); long finish = System.currentTimeMillis(); System.out.println(finish - start + "ms");
種類 時間 配列 345ms ArrayList 409ms 配列の方がやや早いようです。
拡張for文long start = System.currentTimeMillis(); for (Object obj : objects) { if (obj instanceof String) { ((String) obj).toUpperCase(); } } long finish = System.currentTimeMillis(); System.out.println(finish - start + "ms");
種類 時間 配列 122ms ArrayList 212ms 今回の比較では、Streamよりも拡張for文の方が早いということがわかりました1。
普段百万、千万単位の要素数を扱うコードを組まないのであまり意識せずストリームを使うことが多いのですが、パフォーマンスを重視する上では、ストリームは注意して用いる必要がありそうです。
計測方法に不備などございましたら、恐れ入りますが教えて頂けますと幸いです。 ↩
- 投稿日:2020-09-19T14:35:09+09:00
Kotlinを使いこなす 〜Convert Java File to Kotlin File 卒業への道〜 その3
はじめに
Android等でKotlinを使用している場合、Javaから『Convert Java File to Kotlin File』で自動変換している人は多い。実際にそれでも動くのだが、そこをもう1歩先に進みKotlinをより使いこなす為のメモ。
第3回目
今回はListとfor文の処理を習得する。
Kotlinバージョン:1.4Kotlin変換前のサンプルコード
あるオブジェクトからListを生成する処理があるとした例。
Listの作成にはいくつか条件がある。ItemData.java// Stringが7個あるオブジェクト public class ItemData { private String item1; private String item2; private String item3; private String item4; private String item5; private String item6; private String item7; public ItemData2(String item1, String item2, String item3, String item4, String item5, String item6, String item7) { this.item1 = item1; this.item2 = item2; this.item3 = item3; this.item4 = item4; this.item5 = item5; this.item6 = item6; this.item7 = item7; } public String getItem1() { return item1; } public String getItem2() { return item2; } public String getItem3() { return item3; } public String getItem4() { return item4; } public String getItem5() { return item5; } public String getItem6() { return item6; } public String getItem7() { return item7; } }ListUtil.java// オブジェクトからListを作成するUtil public final class ListUtil { private ListUtil() { }; private static final int MAX_SIZE = 4; // オブジェクトのitem1、item2、item3、item4、item5でリストを作る // 但し、nullだったらListに追加しない public static List<String> createItemList(final ItemData itemData) { if (itemData == null) { return new ArrayList<>(); } final List<String> list = new ArrayList<>(); for (int i = 0; i <= MAX_SIZE; i++) { switch (i) { case 0: if (itemData.item1 != null) { list.add(itemData.item1); } break; case 1: if (itemData.item2 != null) { list.add(itemData.item2); } break; case 2: if (itemData.item3 != null) { list.add(itemData.item3); } break; case 3: if (itemData.item4 != null) { list.add(itemData.item4); } break; case 4: if (itemData.item5 != null) { list.add(itemData.item5); } break; default: } } return list; } }Kotlin自動変換後のサンプルコード
これを自動変換したらこんな感じに・・・。
とりあえずこれでも動きはする。ItemData.kt// Stringが7個あるオブジェクト class ItemData( val item1: String, val item2: String, val item3: String, val item4: String, val item5: String, val item6: String, val item7: String )ListUtil.kt// オブジェクトからListを作成するUtil object ListUtil { private const val MAX_SIZE = 4 // オブジェクトのitem1、item2、item3、item4、item5でリストを作る // 但し、nullだったらListに追加しない fun createItemList(itemData: ItemData?): List<String?> { if (itemData == null) { return ArrayList() } val list: MutableList<String?> = ArrayList() for (i in 0 until MAX_SIZE) { when (i) { 0 -> if (itemData.item1 != null) { list.add(itemData.item1) } 1 -> if (itemData.item2 != null) { list.add(itemData.item2) } 2 -> if (itemData.item3 != null) { list.add(itemData.item3) } 3 -> if (itemData.item4 != null) { list.add(itemData.item4) } 4 -> if (itemData.item5 != null) { list.add(itemData.item5) } else -> { } } } return list } }実装方法を考える
せっかくKotlinで実装しているのでもっとシンプルにしたい。
早速、修正してみるItemData.kt// dataを付けてデータクラスと明示する data class ItemData( val item1: String, val item2: String, val item3: String, val item4: String, val item5: String, val item6: String, val item7: String )ListUtil.kt// オブジェクトからListを作成するUtil object ListUtil { private const val MAX_SIZE = 4 // オブジェクトのitem1、item2、item3、item4、item5でリストを作る // 但し、nullだったらListに追加しない // 戻り値はList<String?>→List<String>が良い fun createItemList(itemData: ItemData?): List<String> { return itemData?.let { // nullの場合はここに入らない val list: MutableList<String?> = ArrayList() // for (i in 0..MAX_SIZE) という記述も可能 for (i in 0 until MAX_SIZE) { when (i) { 0 -> list.add(it.item1) 1 -> list.add(it.item2) 2 -> list.add(it.item3) 3 -> list.add(it.item4) 4 -> list.add(it.item5) else -> { } } } // ここでreturnされる list.filterNotNull() // これでListのnullが消えStringがNonNullになる // add時にnullチェックしなくて済む } ?: emptyList() // null時は空Listを返す // 空ListはArrayList()でなくemptyList()を返す } }とてもシンプル!
- 投稿日:2020-09-19T07:21:46+09:00
vue-wordcloudでツイートを検索して紐づくプロフィールに何が書いてあるかの傾向を調べるWebアプリを作ってみた
1.っていうよく見るアレです。
アレってなんだよっていう方のために結果を先にお見せします。
ツイートを検索ワードで検索すると、ヒットしたツイートしてる人のプロフィールの単語をWord Cloudのタグクラウドで表示します。
出現回数が多い文字が大きくなります。
作り方は簡単みたいですが、調べるとだいたいPythonです。
今、Vue.jsとJava(Spring Boot)で開発してるので、それで作れないかな~と思ってちょっとWebアプリを作ってみました。
今回はツイートの内容じゃなくて、ツイートの内容からプロフィールの内容をひっぱってきてるのでそこが少しトリッキーかも。
Webアプリ作成日数:2日
Qiita記事作成:2日Qiita記事作成のほうがしんどい。
何でこんなに長いタイトルになってしまったのか。
Webアプリの動作の流れはこんな感じです。
TwitterのAPIでツイートのプロフィール情報取得
↓
プロフィール情報の形態素解析
↓
可視化
形態素解析とは?文章を単語に分割する作業って感じですが私はそれ以上のことはわからないのでみなさんの方がきっと詳しくなります。
2.環境
Vue.js:2.6.12
Spring Boot:v2.3.3
Java:AdoptOpenJDK:11.0.6
Kuromoji:0.9.0
vue-wordcloud:1.1.1
axios:0.20.0性懲りもなくVue.jsをフロント、Spring BootをサーバサイドのAPIとします。
大まかなディレクトリ構造はこんな感じです。
┠ src ┃ ┗ main ┃ ┗ java/app/myapp ┃ ┠ common ┃ ┃ ┗ HttpAccess.java ┃ ┠ controller ┃ ┃ ┗ TwitterAnalysisRestController.java ┃ ┠ entity ┃ ┃ ┗ VueWordCloudEntity.java ┃ ┠ service ┃ ┃ ┗ TwitterAnalysisService.java ┃ 色々 ┃ ┠ web ┃ ┠ 色々 ┃ ┠ src ┃ ┃ ┠ 色々 ┃ ┃ ┠ router ┃ ┃ ┃ ┗ index.js ┃ ┃ ┠ views ┃ ┃ ┃ ┗ TwitterAnalysis.vue ┃ ┃ ┃ ┃ ┃ 色々 ┃ 色々 ┃ ┠ build.gradle 色々3.手順
3-1.前提
Spring Boot のプロジェクトが作成済。
3-2.TwitterのAPI利用申請
まず、Twitter側にTwitterのAPI利用申請をする必要があります。
Twitterのアカウントを持ってる前提です。
親切に解説してくれてるサイトがあるので、利用申請はこちらを参考にしてください。
2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説情報は日々変わるのですでに変わってるところがあるかもしれませんが、多分もっと簡単になってるはずです。
やることは、Twitter側からのいくつかの質問に答えるだけです。
どうみても英語で書くことを強いられる申請画面なのですが、実は日本語でもよかったりして…
質問に答えたら承認待ちかな…と思ったらそんなことはなく、すぐにトークン(Bearer Token)の表示画面に行けました。
このトークンとあとキーをコピーしておけばOKです。
ちょっと前は承認待ちに数日かかっていたようです。
3-3.Javaの実装
まずはTwitterのAPIを利用してプロフィール情報を取得します。
Twitter4Jというライブラリを使えば便利なようですが、今回は使ってません。
理由は特にありません。
APIのURLはこんな感じです。
API、最近新しくなったみたいですね。
https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100&query=<検索キーワード>
今回使うパラメータ 内容 expansions=author_id ツイートの文章と同時に、そのツイートをした人の情報を取得するために指定 user.fields=description 取得する情報に、ツイートをした人のプロフィールの文章を含めるために指定 max_results=100 最大取得件数 query=<検索ワード> ツイートに対する検索ワード 詳しくは公式サイト
で、APIを利用して情報を取得するのはこんな感じ。
トークンには上記で取得したBearer Tokenを指定します。
TwitterAnalysisService.javaString urlString = "https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100"; String method = "GET"; String bearerToken = <トークン>; HttpAccess httpAccess = new HttpAccess(); String response = httpAccess.requestHttp(urlString, method, bearerToken, <検索ワード>);HttpAccess.javaimport java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; public class HttpAccess { public String requestHttp(String urlString, String method, String bearerToken, String query) { String result = null; String urlStringFull = null; if (query == null || query.isEmpty()) return result; try { urlStringFull = urlString + "&query=" + URLEncoder.encode(query, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (urlStringFull == null) return result; HttpURLConnection urlConn = null; InputStream in = null; BufferedReader reader = null; try { URL url = new URL(urlStringFull); urlConn = (HttpURLConnection) url.openConnection(); urlConn.setRequestMethod(method); urlConn.setRequestProperty("Authorization","Bearer " + bearerToken); urlConn.connect(); int status = urlConn.getResponseCode(); if (status == HttpURLConnection.HTTP_OK) { in = urlConn.getInputStream(); reader = new BufferedReader(new InputStreamReader(in)); StringBuilder output = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { output.append(line); } result = output.toString(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (reader != null) { reader.close(); } if (urlConn != null) { urlConn.disconnect(); } } catch (IOException e) { e.printStackTrace(); } } return result; } }次に形態素解析ですが、Javaでやりたい場合はKuromojiが便利です。
build.gradle ファイルに一行追加するだけです。
build.gradledependencies { ~略~ implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0' ~略~ }あと、データをどういう形式で返すかというと、ビューの方でvue-wordcloudが「name」と「value」をキーに持つオブジェクトの配列をデータとして扱うので、「name」と「value」をキーに持つオブジェクトの配列をJSONで返します。
※キーの名称は変更可能「name」には形態素解析した結果の単語、「value」にはその単語が何回出現したかを入れます。
今回、名詞のみを対象にするとして、上記のAPIへのアクセスに加えて形態素解析を行い、「name」と「value」をフィールドに持つオブジェクトを作成したのが以下の通りです。
形態素解析にはTokenizerクラスを使います。
TwitterAnalysisService.javaimport java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import com.atilika.kuromoji.ipadic.Token; import com.atilika.kuromoji.ipadic.Tokenizer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import app.myapp.common.HttpAccess; import app.myapp.entity.VueWordCloudEntity; @Service public class TwitterAnalysisService { public List<VueWordCloudEntity> analysisTwitterProfileByQuery(String query) { List<VueWordCloudEntity> result = new ArrayList<>(); String urlString = "https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100"; String method = "GET"; String bearerToken = <トークン>; HttpAccess httpAccess = new HttpAccess(); String response = httpAccess.requestHttp(urlString, method, bearerToken, query); if (response == null) return result; ObjectMapper mapper = new ObjectMapper(); JsonNode root = null; try { root = mapper.readTree(response); } catch (JsonProcessingException e) { e.printStackTrace(); } if (root == null || root.get("meta").get("result_count").asInt() == 0) { return result; } Tokenizer tokenizer = new Tokenizer(); List<Token> tokens = new ArrayList<>(); JsonNode users = root.get("includes").get("users"); for(int i = 0; i < users.size(); i++) { if (users.get(i).get("description") != null) { tokens.addAll(tokenizer.tokenize(users.get(i).get("description").asText())); } } List<VueWordCloudEntity> vueWordCloudEntityList = new ArrayList<>(); tokens.stream() .filter(x -> x.getPartOfSpeechLevel1().equals("名詞")) .map(x -> x.getSurface()) .collect(Collectors.groupingBy(x -> x, Collectors.counting())) .forEach((k, v) -> { VueWordCloudEntity vueWordCloudEntity = new VueWordCloudEntity(); vueWordCloudEntity.setName(k); vueWordCloudEntity.setValue(v); vueWordCloudEntityList.add(vueWordCloudEntity); }); result = vueWordCloudEntityList; return result; } }VueWordCloudEntity.javaimport lombok.Data; @Data public class VueWordCloudEntity { private String name; private Long value; }TwitterAnalysisRestController.javaimport java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; import app.myapp.entity.VueWordCloudEntity; import app.myapp.service.TwitterAnalysisService; @RestController @RequiredArgsConstructor @RequestMapping("/api") public class TwitterAnalysisRestController { @Autowired private TwitterAnalysisService twitterAnalysisService; @GetMapping("/twitter/analysis/profile/by/{query}") public ResponseEntity<List<VueWordCloudEntity>> analysisTwitterProfileByQuery( @PathVariable String query) { List<VueWordCloudEntity> result = twitterAnalysisService.analysisTwitterProfileByQuery(query); // テストアプリのためここでCORS対応 HttpHeaders headers = new HttpHeaders(); headers.add("Access-Control-Allow-Credentials", "true"); headers.add("Access-Control-Allow-Origin", "http://localhost:<Vue.js実行環境ポート番号>"); return new ResponseEntity<>(result, headers, HttpStatus.OK); } }きっとJavaでTwitter4Jを使わない場合のもっと簡単なやり方があるはず!
どこかにあるよきっとそんなサンプル。
今回は残念、私のサンプルでした。
※ソースコードは「動作する」優先です、という言い訳をしてみる…
3-4.Vue.jsの実装
こっちはvue-wordcloudをインストールしてvue-wordcloud公式のソースをほぼコピペするだけなので簡単です。
vue-cliのGUIでプロジェクトを作成して上記のディレクト構造の場所へ配置します。
その後、vue-cliのGUIでvue-wordcloudをインストールします。
あ、あとaxiosも。
あとは、公式のサンプルをコピペし、ちょこっと直してSpring BootのAPIからデータを取得します。
TwitterAnalysis.vue<template> <div id="twitter-analysis"> <input v-model="query" placeholder="キーワードを入力してください" style="width:400px;"> <button @click="analyzeProfile" style="margin-left:10px;">解析</button> <wordcloud :data="analyzedWords" nameKey="name" valueKey="value" color="Accent" :showTooltip="true" :wordClick="wordClickHandler"> </wordcloud> </div> </template> <script> import wordcloud from 'vue-wordcloud' import axios from 'axios' axios.defaults.withCredentials = true export default { name: 'TwitterAnalysis', components: { wordcloud }, data() { return { query: '', analyzedWords: [], } }, methods: { wordClickHandler(name, value, vm) { console.log('wordClickHandler', name, value, vm); }, analyzeProfile: async function () { if (this.query == null || this.query === '') return await axios.get('http://localhost:<Spring Boot実行環境ポート番号>/api/twitter/analysis/profile/by/' + encodeURIComponent(this.query)) .then(res => { if (res.data != null) { this.analyzedWords = res.data } }) .catch(err => { alert(err + ' エラーです。') }) }, }, } </script>router/index.jsimport Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', name: 'TwitterAnalysis', component: () => import(/* webpackChunkName: "twitteranalysis" */ '../views/TwitterAnalysis.vue') }, ] const router = new VueRouter({ mode: 'history', routes }) export default routerこれで
http://localhost:<Vue.js実行環境ポート番号>/
にアクセスします。検索ワードを入力して解析ボタンを押すと
成功!するはず。
見ると、スラッシュとかhttpとかどうでもいい単語があるので、こういうのはきっと除外するんだと思います。
このアプリで何か面白い傾向がわかるかな~と思ったんですが、あんまりわからない!
そもそも、そもそもな感じ。
色々勉強が必要みたいですねぇ…
- 投稿日:2020-09-19T03:14:09+09:00
Azure App Service (Windows) の Tomcat の構成変更
この記事について
通常 Java アプリを使用し Tomcat の設定を変更する際「server.xml」を編集しますが、
Azure App Service で設定されているファイル「D:\Program Files (x86)\apache-tomcat-x.x.xx\conf\server.xml
」は変更できません。
そこで以下の方法で Tomcat の構成を変更します。1.前提
- Apprication settings で Java / Tomcat の設定が完了しているものとします。
- Kudu コンソールを利用を想定しています。
https://<YourAppName>.scm.azurewebsites.net/DebugConsole
- 以下に内部のイメージを示します。
1.ディレクトリ構成
- Azure App Service の基本的なディレクトリ構成。
D:. ├─home │ ├─LogFiles │ └─site │ └─wwwroot │ │ web.config │ │ │ ├─conf │ │ server.xml │ │ │ └─webapps ←アプリ(.war)のデプロイディレクトリ │ ROOT │ ROOT.war │ └─Program Files (x86) └─apache-tomcat-x.x.xx └─conf server.xml2.web.config の設定
- IIS 構成ファイルであり Tomcat を起動するときに引数で追加構成を設定します。
- JVM のメモリ設定が可能です。
- 以下のサンプルでは、変更した Tomcat の設定ファイル「server.xml」を引数として設定しています。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <remove name="httpPlatformHandlerMain" /> <add name="httpPlatformHandlerMain" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" /> </handlers> <httpPlatform processPath="D:\Program Files (x86)\apache-tomcat-x.x.xx\bin\startup.bat" requestTimeout="00:04:00" arguments="-config D:\home\site\wwwroot\conf\server.xml start" startupTimeLimit="60" startupRetryCount="3" stdoutLogEnabled="true"> <environmentVariables> <environmentVariable name="CATALINA_OPTS" value="-Xms2048 -Xmx2048m" /> </environmentVariables> </httpPlatform> </system.webServer> </configuration>3.server.xml の設定
- 上記2.で設定したパスに「server.xml」ファイルを作成します。(D:\home\site\wwwroot\conf\server.xml)
※server.xmlは「D:\Program Files (x86)\apache-tomcat-x.x.xx\conf\server.xml
」からコピーし、必要に応じて変更してください。
- 投稿日:2020-09-19T00:45:20+09:00
【Kotlin】KFunctionからJavaのConstructor/Methodを取得し呼び出す
Kotlin
のリフレクションでのメソッド/コンストラクタであるKFunction
は様々な部分が抽象化されており、アプリケーションからの利用が容易となっています。
一方、Java
のConstructor
/Method
を直接呼び出した場合と比べ、KFunction
は抽象化によるオーバーヘッドから呼び出しが遅いという問題が有ります。この記事では、このオーバーヘッドを回避するため、
KFunction
の定義別にJava
のConstructor
/Method
を取得し直接呼び出すパターンをまとめます。
バージョンはそれぞれKotlin 1.4.10
/Java8
です。前置き
本題に入る前に、
kotlin.reflect.KFunction
とjava.lang.reflect.Constructor
/java.lang.reflect.Method
について書きます。パッケージ名の通り、前者は
Kotlin
のリフレクションでの関数です。
Kotlin
では、メソッドとコンストラクタは同じKFunction
として同様に扱えます。
KFunction
は、callBy
呼び出しによってデフォルト引数を用いたり、特定条件でJava
のMethod.invoke
におけるインスタンス引数を無視することができます。後者は
Java
のリフレクションでの関数です。
Constructor
とMethod
は型として分かれているため同様には取り扱えず、またMethod
はインスタンスパラメータ(static method
の場合null
、それ以外はインスタンス)を要求します。冒頭で書いた「
Java
のConstructor
/Method
を直接呼び出した場合と比べ、KFunction
は呼び出しが遅い」というのは、デフォルト引数を用いず(= 全パラメータを揃えて)KFunction.call
を呼び出した場合とConstructor.newInstance
/Method.invoke
を呼び出した場合との比較です。本文
関数の定義方法は大まかに2種類、細かく見て4種類としてそれぞれについてまとめます。
- コンストラクタ
- コンストラクタ以外
- インスタンス
- コンパニオンオブジェクト
- コンパニオンオブジェクト(
@JvmStatic
有り)検証には以下のコードを用いました。
検証に用いたプログラムの全体
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.lang.reflect.Constructor import java.lang.reflect.Method import kotlin.reflect.KFunction import kotlin.reflect.full.companionObject import kotlin.reflect.full.companionObjectInstance import kotlin.reflect.full.functions import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaMethod @Suppress("UNCHECKED_CAST") class CallJavaReflectionTest { data class ConstructorSample(val foo: Int, val bar: String) fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) companion object { fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) @JvmStatic fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) } val expected = ConstructorSample(1, "2") @Test @DisplayName("コンストラクタの場合") fun constructorTest() { val function: KFunction<ConstructorSample> = ::ConstructorSample assertEquals(expected, function.call(1, "2")) val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!! assertEquals(expected, javaConstructor.newInstance(1, "2")) } @Test @DisplayName("インスタンス関数の場合") fun instanceMethodTest() { val function: KFunction<ConstructorSample> = this::instanceMethodSample assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(this, 1, "2")) } @Nested @DisplayName("コンパニオンオブジェクトに定義したメソッドの場合") inner class CompanionObjectFunctionTest { @Test @DisplayName("メソッドリファレンスで取得した場合") fun byMethodReferenceTest() { val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample // メソッドリファレンスで取得した場合インスタンスパラメータが不要 assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } @Test @DisplayName("リフレクションで取得した場合") fun byReflectionTest() { val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!! .functions.first { it.name == "companionObjectFunctionSample" } .let { it as KFunction<ConstructorSample> } // リフレクションで取得した場合インスタンスパラメータが必要 assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } } @Nested @DisplayName("コンパニオンオブジェクトに定義したメソッド(JvmStatic指定有り)の場合") inner class StaticMethodTest { @Test @DisplayName("メソッドリファレンスで取得した場合") fun byMethodReferenceTest() { val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } @Test @DisplayName("コンパニオンオブジェクトからリフレクションで取得した場合") fun byReflectionTest() { val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!! .functions.first { it.name == "staticMethodSample" } .let { it as KFunction<ConstructorSample> } // リフレクションで取得した場合インスタンスパラメータが必要 assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } } }コンストラクタの場合
KFunction
の生成元がコンストラクタの場合、KFunction.javaConstructor
でConstructor
を取得することができます。
コンストラクタはインスタンスパラメータなどを要求しないため、KFunction.call
と同じようにConstructor.newInstance
を呼び出すことができます。@Suppress("UNCHECKED_CAST") class CallJavaReflectionTest { data class ConstructorSample(val foo: Int, val bar: String) val expected = ConstructorSample(1, "2") @Test @DisplayName("コンストラクタの場合") fun constructorTest() { val function: KFunction<ConstructorSample> = ::ConstructorSample assertEquals(expected, function.call(1, "2")) val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!! assertEquals(expected, javaConstructor.newInstance(1, "2")) } }コンストラクタ以外の場合
KFunction
の生成元がコンストラクタ以外の場合、KFunction.javaMethod
でMethod
を取得することができます。
前述の通り、Method
を呼び出すには、インスタンスパラメータ(static method
の場合null
、それ以外はインスタンス)が必要になります。ここで、コンストラクタ以外の場合、生成方法に関わらず
KFunction
からインスタンスを取得する方法が公開されていないため、KFunction.call
でインスタンスパラメータが省略されている場合でも、KFunction
単体からMethod.invoke
を呼び出すことはできません1。
JvmStatic
を付けた場合でも、KFunction
として取得する方法で取得できるのはcompanion object
への定義となるため、インスタンスパラメータをnull
としてMethod.invoke
を呼び出すことはできません。従って、コンストラクタ以外から取得した
KFunction
からMethod
を呼び出すには、別口でインスタンスを用意する必要が有ります。インスタンス関数の場合
@Suppress("UNCHECKED_CAST") class CallJavaReflectionTest { data class ConstructorSample(val foo: Int, val bar: String) fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) val expected = ConstructorSample(1, "2") @Test @DisplayName("インスタンス関数の場合") fun instanceMethodTest() { val function: KFunction<ConstructorSample> = this::instanceMethodSample assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(this, 1, "2")) } }コンパニオンオブジェクトに定義した関数の場合
@Suppress("UNCHECKED_CAST") class CallJavaReflectionTest { data class ConstructorSample(val foo: Int, val bar: String) companion object { fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) } val expected = ConstructorSample(1, "2") @Nested @DisplayName("コンパニオンオブジェクトに定義したメソッドの場合") inner class CompanionObjectFunctionTest { @Test @DisplayName("メソッドリファレンスで取得した場合") fun byMethodReferenceTest() { val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample // メソッドリファレンスで取得した場合インスタンスパラメータが不要 assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } @Test @DisplayName("リフレクションで取得した場合") fun byReflectionTest() { val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!! .functions.first { it.name == "companionObjectFunctionSample" } .let { it as KFunction<ConstructorSample> } // リフレクションで取得した場合インスタンスパラメータが必要 assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } } }コンパニオンオブジェクト(
@JvmStatic
有り)の場合2@Suppress("UNCHECKED_CAST") class CallJavaReflectionTest { data class ConstructorSample(val foo: Int, val bar: String) companion object { @JvmStatic fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar) } val expected = ConstructorSample(1, "2") @Nested @DisplayName("コンパニオンオブジェクトに定義したメソッド(JvmStatic指定有り)の場合") inner class StaticMethodTest { @Test @DisplayName("メソッドリファレンスで取得した場合") fun byMethodReferenceTest() { val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample assertEquals(expected, function.call(1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } @Test @DisplayName("コンパニオンオブジェクトからリフレクションで取得した場合") fun byReflectionTest() { val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!! .functions.first { it.name == "staticMethodSample" } .let { it as KFunction<ConstructorSample> } // リフレクションで取得した場合インスタンスパラメータが必要 assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2")) val javaMethod: Method = function.javaMethod!! assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2")) } } }まとめ
この記事では
KFunction
の呼び出しオーバーヘッドを回避するため、KFunction
の定義別にJava
のConstructor
/Method
を取得し直接呼び出すパターンをまとめました。
検証の結果、コンストラクタの場合はKFunction
単体からConstructor.newInstance
を呼び出すことができ、それ以外の場合は別口でインスタンスを取得できる場合に限りMethod.invoke
を呼び出せるという結論を得ました。単純に動くツールを作るだけであれば
KFunction.callBy
を使うのが最も容易だと思われますが、実行速度にも拘ってツールを作る場合はConstructor
/Method
直接呼び出せるよう工夫してみても良いかもしれません。この記事が何かのお役に立てば幸いです。