20210108のJavaに関する記事は4件です。

特定の maven task を実行したときだけ ClassLoader で目的のクラスがスキャンできない

TL;DR

  • maven-surefire-plugin というのが、 classpath 内のたくさんの jar ファイルを surefirebooter~~~.jar というものにまとめてしまう
  • ClasspathHelper.forClassLoader() の後に ClasspathHelper.forManifest() を挟むことで内部の URL 一覧を取得できる

詳細

実行時に特定の interface を持つサブクラスをスキャンするために、次のようなコードを書いてました。

    List<ClassLoader> classLoadersList = new LinkedList<>();
    classLoadersList.add(ClasspathHelper.contextClassLoader());
    classLoadersList.add(ClasspathHelper.staticClassLoader());

    Reflections reflections = new Reflections(new ConfigurationBuilder()
        .setScanners(new SubTypesScanner(false), new ResourcesScanner())
        .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
        // Should append suffix \..* since filter is applied to package+class name.
        .filterInputsBy(new FilterBuilder().include(packageRegex + "\\..*")));

    // Get subtypes you want.
    reflections.getSubTypesOf(YourInterface.class).stream()...

IDE (IntelliJ IDEA) で GUI でテストしている間は正常に動作していましたが、 mvn verify をした時だけほしいクラスが見つからないということがありました。
そこで classpath のリストを試しに出力してみました。

    for (URL url : ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))) {
      System.out.println(String.format("%s", url.toString()));
    }

本来なら使用しているライブラリの jar やプロジェクト内のモジュールへのフォルダパスなどが出力されるはずです。

  • 本来ほしい内容
file:/C:/Users/.../build/classes/java/test
file:/C:/Users/.../build/libs/mylibrary.jar
file:/C:/Users/.../.m2/repository/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar
...
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/dnsns.jar
...

ですが mvn verify したときは次のような結果になりました。

  • mvn verify した時の結果
file:/C:/Users/.../target/surefire/surefirebooter4604658542841964121.jar
file:/C:/Users/.../.m2/repository/org/jacoco/org.jacoco.agent/0.8.2/org.jacoco.agent-0.8.2-runtime.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/dnsns.jar
...

これは maven-surefire-plugin というのが、たくさんある jar を surefirebooter4604658542841964121.jar にまとめているためです。
なぜまとめるかというと、 OS やコマンドライン環境の違いにより実行できるコマンドの長さに限界があるため java コマンドを実行する際にすべての jar を classpath 指定できない場合があるためです。
で、この surefirebooter の jar の中身は特にオプションが明示されてない場合は実際の jar ではなく manifest ファイルになっています。
image.png

manifest.mf については、ClasspathHelper.forClassLoader() ではなく ClasspathHelper.forManifest() を使うことで、 manifest.mf 内の URL を展開して受け取ることができます。また ClasspathHelper.forManifest() は渡されたものが manifest ファイルでなかった場合もそのまま jar の URL を返してくれます。なので特に manifest ファイルであるかどうかを判断することなく、次のように forClassLoader()forManifest() を重ね掛けすることですべての jar の URL を獲得することができます。

    final Collection<URL> effectiveClassUrls =
        // Resolve manifest in Surefirebooter jar in classpath.
        ClasspathHelper.forManifest(
            ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])));

最終的に、必要なサブクラスをスキャンする処理は次のようになります。

    List<ClassLoader> classLoadersList = new LinkedList<>();
    classLoadersList.add(ClasspathHelper.contextClassLoader());
    classLoadersList.add(ClasspathHelper.staticClassLoader());

    final Collection<URL> effectiveClassUrls =
        // Resolve manifest in Surefirebooter jar in classpath.
        ClasspathHelper.forManifest(
            ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])));

    Reflections reflections = new Reflections(new ConfigurationBuilder()
        .setScanners(new SubTypesScanner(false), new ResourcesScanner())
        .setUrls(effectiveClassUrls)
        // Should append suffix \..* since filter is applied to package+class name.
        .filterInputsBy(new FilterBuilder().include(packageRegex + "\\..*")));

    // Get subtypes you want.
    reflections.getSubTypesOf(YourInterface.class).stream()...

補足

maven-surefire-plugin が明示的に pom.xml に追加されてない場合も、依存ライブラリ内で定義されている場合があるのでご注意ください。
今回私の場合は maven-failsafe-plugin を使用しており、その中で surefire も有効になっていました。

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

javaの基礎の基礎②~if文とswitch文~

・if文
①if文を使うと「もしも・・・ならば」という文章をプログラムで表現することができます。
 「もしも降水確率が50%以上なら、傘を持っていく」をプログラムで書いてみましょう。
 (if文を説明するためのプログラムの断片です)

 if (p >= 50) {
  System.out.println("傘を持っていきます");
}

 これは「変数pの値を調べ、その値がもしも50以上なら、"傘を持って行きます"と画面に表示する」という動作をします。
 上のプログラムは次のような構造をしています。
 
 if (条件式) {
  条件が成り立つときの処理
}

 条件式の値は、「条件を満たすときtrue(トゥルー)」、「満たさないときはfalse(フォルス)」になります。
 条件式の値は、必ずこのどちらかになります。
 ()でくくられた条件式の後には{}でくくられた処理がやってきます。これは条件が満たされたときに行う処理の範囲を示します。
 if文は、()の中の条件が満たされるかどうかを調べ、もし満たされるなら{}の中を実行し、満たされなければ{}の中を実行しません。
