【Gradle】マルチモジュールプロジェクトのルートbuild.gradleへMockitoの除外を指定する【SpringBoot】

状況

Java 21化後、Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK.という警告が出るようになりました。

github.com

当該プロジェクトではMockitoを利用していなかったため、除外することにしました。
また、当該プロジェクトはSpringBootのマルチモジュールプロジェクトで、一々個別のbuild.gradleへ除外を指定するのは手間なので、ルートに1発で指定する方法を探しました。

やり方

ChatGPTに聞いた所以下の方法を提示されました。
このプロジェクトはルートにコードが無かったですが、ルートにもコードが有る場合はallProjectsに指定する方が良いかもしれません。

subprojects {
    configurations.configureEach {
        exclude(group = "org.mockito")
    }
}

一応、誤ってMockito関係の内容が利用されていた箇所にコンパイルエラーが出たため、これで機能していると思われます。

【jackson-module-kogera】value class関連処理をMethodHandle化するとどれ位高速化するか

jackson-module-kogera 2.19.0-beta25にて、value classシリアライズ / デシリアライズに関連する処理でMethodHandleを使うように変更しました。

github.com

この記事では、変更の効果が最低でどの程度かを確認した結果を紹介します。

value class関連処理をMethodHandle化する理由

Jacksonvalue classを違和感なく処理できるようにするため、value class関連処理は多くのリフレクション呼び出しが必要であり、リフレクション呼び出しが連続することもしばしば有るためです。

例えばデシリアライズ時は、最低でも「プライマリコンストラクタ呼び出し -> box化呼び出し」と2回のリフレクション呼び出しが必要です。
プロパティが非nullの場合、更にunbox化呼び出しも追加で必要です。

シリアライズの場合も、box化呼び出しとunbox化で2回のリフレクション呼び出しが必要です。

これらはMethodHandle化することで多くの改善が期待できます。

ベンチマーク

以下のリポジトリを使って比較を行いました。

github.com

ベンチマーク内容

MethodHandleで効果的に高速化するためには、型を明示することが重要です。
jackson-module-kogera 2.19.0-beta25では、ラップされる型がInt, Long, String, (Java)UUIDのいずれかだった場合に関して、型を明示的に扱うことによる最適化を行っています。
よって、この最適化の有無を比較する必要が有ります。

また、ベンチマーク内容は、Jacksonによる処理の量をなるべく減らし、今回の高速化に関する効果が表れやすいようにする必要が有ります。

以上を考慮し、ベンチマーク内容は以下のようにしました。

  • ベンチマークの入出力は基本的に1桁整数 + 最低限のJSON構造
  • ベンチマーク対象となる型は1プロパティのみ持たせる
  • 検証対象とするvalue classは、Intをラップしたもの(= 型明示による最適化有り)と、Shortをラップしたもの(= 型明示による最適化無し)

最適化の効果はvalue classの数が増えた分だけ高まるため、このベンチマークからは、value classに関する処理が最低でどれだけ改善されたかが分かります。

結果

8ad900dをまとめたものです。

スループット

比が1より大きいほど改善されています。

シリアライズ
10/12ケースで改善が確認できました。
最も基本的なケースであるunbox.IntBenchmark.wrapped(シンプルなvalue classをプロパティに持つクラス)に関しては、約10%のスループット向上を記録しました。

key.jsonKey.ShortBenchmark.benchmarkunbox.ShortBenchmark.directに関しては劣化が確認されましたが、それぞれ1%未満であり、誤差の範疇です。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
key.jsonKey.IntBenchmark.benchmark 1777332.255594 1817286.155664 1.022479702
key.jsonKey.ShortBenchmark.benchmark 1831096.322124 1819773.928008 0.9938166038
key.unbox.IntBenchmark.benchmark 1548940.949888 1557133.079293 1.005288858
key.unbox.ShortBenchmark.benchmark 739703.867427 760804.578200 1.028525889
jsonValue.IntBenchmark.direct 2892679.151030 2912412.745928 1.006821909
jsonValue.IntBenchmark.wrapped 1663825.635318 1866756.436996 1.121966387
jsonValue.ShortBenchmark.direct 2811182.009216 2904066.732081 1.033041163
jsonValue.ShortBenchmark.wrapped 1670737.423967 1832761.453181 1.096977554
unbox.IntBenchmark.direct 3179525.595621 3225851.588935 1.014570096
unbox.IntBenchmark.wrapped 1874000.549577 2057832.237266 1.098095856
unbox.ShortBenchmark.direct 3137818.450200 3135692.306827 0.9993224135
unbox.ShortBenchmark.wrapped 1631790.050848 1727636.426984 1.058736953

