欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java.lang.Instrument 代理Agent使用详细介绍

程序员文章站 2024-03-09 15:33:59
java.lang.instrument 代理agent使用 java.lang.instrument包是在jdk5引入的,程序员通过修改方法的字节码实现动态修改类代码。...

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));

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!