java.lang.Instrument 代理 Agent 使用详细介绍
这里有新鲜出炉的 Java 函数式编程, 程序狗速度看过来!
Java 程序设计语言
java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言, 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台 (即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se)) 的总称
这篇文章主要介绍了 java.lang.Instrument 代理 Agent 使用详细介绍的相关资料, 附有实例代码, 帮助大家学习参考, 需要的朋友可以参考下
java.lang.Instrument 代理 Agent 使用
java.lang.Instrument 包是在 JDK5 引入的, 程序员通过修改方法的字节码实现动态修改类代码这通常是在类的 main 方法调用之前进行预处理的操作, 通过 java 指定该类的代理类来实现在类的字节码载入 JVM 前会调用 ClassFileTransformer 的 transform 方法, 从而实现修改原类方法的功能, 实现 AOP, 这个的好处是不会像动态代理或者 CGLIB 技术实现 AOP 那样会产生一个新类, 也不需要原类要有接口
(1) 代理 (agent) 是在你的 main 方法前的一个拦截器 (interceptor), 也就是在 main 方法执行之前, 执行 agent 的代码 agent 的代码与你的 main 方法在同一个 JVM 中运行, 并被同一个 system classloader 装载, 被同一的安全策略 (security policy)和上下文 (context)所管理 代理 (agent) 这个名字有点误导的成分, 它与我们一般理解的代理不大一样 java agent 使用起来比较简单怎样写一个 java agent? 只需要实现 premain 这个方法: public static void premain(String agentArgs, Instrumentation inst) JDK 6 中如果找不到上面的这种 premain 的定义, 还会尝试调用下面的这种 premain 定义: public static void premain(String agentArgs)
(2)Agent 类必须打成 jar 包, 然后里面的 META-INF/MAINIFEST.MF, 必须包含 Premain-Class 这个属性 下面是一个 MANIFEST.MF 的例子:
Manifest-Version: 1.0 Premain-Class:MyAgent1 Created-By:1.6.0_06
然后把 MANIFEST.MF 加入到你的 jar 包中以下是 agent jar 文件的 Manifest Attributes 清单: Premain-Class 如果 JVM 启动时指定了代理, 那么此属性指定代理类, 即包含 premain 方法的类如果 JVM 启动时指定了代理, 那么此属性是必需的如果该属性不存在, 那么 JVM 将中止注: 此属性是类名, 不是文件名或路径 Agent-Class 如果实现支持 VM 启动之后某一时刻启动代理的机制, 那么此属性指定代理类 即包含 agentmain 方法的类 此属性是必需的, 如果不存在, 代理将无法启动注: 这是类名, 而不是文件名或路径 Boot-Class-Path 设置引导类加载器搜索的路径列表路径表示目录或库 (在许多平台上通常作为 JAR 或 zip 库被引用) 查找类的特定于平台的机制失败后, 引导类加载器会搜索这些路径按列出的顺序搜索路径列表中的路径由一个或多个空格分开路径使用分层 URI 的路径组件语法如果该路径以斜杠字符 (/) 开头, 则为绝对路径, 否则为相对路径相对路径根据代理 JAR 文件的绝对路径解析忽略格式不正确的路径和不存在的路径如果代理是在 VM 启动之后某一时刻启动的, 则忽略不表示 JAR 文件的路径此属性是可选的 Can-Redefine-Classes 布尔值 (true 或 false, 与大小写无关) 是否能重定义此代理所需的类 true 以外的值均被视为 false 此属性是可选的, 默认值为 false Can-Retransform-Classes 布尔值 (true 或 false, 与大小写无关) 是否能重转换此代理所需的类 true 以外的值均被视为 false 此属性是可选的, 默认值为 false Can-Set-Native-Method-Prefix 布尔值 (true 或 false, 与大小写无关) 是否能设置此代理所需的本机方法前缀 true 以外的值均被视为 false 此属性是可选的, 默认值为 false
(3)所有的这些 Agent 的 jar 包, 都会自动加入到程序的 classpath 中所以不需要手动把他们添加到 classpath 除非你想指定 classpath 的顺序
(4)一个 java 程序中 - javaagent 这个参数的个数是没有限制的, 所以可以添加任意多个 java agent 所有的 java agent 会按照你定义的顺序执行 例如:
java -javaagent:MyAgent1.jar -javaagent:MyAgent2.jar -jar MyProgram.jar
假设 MyProgram.jar 里面的 main 函数在 MyProgram 中 MyAgent1.jar, MyAgent2.jar, 这 2 个 jar 包中实现了 premain 的类分别是 MyAgent1, MyAgent2 程序执行的顺序将会是:
MyAgent1.premain -> MyAgent2.premain -> MyProgram.main
(5)另外, 放在 main 函数之后的 premain 是不会被执行的, 例如:
java -javaagent:MyAgent1.jar -jar MyProgram.jar -javaagent:MyAgent2.jar
MyAgent2 都放在了 MyProgram.jar 后面, 所以 MyAgent2 的 premain 都不会被执行, 所以执行的结果将是:
MyAgent1.premain -> MyProgram.main
(6)每一个 java agent 都可以接收一个字符串类型的参数, 也就是 premain 中的 agentArgs, 这个 agentArgs 是通过 java option 中定义的例如:
java -javaagent:MyAgent2.jar=thisIsAgentArgs -jar MyProgram.jar
MyAgent2 中 premain 接收到的 agentArgs 的值将是 thisIsAgentArgs (不包括双引号)
(7)参数中的 Instrumentation: 通过参数中的 Instrumentation inst, 添加自己定义的 ClassFileTransformer, 来改变 class 文件这里自定义的 Transformer 实现了 transform 方法, 在该方法中提供了对实际要执行的类的字节码的修改, 甚至可以达到执行另外的类方法的地步例如: 写 agent 类:
- package org.toy;
- import java.lang.instrument.Instrumentation;
- import java.lang.instrument.ClassFileTransformer;
- public class PerfMonAgent {
- private static Instrumentation inst = null;
- /**
- * This method is called before the application's main-method is called,
- * when this agent is specified to the Java VM.
- **/
- public static void premain(String agentArgs, Instrumentation _inst) {
- System.out.println("PerfMonAgent.premain() was called.");
- // Initialize the static variables we use to track information.
- inst = _inst;
- // Set up the class-file transformer.
- ClassFileTransformer trans = new PerfMonXformer();
- System.out.println("Adding a PerfMonXformer instance to the JVM.");
- inst.addTransformer(trans);
- }
- }
写 ClassFileTransformer 类:
- package org.toy;
- import java.lang.instrument.ClassFileTransformer;
- import java.lang.instrument.IllegalClassFormatException;
- import java.security.ProtectionDomain;
- import javassist.CannotCompileException;
- import javassist.ClassPool;
- import javassist.CtBehavior;
- import javassist.CtClass;
- import javassist.NotFoundException;
- import javassist.expr.ExprEditor;
- import javassist.expr.MethodCall;
- public class PerfMonXformer implements ClassFileTransformer {
- public byte[] transform(ClassLoader loader, String className, Class < ?>classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
- byte[] transformed = null;
- System.out.println("Transforming" + className);
- ClassPool pool = ClassPool.getDefault();
- CtClass cl = null;
- try {
- cl = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
- if (cl.isInterface() == false) {
- CtBehavior[] methods = cl.getDeclaredBehaviors();
- for (int i = 0; i < methods.length; i++) {
- if (methods[i].isEmpty() == false) {
- doMethod(methods[i]);
- }
- }
- transformed = cl.toBytecode();
- }
- } catch(Exception e) {
- System.err.println("Could not instrument" + className + ", exception :" + e.getMessage());
- } finally {
- if (cl != null) {
- cl.detach();
- }
- }
- return transformed;
- }
- private void doMethod(CtBehavior method) throws NotFoundException,
- CannotCompileException {
- // method.insertBefore("long stime = System.nanoTime();");
- // method.insertAfter("System.out.println(\"leave "+method.getName()+" and time:\"+(System.nanoTime()-stime));");
- method.instrument(new ExprEditor() {
- public void edit(MethodCall m) throws CannotCompileException {
- m.replace("{ long stime = System.nanoTime(); $_ = $proceed($); System.out.println(\"" + m.getClassName() + "." + m.getMethodName() + ":\"+(System.nanoTime()-stime));}");
- }
- });
- }
- }
上面两个类就是 agent 的核心了, jvm 启动时并会在应用加载前会调用 PerfMonAgent.premain, 然后 PerfMonAgent.premain 中实例化了一个定制的 ClassFileTransforme 即 PerfMonXformer, 并通过 inst.addTransformer(trans); 把 PerfMonXformer 的实例加入 Instrumentation 实例(由 jvm 传入), 这就使得应用中的类加载的时候, PerfMonXformer.transform 都会被调用, 你在此方法中可以改变加载的类, 真的有点神奇, 为了改变类的字节码, 我使用了 jboss 的 javassist, 虽然你不一定要这么用, 但 jboss 的 javassist 真的很强大, 让你很容易的改变类的字节码
在上面的方法中我通过改变类的字节码, 在每个类的方法入口中加入了 long stime = System.nanoTime();, 在方法的出口加入了 System.out.println(methodClassName.methodName:+(System.nanoTime()-stime));
来源: http://www.phperz.com/article/18/0208/359300.html