シリアライズ
8/10ケースで改善が確認できました。
最も基本的なケースであるbyPrimaryConstructor.IntBenchmark.wrapped(シンプルなvalue classをプロパティに持つクラス)に関しては、約7%のスループット向上を記録しました。

byJsonCreator.ShortBenchmark.directbyPrimaryConstructor.IntBenchmark.directに関しては、それぞれ約4%の劣化が確認されました。
ただし、これらはvalue classを直接デシリアライズする稀なケースです。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
KeyBenchmark._int 628928.592524 642907.263310 1.022226165
KeyBenchmark._short 536357.711456 555365.450802 1.03543855
byJsonCreator.IntBenchmark.direct 1784708.471187 1850169.260039 1.036678701
byJsonCreator.IntBenchmark.wrapped 916055.903594 958713.142722 1.046566196
byJsonCreator.ShortBenchmark.direct 1880281.625663 1809603.994811 0.9624111463
byJsonCreator.ShortBenchmark.wrapped 921136.345195 935274.268244 1.01534835
byPrimaryConstructor.IntBenchmark.direct 2147950.142659 2057745.453727 0.9580042911
byPrimaryConstructor.IntBenchmark.wrapped 995537.946778 1064727.840156 1.069500006
byPrimaryConstructor.ShortBenchmark.direct 1691792.929911 1739138.397988 1.02798538
byPrimaryConstructor.ShortBenchmark.wrapped 904488.697510 963217.354551 1.064930228

シングルショット

比が1より小さいほど改善されています。

シリアライズ
11/12ケースで劣化、劣化しなかったケースもほぼ差が有りませんでした。
最も基本的なケースであるunbox.IntBenchmark.wrappedに関して、劣化は2%未満でした。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
key.jsonKey.IntBenchmark.benchmark 184.561424 195.296498 1.058165318
key.jsonKey.ShortBenchmark.benchmark 187.364271 194.738718 1.039358875
key.unbox.IntBenchmark.benchmark 192.471979 192.413958 0.9996985483
key.unbox.ShortBenchmark.benchmark 185.425115 186.525809 1.005936057
jsonValue.IntBenchmark.direct 172.408964 176.969350 1.02645098
jsonValue.IntBenchmark.wrapped 286.545790 295.294399 1.030531277
jsonValue.ShortBenchmark.direct 175.135014 175.543538 1.002332623
jsonValue.ShortBenchmark.wrapped 282.984258 295.457164 1.044076325
unbox.IntBenchmark.direct 175.688984 177.968913 1.012977074
unbox.IntBenchmark.wrapped 292.981319 297.085654 1.014008862
unbox.ShortBenchmark.direct 176.957803 177.437413 1.002710307
unbox.ShortBenchmark.wrapped 281.099359 288.922695 1.027831213

シリアライズ
7/10ケースで劣化、劣化しなかったケースもほぼ差が有りませんでした。
最も基本的なケースであるbyPrimaryConstructor.IntBenchmark.wrappedに関して、劣化は2%少しでした。

2.19.0-beta24 2.19.0-beta25 2.19.0-beta25 / 2.19.0-beta24
KeyBenchmark._int 281.434946 280.577312 0.9969526386
KeyBenchmark._short 277.767897 286.769142 1.032405635
byJsonCreator.IntBenchmark.direct 269.644305 270.065927 1.001563623
byJsonCreator.IntBenchmark.wrapped 305.317364 307.809560 1.008162641
byJsonCreator.ShortBenchmark.direct 270.556615 275.870427 1.019640296
byJsonCreator.ShortBenchmark.wrapped 308.366190 306.834562 0.9950330871
byPrimaryConstructor.IntBenchmark.direct 275.885608 278.743274 1.010358155
byPrimaryConstructor.IntBenchmark.wrapped 302.336269 309.165245 1.022587353
byPrimaryConstructor.ShortBenchmark.direct 272.375393 269.656569 0.9900180998
byPrimaryConstructor.ShortBenchmark.wrapped 297.234643 316.185681 1.063757837

全体を通した考察

スループットに関しては、想定通り基本的なケース全般で改善を確認できました。
特にシリアライズは10%、デシリアライズは7%程度と、Jacksonに関するその他処理が有る中ではそれなりの改善量が得られたことは良かったです。
ただし、恐らくベンチマーク負荷が軽すぎたため、型明示による最適化有無の差はそれ程確認できませんでした(これに関しては、逆に「何が何でも最適化しなければならないほどの差は無い」とも言えるでしょうか)。

シングルショットに関しては、初期化処理の量自体は増えているため、想定通りほぼ劣化となりました。
ただ、全体を通しても劣化幅は10%未満であるため、それ程酷くならなかった点は安心しました。

data classとの差について

data classと比較した際のスコアは、残念ながらMethodHandle化後も大きいままでした。

github.com

補足

