Java エージェントは、文字通り Java エージェントと訳され、Java プローブ テクノロジとも呼ばれます。
Java エージェント このテクノロジーは JDK1.5 で導入され、実行時に Java バイトコードを動的に変更できます。 Java のクラスは、JVM によって実行されるバイトコードを形成するためにコンパイルされます。JVM は、これらのバイトコードを実行する前にこれらのバイトコードの情報を取得し、バイトコード コンバータを通じてこれらのバイトコードを変更してプロセスを完了します。
Java エージェントは、独立して実行できない jar パッケージであり、ターゲット プログラムに接続された JVM プロセスを通じて動作します。起動時に、ターゲット プログラムの起動パラメータに -javaagent パラメータを追加して、ClassFileTransformer
バイトコード コンバータを追加するだけです。これは、main メソッドの前にインターセプタを追加するのと同じです。
Java エージェントには主に次の機能があります。
Java エージェント
ロード前に Java バイトコードをインターセプトして変更できます;
Eclipse、IntelliJ IDEA などの IDE のデバッグ機能、
主にクラスをロードする前にバイトコードをインターセプトして変更します
#JVMTI
JVM ツール インターフェイス
で、ユーザー拡張のために JVM によって公開されるインターフェイスのコレクションです。JVMTI はイベント駆動型で、JVM が特定のロジックを実行するたびにトリガーされます。一部のイベントのコールバック インターフェイスこれらのコールバック インターフェイスを通じて、ユーザーは自分自身を拡張できますJVMTI は、主流の Java 仮想マシンにデバッガー、プロファイラー、モニター、スレッド アナライザーなどのツールを実装するための統合基盤です。実装されている
#JVMTIAgent は、JVMTI によって公開されているいくつかのインターフェイスを使用して、実行したいが通常の状況では実行できないことを実行する動的ライブラリです。通常の動的ライブラリと区別するために、
通常、次のように 1 つ以上の関数を実装します。
Agent_OnAttach
関数。エージェントが起動時にロードされない場合は、最初にターゲット プロセスにアタッチします。その後、ロード コマンドを対応するターゲット プロセスに送信してロードします。Agent_OnAttach 関数Agent_OnUnload
関数は、ロード プロセス中に呼び出されます。エージェントがアンインストールされるとき、これは
#javaagent
を間接的にロードすることもできます。実行時の動的ロードは JVM のアタッチ メカニズムに依存しており、エージェント はロードを送信することによってロードされます。
Java Agent case使用します。 print メソッド 実行時間を例に挙げると、これは
まず、合理化された Maven プロジェクトを構築する必要があります。このプロジェクトでは、プラグイン エージェントの実装用とテスト ターゲット プログラムの実装用の 2 つの Maven サブプロジェクトを構築します。
2 つのプロジェクトが共通の依存関係を持つパッケージを親アプリケーションにインポートします
<dependencies> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency> </dependencies>
最初にビルドしますテスト対象プログラム
// 启动类 public class APPMain { public static void main(String[] args) { System.out.println("APP 启动!!!"); AppInit.init(); } } // 模拟的应用初始化的类 public class AppInit { public static void init() { try { System.out.println("APP初始化中..."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
然后我们启动程序,测试是否能正常执行,程序正常执行之后,我们开始构建探针程序
探针程序中我们需要编写,改变原有class的Transformer
,通过自定义的Transformer类完成输出方法执行时间的功能,
首先构检Agent程序的入口
public class RunTimeAgent { public static void premain(String arg, Instrumentation instrumentation) { System.out.println("探针启动!!!"); System.out.println("探针传入参数:" + arg); instrumentation.addTransformer(new RunTimeTransformer()); } }
这里每个类加载的时候都会走这个方法,我们可以通过className进行指定类的拦截,然后借助javassist这个工具,进行对Class的处理,这里的思想和反射类似,但是要比反射功能更加强大,可以动态修改字节码。
javassist是一个开源的分析、编辑和创建Java字节码的类库。
import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class RunTimeTransformer implements ClassFileTransformer { private static final String INJECTED_CLASS = "com.zhj.test.init.AppInit"; @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { String realClassName = className.replace("/", "."); if (realClassName.equals(INJECTED_CLASS)) { System.out.println("拦截到的类名:" + realClassName); CtClass ctClass; try { // 使用javassist,获取字节码类 ClassPool classPool = ClassPool.getDefault(); ctClass = classPool.get(realClassName); // 得到该类所有的方法实例,也可选择方法,进行增强 CtMethod[] declaredMethods = ctClass.getDeclaredMethods(); for (CtMethod method : declaredMethods) { System.out.println(method.getName() + "方法被拦截"); method.addLocalVariable("time", CtClass.longType); method.insertBefore("System.out.println(\"---开始执行---\");"); method.insertBefore("time = System.currentTimeMillis();"); method.insertAfter("System.out.println(\"---结束执行---\");"); method.insertAfter("System.out.println(\"运行耗时: \" + (System.currentTimeMillis() - time));"); } return ctClass.toBytecode(); } catch (Throwable e) { //这里要用Throwable,不要用Exception System.out.println(e.getMessage()); e.printStackTrace(); } } return classfileBuffer; } }
我们需要在Maven中配置,编译打包的插件,这样我们就可以很轻松的借助Maven生成Agent的jar包
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <!-- 指定maven编译的jdk版本。若不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <!--自动添加META-INF/MANIFEST.MF --> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Menifest-Version>1.0</Menifest-Version> <Premain-Class>com.zhj.agent.RunTimeAgent</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
否则我们需要在resources下创建META-INF/MANIFEST.MF文件,文件内容如下,我们可以看出这个与Maven中的配置是一致的,然后通过配置编译器,借助编译器打包成jar包,需指定该文件
Manifest-Version: 1.0 Premain-Class: com.zhj.agent.RunTimeAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
告示文件MANIFEST.MF参数说明:
Manifest-Version
文件版本
Premain-Class
包含 premain 方法的类(类的全路径名)main方法运行前代理
Agent-Class
包含 agentmain 方法的类(类的全路径名)main开始后可以修改类结构
Boot-Class-Path
设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。(可选)
Can-Redefine-Classes true
表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes true
表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix true
表示能设置此代理所需的本机方法前缀,默认值为 false(可选)
最后通过Maven生成Agent的jar包,然后修改测试目标程序的启动器,添加JVM参数即可
参数示例:-javaagent:F:\code\myCode\agent-test\runtime-agent\target\runtime-agent-1.0-SNAPSHOT.jar=hello
最终效果:
以上がJavaエージェントの使用方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。