Spring に精通している友人は、AOP をよりよく理解できるはずです。アスペクト指向プログラミングを使用すると、ターゲット メソッドの前後に実行するロジックを組み込むことができます。本日ご紹介する Java エージェント テクノロジーは、概念的には AOP に似ており、Java エージェントまたは Java プローブ テクノロジーとも言い換えることができます。 Java エージェントは JDK1.5 以降に登場しました。これにより、プログラマーはエージェント テクノロジを使用して、アプリケーションから独立したエージェント プログラムを構築できるようになります。幅広い用途があり、他の JVM 上のプログラムの監視、実行、さらには置き換えにも役立ちます。どのようなシナリオで使用されるかを確認するには、次の図を見てみましょう。 これを見ると、どのような魔法の技術がこれほど多くのシナリオに適用できるのか、興味が湧いてくるのではないでしょうか。さて、今日はそれを詳しく調べて、魔法の Java エージェントが最下層でどのように動作し、非常に多くの優れたアプリケーションを静かにサポートしているかを見ていきます。 記事の冒頭の例えに戻って、AOP との比較を使用して Java エージェントの概要を理解しましょう。
次に、両方のモードでエージェント プログラムを実装する方法について説明します。 プリメインモードPremain モードでは、メイン プログラムが実行される前にエージェントを実行できます。実装は非常に簡単です。以下では、 2 つのコンポーネントを個別に実装します。 エージェントメイン プログラムが実行される前に文を出力し、プロキシに渡されたパラメータを出力する簡単な関数を記述してみましょう。
エージェント ロジックを記述した後、それを jar ファイルにパッケージ化する必要があります。ここでは、Maven プラグインのパッケージ化方法を直接使用し、パッケージ化する前にいくつかの構成を実行します。
構成されたパッケージ パラメータで、manifestEntries を通じて MANIFEST.MF ファイルに属性を追加します。以下にいくつかのパラメータを示します。
Premain-Class は必須の構成であり、残りのオプションはオプションです。これらはすべてデフォルトで false ですが、通常は推奨されます。これらの機能については後ほど詳しく紹介します。設定が完了したら、mvn コマンドを使用してパッケージ化します。
パッケージ化が完了すると、myAgent-1.0.jar ファイルが生成されます。 jar ファイルを解凍して、生成された MANIFEST.MF ファイルを確認します。 追加された属性がファイルに追加されたことがわかります。この時点でエージェント部分は完成です。エージェントは直接実行できず、他のプログラムにアタッチする必要があるため、メイン プログラムを実装するための新しいプロジェクトが作成されます。 メインプログラムメイン プログラム プロジェクトでは、実行可能なメイン メソッド エントリのみが必要です。
メインプログラムが完成した後に検討する必要があるのは、メインプログラムとエージェントプロジェクトをどのように接続するかです。ここで、-javaagent パラメータを使用して実行中のエージェントを指定できます。コマンドの形式は次のとおりです。
指定できるエージェントの数に制限はありません。各エージェントは指定された順序で実行されます。 2 つのエージェントを同時に実行する場合は、次のコマンドを実行します。
idea でプログラムを実行する例を取り、VM オプションに起動パラメータを追加します。
メイン メソッドを実行し、出力結果を表示します。 実行結果の print ステートメントによると、メイン プログラムを実行する前にエージェントが 2 回連続して実行されたことがわかります。実行エージェントとメインプログラムの実行順序は次の図で表すことができます。 欠陥利便性を提供する一方で、プリメインモードにはいくつかの欠点もあります。たとえば、エージェントの動作中に例外が発生した場合、メインプログラムの起動は失敗します。上記の例のエージェント コードを変更し、手動で例外をスローします。
メインプログラムを再度実行します。 エージェントが例外をスローした後、メイン プログラムが開始されないことがわかります。 premain モードのいくつかの欠陥に対処するために、JDK 1.6 以降では agentmain モードが導入されました。 エージェントメインモードエージェントメインモードは、プレメインのアップグレード版とも言えます。これにより、まずターゲット メイン プログラムの JVM を起動し、次にアタッチ メカニズムを通じて 2 つの JVM を接続できるようになります。 3つの部分に分けて実装します。 エージェントエージェント部分は上記と同じで、シンプルな印刷機能を実現します。
Maven プラグインの設定を変更し、Agent-Class を指定します。
メインプログラムここでは、メイン プログラムを直接起動し、エージェントがロードされるのを待ちます。メイン プログラムでは、System.in を使用してブロックし、メイン プロセスが途中で終了するのを防ぎます。
アタッチ機構プレメイン モードとは異なり、起動パラメータを追加してエージェントとメイン プログラムを接続することはできなくなります。ここでは、com.sun.tools.attach パッケージの VirtualMachine ツール クラスを使用する必要があります。このクラスは JVM 標準仕様ではなく、Sun 自身によって実装されていることに注意してください。使用する前に、依存関係を導入する必要があります。
VirtualMachine は、接続される Java 仮想マシン、つまりプログラムで監視する必要があるターゲット仮想マシンを表します。外部プロセスは、VirtualMachine のインスタンスを使用して、エージェントをターゲットの仮想マシンにロードできます。まず、静的メソッドのattachを見てみましょう。
jvm オブジェクト インスタンスは、attach メソッドを通じて取得できます。ここで渡されるパラメータは、実行中のターゲット仮想マシンのプロセス ID pid です。つまり、attach を使用する前に、起動したメイン プログラムの pid を取得し、jps コマンドを使用してスレッド pid を表示する必要があります。
メイン プログラム AgentmainTest のランタイム pid は 16392 で、仮想マシンの接続に適用されます。
VirtualMachine インスタンスを取得したら、loadAgent メソッドを使用してエージェント クラスを挿入できます。メソッドの最初のパラメーターはエージェントのローカル パスであり、2 番目のパラメーターはエージェントに渡されるパラメーターです。 AttachTest を実行し、メイン プログラム AgentmainTest のコンソールに戻ります。エージェント内のコードが実行されたことがわかります。 このようにして、単純なエージェントメイン モード エージェントが実装されます。 3 つのモジュール間の関係は、次の図で整理できます。 応用ここまで、2 つのモードを実装する方法について簡単に説明しました。しかし、質の高いプログラマーとして、エージェントを使用して単にステートメントを印刷するだけでは満足してはなりません。次に、Java Agent を使用して実用的な操作を行う方法を見てみましょう。 上記の 2 つのモードでは、エージェント部分のロジックはそれぞれ premain メソッドと agentmain メソッドに実装されます。さらに、これら 2 つのメソッドには、シグネチャ内のパラメータに関する厳格な要件があります。 premain メソッドでは、次の 2 つの方法で定義できます。
agentmain メソッドは、次の 2 つの方法で定義できます。
エージェント内に同じシグネチャを持つ 2 つのメソッドがある場合、Instrumentation パラメータを持つメソッドの方が優先順位が高く、JVM によって最初にロードされます。そのインスタンス inst は JVM によって自動的に挿入されます。 Instrumentation を通じてどのような機能が実現できるかを見てみましょう。 計装まず、Instrumentation インターフェースの概要を説明します。この中のメソッドを使用すると、実行時に Java プログラムを操作して、バイトコードの変更、jar パッケージの追加、クラスの置き換えなどの機能を実現できます。これらの機能により、Java はより強力な動的制御および解釈機能を備えることができます。エージェントを作成するプロセスでは、Instrumentation の次の 3 つの方法がより重要であり、よく使用されます。詳しく見てみましょう。 トランスフォーマーを追加addTransformer メソッドを使用すると、クラスがロードされる前にクラスを再定義できます。メソッドの定義を見てみましょう。
ClassFileTransformer は、変換メソッドを 1 つだけ持つインターフェースです。メイン プログラムのメイン メソッドが実行される前に、ロードされた各クラスを transform を通じて 1 回実行する必要があります。コンバーターとも言えます。このメソッドを実装してクラスを再定義できます。使い方の例を見てみましょう。 まず、メイン プログラム プロジェクトに Fruit クラスを作成します。
コンパイルが完了したら、クラス ファイルをコピーして Fruit2.class に名前を変更し、Fruit 内のメソッドを次のように変更します。
メイン プログラムを作成し、メイン プログラム内に Fruit オブジェクトを作成して、その getFruit メソッドを呼び出します。
この時点で実行結果として apple が出力され、その後、premain プロキシ部分の実装が開始されます。 エージェントの premain メソッドで、Instrumentation の addTransformer メソッドを使用してクラスの読み込みをインターセプトします。
FruitTransformer クラスは ClassFileTransformer インターフェースを実装します。クラス部分を変換するロジックは、transform メソッドにあります。
transform メソッドでは、主に次の 2 つの処理が行われます。
エージェント部分をパッケージ化した後、メイン プログラムに起動パラメータを追加します。
メインプログラムを再度実行し、結果を出力します。
この方法では、メイン メソッドが実行される前にクラスが置き換えられます。 クラスを再定義するメソッドの名前からその機能を直感的に理解できます。クラスを再定義するということは、簡単に言えば、指定されたクラスを置き換えることを意味します。メソッドの定義は次のとおりです。
そのパラメータは可変長の ClassDefinition 配列です。 ClassDefinition のコンストラクタを見てみましょう。
ClassDefinition で指定された Class オブジェクトと変更されたバイトコード配列は、簡単に言えば、元のクラスを指定されたクラス ファイル バイトに置き換えます。さらに、redefineClasses メソッドの再定義プロセス中に、ClassDefinition の配列が渡され、クラス間の相互依存関係がある場合の変更を満たすために、この配列の順序でロードされます。 プレメイン プロキシ部分を例にして、その有効性プロセスを見てみましょう。
メインプログラムは上記を直接再利用し、実行後に印刷することができます。
元のクラスが指定したクラス ファイルのバイトに置き換えられ、指定したクラスの置き換えが実現されていることがわかります。 再変換クラスretransformClasses は agentmain モードに適用され、クラスのロード後にクラスを再定義、つまりクラスの再ロードをトリガーできます。まず、このメソッドの定義を見てみましょう。
パラメータ classes は変換する必要のあるクラスの配列であり、可変長パラメータは redefineClasses メソッドと同様にクラス定義を一括変換できることも示しています。 次に、例を通して retransformClasses メソッドの使用方法を見ていきます。エージェントのコードは次のとおりです。
ここで呼び出される addTransformer メソッドの定義を見てみましょう。これは上記とは少し異なります。
ClassFileTransformer コンバーターは、上記の FruitTransformer を引き続き再利用します。新しく追加された 2 番目のパラメータに注目してください。 canRetransform が true の場合、クラスの再定義が許可されることを意味します。このとき、コンバーター ClassFileTransformer の transform メソッドを呼び出すのと同じになり、変換されたクラスのバイトが新しいクラス定義としてロードされます。 メイン プログラム コードでは、クラスが変更されたかどうかを監視するために、無限ループで print ステートメントを継続的に実行します。
最後に、アタッチ API を使用してエージェントをメイン プログラムに挿入します。
実行結果を表示するには、メイン プログラム コンソールに戻ります。 プロキシを挿入した後、print ステートメントが変更され、クラス定義が変更されて再ロードされたことがわかります。 他のこれらの主な方法に加えて、Instrumentation には他の方法もいくつかあります。ここでは、一般的なメソッドの機能を簡単にリストします。
ジャバシスト上記の例では、クラス ファイル内のバイトを直接読み取り、クラスを再定義または変換します。しかし、実際の作業環境では、クラスファイルのバイトコードを動的に変更することが必要になる場合があります。この場合、javassist を使用すると、バイトコード ファイルをより簡単に変更できます。 簡単に言えば、javassist は Java バイトコードを分析、編集、作成するためのクラス ライブラリです。これを使用すると、提供される API を直接呼び出して、クラスの構造をコーディングの形で動的に変更したり生成したりすることができます。基礎となる仮想マシン命令の理解を必要とする ASM などの他のバイトコード フレームワークと比較すると、javassist は非常にシンプルで高速です。 次に、簡単な例を使って、Java エージェントと Javassist を一緒に使用する方法を説明します。まず、javassist 依存関係を導入します。
実現したい機能は、プロキシを介してメソッドの実行時間を計算することです。プレメインプロキシ部分は基本的に以前と同じです。まずコンバーターを追加します:
calculate メソッドでは、javassist を使用してメソッド定義を動的に変更します。
上記のコードでは、主に以下の機能が実装されています。
メイン プログラムは以前のコードを引き続き再利用し、結果を実行して表示し、プロキシで実行時間統計関数を完了します。 この時点で、もう一度反省を通して見てみましょう。
結果を見ると、クラスに新しいメソッドが実際に追加されたことがわかります。 さらに、javassist には、新しいクラスの作成、親クラスの設定、バイトコードの読み書きなど、他の多くの機能があります。特定のシナリオでの使用法を学ぶことができます。 要約する日常業務では、Java Agent を直接使用する場面は多くないかもしれませんが、ホットデプロイメント、監視、パフォーマンス分析などのツールの中で業務システムの片隅に隠れ、静かに大きな役割を果たしている可能性があります。 この記事では、Java エージェントの 2 つのモードから始めて、手動で実装し、そのワークフローを簡単に分析します。ここではいくつかの単純な機能のみが完成していますが、Java エージェントの出現により、プログラムが従来の方法で実行されなくなり、コードに無限の可能性がもたらされたと言わざるを得ません。 |
<<: Kubernetesを導入することで、企業は時間とコストを節約できる
>>: HarmonyOS 分散チャットルームアプリケーション
意味分散ストレージシステムは、インターネットを介して接続された多数の一般的な PC サーバーであり、...
1993年の大ヒット映画『ジュラシック・パーク』で、イアン・マルコム博士は恐竜を復活させるという考え...
エージェント監視とエージェントレス監視は、IT サービス管理業界で常に熱く議論されているトピックです...
中国の高級品オンラインショッピングの巨大な市場需要は、少しの混乱で簡単に変わることはないだろう。現在...
Servarica は 2010 年に設立され、カナダに登録されています。データ センターもカナダに...
最近、一部のネットユーザーがWeiboで、Dedecmsで構築された多くのウェブサイトがハッキングさ...
desivps が LosAngelesVPS を買収してからしばらく経ちました。以前の LosAn...
この記事の著者は、現在主流となっている分散アーキテクチャ、分散アーキテクチャにおける一般的な理論、お...
数日前、licloud の香港 VPS がリリースされました。これは、100Mbps の帯域幅を年間...
ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス企業であれ個人であれ、W...
バランスポイントを見つけるのは非常に難しく、起業家はユーザーの根本的なニーズを把握する必要があります...
導入[[256737]]物理メモリが 8G あり、主に Java サービスを実行している一部のサーバ...
北京時間4月19日、海外メディアの報道によると、あなたのウェブサイトはあなたの「顔」です。実店舗をお...
LightNetwork Computing Limited は最近、香港 HKT データ センター...
背景Kubernetes ゲートウェイおそらく、より正確にはKubernetes Gateway A...