経験上、恐らくLambdaMetaFactoryを使った方が高速ですが、以下の理由からJacksonではLambdaMetaFactoryを使わない方針となっているため、試していません。

github.com

おまけ

ベンチマーク結果は以下に格納してあります。
一応比較用テンプレートも用意しているため、ローカルでの実行結果比較も可能です。

drive.google.com

Junieのクラウドクレジットはライセンスアクティベートから30日単位でリセットされる

TL;DR

  • Junieクラウドクレジットは、ライセンスアクティベートから30日単位でリセットされる
  • クラウドクレジットの前借・繰越は不可
    • AI Ultimate契約の場合、サポートに相談できるらしい

※2025/06/25時点の話なので、将来的には変わってるかもしれません。

追記

JetBrainsの方より、クレジットを使い切ってしまった場合の対処は以下を参照するよう案内を頂きました。

blog.jetbrains.com

ソース

クラウドクレジットのリセットタイミングについて

Junieクラウドクレジットは、ライセンスアクティベートから30日単位でリセットされる

これはFAQに記載が有りました。

When your monthly cloud quota is used up, cloud-powered features are paused until the quota resets.
The reset occurs every 30 days from the date you first activated your JetBrains AI license — whether free or paid — which may not necessarily coincide with the start of your billing cycle. Local features remain unaffected.

lp.jetbrains.com

クラウドクレジットの前借・繰越について

AI Ultimate契約の場合、サポートに相談できるらしい

これはメール問い合わせの返答からです。

This message is AI-generated based on our knowledge base, FAQ, and other similar requests. If it doesn't answer your question, please reply to this message.

ユーザー様

Junieのクラウドクレジット(AIクォータ)の管理についてお問い合わせいただき、ありがとうございます。

Junieのクラウドクレジットは月単位で管理されており、毎月初めにクォータがリセットされます。この月ごとのリセットは、月額契約・年額契約のいずれの場合でも適用されます。未使用分のクレジットは翌月に繰り越されず、また、将来の月からクレジットを前借りすることもできません。

月間クォータを使い切った場合、Junieを含むクラウドベースの機能は、翌月のリセットまで一時停止されます。AI Ultimateをご利用の場合、月間クォータを使い切った際にはサポートまでご相談いただけますが、追加クレジットの付与は保証されておらず、個別に対応させていただきます。

詳細はJetBrains AI FAQおよび価格ページをご参照ください。

ご不明点やご質問がございましたら、お気軽にお問い合わせください。

雑感

以下の記事から、週末のOSS開発でAI Proプランを使ってました。
開発出来なかった週も有った位なのに30日でクレジットを使い切ったので、業務レベル利用ならAI Ultimateプラン必須な感強いです(あるいは、クレジット節約術を探るべきなのか、、、)。

wrongwrong163377.hatenablog.com

【Intellij IDEA(Ultimate)】プロジェクトのJavaバージョン更新後ビルドが通らなくなった問題への対処【Gradle】

TL;DR

  • 壊れていたのはIntellijのプロジェクト設定だった(元から壊れているものが顕在化した)
  • 基本的にProject StructureからJDKを設定すれば直るはずだが、自分の場合何故か直らなかった
  • 諸事情から.idea配下のファイルを弄って直した
    • プロジェクトをpullし直した方が早いかも?

状況

プロジェクトのJDKを17 -> 21に更新した直後からビルドが通らなくなりました。
CIや他の方のローカルでは動いていたので、おま環と特定しました。

対処1

このような場合、基本的にはProject Structure -> Project -> SDKから適切なJDKを設定すれば直るはずです。
また、これがダメでも、Project Structure -> Modulesから、個々のモジュールのLanguage levelを設定する(Project defaultに合わせる)ことで直るはずです。

ただ、自分の環境ではこれをやっても直りませんでした。

対処2

確認していた所、プロジェクトの何かが壊れているようでした(gradle上ではJava 21になっているのに、従来のJava 17や、今使っていないはずのJava 18が指定されている状態だった)。
本来であればプロジェクトをcloneし直すとか、.idea配下を全消しした上でリロードすべきな気もしますが、消えてほしくないものが色々有ったため、手動での修復を試みました。

実際に弄ったもの

以下のような変更を行った後にIDEの再起動を行うことで、一旦テストは通るようになりました。

  • .idea/compiler.xml
    • <component name="CompilerConfiguration">配下のJavaバージョン設定削除
  • .idea/gradle.xml
    • GradleProjectSettings配下のJavaバージョン設定削除
  • .idea/workspace.xml
    • &quot;jdk.selected.JAVA_MODULE&quot;: &quot;corretto-18&quot;,の削除

ただ、対処後も一部変なバージョンが残ってしまうなどの問題が有ったため、手動でもう少し直すことが必要でした。

