好奇心からですが、cglib 以外にランタイム Java コード生成用の (安定した) オープン ソース プロジェクトはありますか? そして、なぜそれらを使用する必要があるのですか?
5 に答える
ASM java-asm
CGLIB と他のほとんどすべてのライブラリは、それ自体が非常に低いレベルで動作する ASM の上に構築されています。これは、バイトコードと、それを適切に使用するためにJVMSを少し理解する必要があるため、ほとんどの人にとって目障りです。しかし、ASM をマスターすることは、間違いなく非常に興味深いものです。ただし、優れた ASM 4 ガイドがありますが、API の一部に javadoc ドキュメントが存在する場合は非常に簡潔になる可能性がありますが、改善されていることに注意してください。新しい機能をサポートするために、JVM のバージョンに厳密に従います。
ただし、完全な制御が必要な場合は、ASM を選択してください。
このプロジェクトは定期的に更新されます。この編集バージョン 5.0.4 の時点で、2015 年 5 月 15 日にリリースされました。
バイトバディ バイトバディ
Byte Buddy はかなり新しいライブラリですが、CGLIB や Javassist が提供するすべての機能を提供します。Byte Buddy は、バイト コード レベルまで完全にカスタマイズでき、非常に読みやすいコードを可能にする表現力豊かなドメイン固有言語が付属しています。
- デフォルト メソッドに関する一部のオペコードの Java 8 セマンティックの変更を含む、すべての JVM バイトコード バージョンをサポートします。
- ByteBuddy は、他のライブラリが持つ欠点に悩まされていないようです
- 高度に構成可能
- かなり速い(ベンチマーク コード)
- タイプセーフな流暢な API
タイプセーフなコールバック
Javassist のアドバイスやカスタム インストルメンテーション コードはプレーンなコードに基づいている
String
ため、このコード内では型チェックとデバッグは不可能ですが、ByteBuddy では純粋な Java でそれらを記述できるため、型チェックが実施され、デバッグが可能になります。注釈主導 (柔軟)
ユーザー コールバックは、コールバックで必要なパラメーターを受け取ることができる注釈を使用して構成できます。
エージェントとして利用可能
気の利いたエージェント ビルダーにより、ByteBuddy を純粋なエージェントとして、またはアタッチ エージェントとして使用できます。それは別の種類を可能にします
- 非常によく文書化されています
- たくさんの例
- クリーンなコード、~94% のテスト カバレッジ
- Android DEX のサポート
おそらく主な欠点は、API が初心者には少し冗長ですが、プロキシ生成 DSL の形をしたオプトイン API として設計されていることです。魔法や疑わしいデフォルトはありません。バイトコードを操作する場合、おそらく最も安全で合理的な選択です。また、複数の例と大きなチュートリアルがあるため、これは実際の問題ではありません。
2015 年 10 月、このプロジェクトはOracle Duke's Choice Award を受賞しました。現時点では1.0.0 マイルストーンに到達したばかりで、これはかなりの成果です。
バージョン 2.1.0 では、mockito が CGLIB を Byte Buddy に置き換えたことに注意してください。
Javassist javassist
Javassist の javadoc は、CGLIB の Javadoc よりもはるかに優れています。クラス エンジニアリング API は問題ありませんが、Javassist も完璧ではありません。特に、ProxyFactory
CGLIB に相当する には、Enhancer
いくつかの欠点があります。
- Bridge メソッドは完全にはサポートされていません (つまり、共変の戻り値の型に対して生成されるもの)
ClassloaderProvider
代わりに静的フィールドである場合、同じクラスローダー内のすべてのインスタンスに適用されます- カスタム命名は歓迎された可能性があります(署名されたjarのチェック付き)
- 拡張ポイントはなく、関心のあるほとんどすべてのメソッドはプライベートです。これは、いくつかの動作を変更したい場合に面倒です
- Javassist はクラス内の注釈属性をサポートしていますが、 ではサポートされていません
ProxyFactory
。
アスペクト指向の側では、プロキシにコードを挿入できますが、Javassist でのこのアプローチは制限されており、少しエラーが発生しやすくなっています。
- アスペクト コードは、オペコードでコンパイルされるプレーンな Java 文字列で記述されます
- 型チェックなし
- ジェネリックなし
- ラムダなし
- 自動 (アン) ボクシングなし
また、Javassist は Cglib よりも遅いことが認識されています。これは主に、CGLIB のようにロードされたクラスを読み取るのではなく、クラス ファイルを読み取るアプローチによるものです。また、公正を期すために、実装自体が読みにくいものになっています。Javassist コードを変更する必要がある場合、何かが壊れる可能性が高くなります。
Javassist も不活発でした。2013 年頃の githubへの移行は、コミュニティからの定期的なコミットとプル リクエストを示しているため、有用であることが証明されたようです。
これらの制限は、バージョン 3.17.1 でも有効です。バージョンはバージョン 3.20.0 に上げられましたが、Javassist にはまだ Java 8 サポートに関する問題があるようです。
JiteScript
JiteScript は、ASM の DSL を適切に形作る新しい部分のように見えます。これは、最新の ASM リリース (4.0) に基づいています。コードはきれいに見えます。
しかし、プロジェクトはまだ初期段階にあるため、API や動作が変更される可能性があり、さらにドキュメントは悲惨です。そして、放棄されない限り、更新はほとんどありません。
プロクセッタ ・ジョッド
これはかなり新しいツールですが、最高のヒューマンAPI を提供します。サブクラス プロキシ (cglib アプローチ)、ウィービング、デリゲーションなど、さまざまな種類のプロキシを使用できます。
ただし、これはかなりまれですが、うまく機能するかどうかは情報がありません。バイトコードを扱う際には、対処しなければならない非常に多くの特殊なケースがあります。
アスペクト J
AspectJ は、アスペクト指向プログラミング(のみ) のための非常に強力なツールです。AspectJ はバイト コードを操作して目標を達成できるようにします。ただし、これにはコンパイル時の操作が必要です。バージョン2.5、4.1.x 以降、エージェントを介したロード時のスプリング オファー ウィービング。
CGLIB cglib
その質問が出されてから更新された CGLIB について一言。
CGLIB は非常に高速です。CGLIB がこれまで (2014 年から 2015 年) まで、どの代替手段よりもほぼ優れた機能を発揮していたという事実と共に、それがまだ存在している主な理由の 1 つです。
一般的に言えば、実行時にクラスを書き換えることができるライブラリは、対応するクラスが書き換えられる前に型をロードすることを避ける必要があります。したがって、リフレクションで使用される型をロードする必要がある Java リフレクション API を利用することはできません。代わりに、IO を介してクラス ファイルを読み取る必要があります (これはパフォーマンス ブレーカーです)。これにより、たとえば Javassist や Proxetta は、単にリフレクション API を介してメソッドを読み取り、それらをオーバーライドする Cglib よりも大幅に遅くなります。
ただし、CGLIB は現在、活発に開発されていません。最近のリリースはありましたが、これらの変更は多くの人にとって重要ではないと見なされ、ほとんどの人はバージョン 3 に更新しませんでした。これは、CGLIB が最後のリリースでいくつかの深刻なバグを導入し、実際には信頼を築くことができなかったからです。バージョン 3.1 は、バージョン 3.0 の多くの問題を修正しました (バージョン 4.0.3 Spring フレームワークがバージョン 3.1を再パッケージ化したため)。
また、CGLIB のソース コードは質が悪く、新しい開発者が CGLIB プロジェクトに参加する様子は見られません。CGLIB の活動の印象については、メーリング リストを参照してください。
guice メーリング リストでの提案に従って、CGLIB がgithubで利用できるようになり、コミュニティがプロジェクトをより適切に支援できるようになりました。動作しているように見えます (複数のコミットとプル リクエスト、ci、更新された maven) が、ほとんどの懸念事項がまだ残っています。 .
現時点では、バージョン 3.2.0 に取り組んでおり、Java 8 に注力していますが、これまでのところ、Java 8 のサポートを希望するユーザーは、ビルド時にトリックを使用する必要があります。しかし、進歩は非常に遅いです。
また、CGLIB は、PermGen のメモリ リークに悩まされていることが今でも知られています。しかし、他のプロジェクトは、これほど長い間実戦でテストされていない可能性があります。
コンパイル時注釈処理 注釈処理
これはもちろんランタイムではありませんが、エコシステムの重要な部分であり、ほとんどのコード生成の使用ではランタイムの作成は必要ありません。
これは、注釈を処理するための別のコマンド ライン ツールが付属している Java 5apt
から始まり、Java 6 からは注釈処理が Java コンパイラに統合されました。
明示的にプロセッサを渡す必要がある場合もありましたが、現在はServiceLoader
アプローチ (このファイルMETA-INF/services/javax.annotation.processing.Processor
を jar に追加するだけ) により、コンパイラは注釈プロセッサを自動的に検出できます。
コード生成におけるこのアプローチには欠点もあり、多くの作業と、バイトコードではなく Java 言語の理解が必要です。この API は少し扱いにくく、1 つがコンパイラのプラグインであるため、このコードが最も回復力があり、ユーザー フレンドリーなエラー メッセージになるように細心の注意を払う必要があります。
ここでの最大の利点は、実行時に別の依存関係を回避できることです。permgen のメモリ リークを回避できます。そして、生成されたコードを完全に制御できます。
結論
2002年、CGLIB は、バイトコードを簡単に操作するための新しい標準を定義しました。私たちが現在持っている多くのツールと方法論 (CI、カバレッジ、TDD など) は、当時は利用できなかったか、成熟していませんでした。CGLIB は 10 年以上にわたって関連性を維持してきました。それはかなりまともな成果です。オペコードを直接操作するよりも高速で、使いやすい API を備えていました。
コード生成に関する新しい標準を定義しましたが、現在では環境と要件が変化し、標準と目標も変化したため、そうではありません。
JVM が変更され、最近および将来の Java (7/8/9/10) バージョン (invokedynamic、デフォルト メソッド、値の型など) で変更される予定です。ASM は API と内部を定期的にアップグレードしてこれらの変更に対応しましたが、CGLIB などはまだそれらを使用していません。
注釈処理は勢いを増していますが、ランタイム生成ほど柔軟ではありません。
2015 年の時点で、Byte Buddyは、この分野では比較的新しいものですが、ランタイム生成に関して最も説得力のあるセールスポイントを提供しています。適切な更新率であり、作成者は Java バイト コードの内部構造について深い知識を持っています。
プロキシを作成する必要がある場合は、commons-proxyを参照してください。これは、CGLIB と Javassit の両方を使用します。
とにかく cglib で使用されていると思われる生のASMを好みます。低レベルですが、ドキュメントは素晴らしく、慣れれば飛べるようになります。
2 番目の質問に答えるには、リフレクション プロキシと動的プロキシが少しまとまりがないと感じ始め、堅実なソリューションが必要な場合に、コード生成を使用する必要があります。過去には、コード生成ステップを Eclipse のビルド プロセスに追加したこともあり、事実上、あらゆるものについてコンパイル時間のレポートを作成できました。
cglibの代わりに Javassistを使用する方が理にかなっていると思います。たとえば、javasist は、cglib とは異なり、署名付きの jar で完全に動作します。その上、 Hibernate プロジェクトなどの壮大なプロジェクトは、 Javassist を支持して cglib の使用をやめることにしました。
CGLIB は、10 年以上前の AOP および ORM の時代に設計および実装されました。現在、これを使用する理由が見当たらず、このライブラリを保守していません (レガシー アプリケーションのバグ修正を除く)。実際、私が今まで見た CGLIB のユースケースはすべて、現代のプログラミングにおけるアンチパターンです。groovy などの JVM スクリプト言語を使用して同じ機能を実装するのは簡単です。