22

実行時に一部のメソッドの内容を置き換えたいと考えています。

これにはjavassistを使用できることはわかっていますが、拡張したいクラスがシステムclassLoaderによってすでにロードされているため、機能しません。

実行時にメソッドの内容を置き換えるにはどうすればよいですか? クラスをアンロードする必要がありますか? どうやってやるの ?可能であることがわかりましたが、それを行う方法がわかりませんでした。

可能であれば、これに外部ライブラリを使用することは避けたいと思います。自分でコーディングしたいと思います。

詳細情報: - 拡張したいクラスがフレームワーク (jar ファイル) に含まれている - 私のコードは実際にはこのフレームワークのプラグインです - 私のプラグインが実行されるフレームワークには独自のclassLoaderがありますが、このclassLoaderはありません独自のクラスをロードします (システム クラス ローダーに委譲します) - 私が使用しているフレームワークはPlayです。

ご協力ありがとうございました !

4

2 に答える 2

8

Javaassistだけでなく、他のバイトコード エンジニアリング ライブラリでも実行できますその魔法はJava Attach APIにあります。これにより、プログラムを実行中の JVM にアタッチ (およびロードされたクラスを変更) できます。

これはcom.sun.tools.attachパッケージに含まれており、その名前が示すように、Oracle JVM に固有のものです。それにもかかわらず、JDK ツールは「実行中の JVM に接続する」機能をサポートするためにそれを好みjstack、使用するため、ここにとどまると言っても過言ではありません。jmap

Attach API に関するドキュメントはかなり説明的であり、このOracle ブログ投稿では、実行時にエージェントをアタッチする方法を示しています。一般に、それは次のように要約されます。

  • et alを使用して、再変換プログラムを「通常の」-javaagent方法で作成しますpremain
  • premainに名前を変更agentmain
  • Agent-Classエージェント クラスを含み、エージェント ( - をagentmain含む) クラスを指すマニフェストを持つ一時 JAR ファイルを作成し、次のようにCan-Retransform-Classes設定します。true
  • ターゲット JVM (潜在的に同じプロセス) の PID を取得し、それに一時的な jar をアタッチします。

ありがたいことに、API はユーザー側で多くの作業を行わなくてもこれを行うことができますが、実行時に JAR 生成を行う場合、エージェントに必要なすべてのクラスをパッケージ化するのは少し難しいかもしれません。

実行時にプロファイラーをアタッチするデモ エージェントを含めたいと思っていましたが、長すぎて投稿できませんでした。それにもかかわらず、私はそれをGithub repoに入れました。

このアプローチの注意点tools.jarは、プログラムが JDK に同梱され、JRE には存在しない に依存するようになることです。tools.jarこれは、アプリケーションに同梱 (またはアプリケーションに抽出) することで回避できますがattach、Attach API が必要とするネイティブ ライブラリをアプリケーションに提供する必要があります。上記のリンク先のリポジトリにあるすべてのプラットフォーム用のライブラリを含めましたが、自分で入手することもできます。

ユースケースによっては、これが理想的な場合とそうでない場合があります。しかし、それは確かに機能します!


これは質問では明確ではありませんが、実行時に自分のクラスと完全に「ホットスワップ」したい場合は、バイトコード操作ライブラリを使用する必要はありません。代わりに、クラスを個別にコンパイルして (パッケージ、クラス名などを同じにするtransform) 、ターゲット クラスで が呼び出されたときに新しいクラスのバイトを返すだけです。

于 2012-09-06T20:16:36.490 に答える
4

通常のClassLoaderは、定義されたクラスの定義解除または変更をサポートしていません。そのため、フレームワークがそのようなカスタマイズのためのフックを提供しない限り、プラグインはフレームワークの動作を変更できません。

親クラスローダーから一部のクラスを非表示にするカスタムクラスローダーを作成し、代わりにそれらを再定義して、必要なインストルメンテーションを追加できます。ただし、フレームワークはプラグインの前にロードされ、独自のクラスローダーを使用してクラスを解決します。そのため、インストルメントされていないバージョンのクラスを引き続き使用します。

これを回避する唯一の合理的な方法(私が考えることができる)は、最初にそこにいることです。コードが最初に起動された場合、フレームワークのロードに使用されるクラスローダーを導入できます。ただし、これは、フレームワークのラッパーとしてコードをチェーンに取り込む方法が必要になることを意味します。これがあなたの状況で実行可能かどうかわからない。

コメントへの返信で更新:
いくつかのクラスを支援するクラスローダーを作成するには、そのloadClassメソッドをオーバーライドする必要があります。ライセンスでGPLコードの使用が許可されている場合は、OpenJDKがデフォルトの実装でこれをどのように行うかを確認できます。非表示にしたくないクラスについては、親クラスローダーに従うだけです。

親バージョンを非表示にした後も、クラスを変更する必要があります。おそらく、BCELクラスローダーが役に立ちます。または、変更されたバージョンを含むjarファイルからクラスをロードします。またはこのようなもの。

于 2012-07-31T21:56:47.300 に答える