OSS開発でJunie軽く使ってみたメモ

環境はIntellij IDEA(Ultimate) in Windowsです。

セットアップ

PluginsからJunieプラグインをインストールして始めました。

フリー版の使用感

一応無料枠が有りました。
ただし、ちょっと触っただけなのに一瞬で使い切ったので、正直フリー状態で出来ることはほぼ無い感が強いです。

(しばらく使った後追記)

この記事執筆時点でAI Proを契約してましたが、週末OSS開発(しかも開発できなかった週有り)ですらクレジット使い切りました。
ガチで使う場合はちゃんとAI Ultimateを契約した方が良さそうです。

wrongwrong163377.hatenablog.com

AI Pro課金後やってみたこと

既存ケースをベースにしたテストケース拡充①

具体的には以下のPRです。

github.com

既存と同じテストケースを、別の型に対して適用しています。
期待する変更内容としては、ファイル毎に分かれているものと、1ファイル内でのケース追加が有りました。

parameterSizeに関するケース(4ファイル・合計4千行弱)は生成できませんでしたが、他の17ファイルは期待通り生成できました。

生成できなかったケースは、恐らく元になるファイルがデカすぎてコンテキストウィンドウに収まらなかったものと思われます(タスクの進め方とかを指定したらやれたのかも……?)。
また、「このパッケージ内全体で適切に生成して」的な指示の出し方だと、深い階層にあるテスト?が無視されてしまい、追加で指示が必要となるケースが有りました。

既存ケースをベースにしたテストケース拡充②

具体的には以下のPRです。

github.com

この変更は以下のような手順で生成させました。

  1. 既存ケースをベースに一旦生成させ、目的に合わせて手動修正
  2. 1をベースに、検証対象を変えながら1つ1つ指示して生成(事前に洗い出していた対象を指定)
  3. 「2で生成したケースと検証対象コードを参考に、他に追加すべきテストが有れば作って」と指示して生成

やってみた感想

特に「既存ケースをベースにしたテストケース拡充②」の3で、自分でも発見できていなかったケースが生成されたことが非常に嬉しかったです。
他にも、単調で面倒なテスト作成作業をワクワクしながら進めることが出来て良かったです。
モチベ的な意味で作業速度が上がったのはいいことでした。

また、テストを実行しながら開発してくれるのは安心感が有って良かったです。

あまり長く触れていないので、まだ使いこなせていない・どこまでやれるか把握できていない感は強いです。
特に、指示後にPlanを吐いてくれるので、その辺りをもっとちゃんと確認すれば、より良い結果を得られるような気がしています。

IntellijCopilot Chatも利用しましたが、コード編集に関する安心感はJunieの方が良く感じました(これに引っかかってまともに使えてないというのも有りますが……)。

【Java】MethodHandleを合成する

TL;DR

  • MethodHandle Aからの戻り値 -> MethodHandle Bの引数」という形の合成は、MethodHandles.filterReturnValueを使えば実現できる

docs.oracle.com

やること

Method Aを呼び出し、その結果をMethod Bに渡す」というような場合、従来のリフレクションではそれぞれのMethodを個別に呼び出す必要が有りました。
一方、MethodHandleの場合、それぞれのMethodHandleを合成することができます。

これによって呼び出しに関する記述が簡略化します。
また、手元で軽くJMHベンチマークを取った限り、それぞれを個別に呼び出すよりも、合成結果を1回呼び出す方が高速でした。

やり方

MethodHandles.filterReturnValueで実現できます。
以下はJavaDocのサンプルにコメントを補完・整形した疑似コードです。

import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;

// 文字列を結合するMethodHandle
MethodHandle cat = lookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
// "x"と"y"を結合すれば"xy"になる
System.out.println((String) cat.invokeExact("x", "y")); // xy

// 文字列長を取得するMethodHandle
MethodHandle length = lookup().findVirtual(String.class, "length", methodType(int.class));

// MethodHandles.filterReturnValueで合成
MethodHandle f0 = filterReturnValue(cat, length);
// 「文字列結合 -> 結合結果の文字列長取得」という1つの関数のように振る舞う
// "x"と"y"を結合した結果の文字列長は2
System.out.println((int) f0.invokeExact("x", "y")); // 2

補足

引数を変換したいケースでは、filterArgumentsが利用できます。

docs.oracle.com

【Kotlin】PublishedApiアノテーションを用い、publicなinline関数から呼び出せるinternal関数/クラスを定義する

internal関数/クラスへ@PublishedApiアノテーションを付与すると、publicinline関数からも呼び出せるようになります。

kotlinlang.org

kotlinlang.org

ただし、privateな内容には適用できないため、モジュール or プロジェクトレベルで分かれている場合にしか利用できません。