②if…else文を使うと「もしも・・・ならば・・・さもなくば・・・」という文章をプログラムで表現できます。
 
 if (p >= 50) {
  System.out.println("傘を持っていきます");
} else {
  System.out.println("傘を持っていきません");
}
 
 ここに登場する新しい言葉は「else(エルス)」です。まさに日本語の「さもなくば」に相当します。
 elseの後ろにはまた{}があります。ここには条件が成り立たなかった(満たされなかった)時に行う処理が書かれています。
 
 if (条件式) {
  条件が成り立った(true)時の処理A
} else {
  条件が成り立たなかった(false)時の処理B
}

 条件が成立すると同時に成立しない、などということはありません。したがって処理Aと処理Bのどちらか一方は必ず行われます。
③演算子「&&」は「~かつ~」を表すもので、演算子「||」は「~または~」を表すものです。
 
 0 <= n && n < 50
 
 は、「nが0以上である、かつ、nが50より小さい」となります。
 「条件式1 && 条件式2」は、「条件式1と条件式2の両方が成り立つときのみ真」となる条件です。
 次に、
 
 n < 0 || 100 < n

 は、「nが0より小さい、または、nが100より大きい」となります。
 「条件式1 || 条件式2」は、「条件式1と条件式2の少なくともどちらか一方が成り立ったとき真」となる条件です。

・switch文
①多くの選択肢から1つを選んで実行するための構文を「switch(スイッチ)文」と言います。
 
  switch (式) {
  case 定数式1:
      処理1
      break;

  case 定数式2:
      処理2
      break;

  default:
      処理3
      break;
}

 {}でくくられた範囲が選択をするための候補たちです。
 候補のはじめには、候補ごとに「case(ケース)」と書きます。caseの最後にはコロン(:)を書きます。
 最後の候補には「default(デフォールト)」と書きます。これは「いままで1つ1つあげた場合のどれでもなかったら」という意味です。
 switch文のdefaultはif文のelseに相当するといってもよいでしょう。
 1つのswitch文の中にcaseは何個書いてもかまわないが、defaultは1個しか書けません。
 caseとして明示的に書かれていない値ならば、default以下が実行されます。
②caseとcase(あるいはdefault)の間に書かれるのが、選択された場合の処理です。
 処理の最後には「break(ブレーク);」を書きます。これは「ここで処理を終わります」という意味です。
 複数のcaseをbreakなしで書いておくと、同じ処理が行われます。言い換えれば、必要なbreakは書き忘れてはいけません。
 さもないと、他のcaseのところまでどんどん処理が進んでしまいます。
③switch文を実行する時、まず()でくくられた式が計算されます。これを「式の評価」と言います。
 式を評価した結果(計算の結果)、定数式1の値に等しければ処理1が実行され、もし定数式2の値に等しければ処理2が実行されます。
 定数式1と定数式2の両方の値と等しくなければ、defaultに対応した処理3が実行されます。
 switch文は、式の値によって処理を選ぶ構文です。ですから、1つのswitch文の中では、2か所以上のcaseに同じ定数式を書いてはいけません。
 同じ定数式が2つ以上あったら、どの処理を実行してよいかわからなくなってしまうからです。

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

Java -versionのように、「_」の後の値をWindowsバッチで取得する方法

sample.bat
@echo off
for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "javaV=%%m"

echo %javaV%
pause

ポイント

Javaのバージョン取得コマンドは何故かエラー出力扱いになる。

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

とあるTomcatプラグインの停止ポート(8081)

はじめに

久しぶりにあるWebアプリとそこから呼び出される別のWebAPIシステムの改修作業が入ったので、開発環境を立ち上げようとしたらWebアプリが起動しなかったんだぁ。

Docker?何それ美味しいの?

環境(細かいのは職場のなので不明)

  • Webアプリ
  • WebAPI
    • SpringBoot

なにが起きたか

  1. WebAPIの実行可能jarをport8081で立ち上げる
    • java -jar xxx.jar --server.port=8081
  2. Webアプリをgradleタスクから立ち上げる
    • gradle tomcatRun
  3. 起動しない\(^o^)/

--traceをつけて実行するとportが使われている様子

Address already in use: JVM_Bind

でもnetstat /nao | findstr "8080"しても該当がない

はて...

なんだったのか

githubに書いてあるのですが、TomcatPluginではtomcatの停止portとして8081を使用していました。

WebAPI側を8090に変更したら正常に動きました。

どうすればすぐに気づけたか

  • さっさと詳細なログを出せばよかった
  • いきなりnetstatで8080に絞り込むべきではなかった
    • WebAPIで使用してるポート側からも探すべきだった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む