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

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

(0)

相关推荐

  • Java使用agent实现main方法之前的实例详解

    Java使用agent实现main方法之前的实例详解 创建Agent项目 PreMainExecutor 类,在main方法之前执行此方法 public class PreMainExecutor { public static void premain(String agentOps, Instrumentation inst){ System.out.println("premain execute.........."); } } META-INF/MANIFEST.MF Man

  • Java Agent入门学习之动态修改代码

    前言 最近用了一下午总算把Java agent给跑通了,本篇文章记录一下具体的操作步骤,以免遗忘.下面话不多说,来一起看看详细的介绍: 通过java agent可以动态修改代码(替换.修改类的定义),进行AOP. 目标: 为所有添加@ToString注解的类实现默认的toString方法 需要两个程序,一个是用来测试的程序,一个agent用于修改代码. 1. 测试程序 被测试的程序包括: - ToString.Java - Foo.java - Main.java 具体代码如下: ToStrin

  • java 解析user-agent 信息

    解析http user-agent信息,使用uasparser-0.6.1.jar和jregex-1.2_01.jar两个包 import cz.mallat.uasparser.OnlineUpdater; import cz.mallat.uasparser.UASparser; import cz.mallat.uasparser.UserAgentInfo; import java.io.IOException; /** * Created by Edward on 2016/7/1.

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

    java.lang.Instrument 代理Agent使用 java.lang.Instrument包是在JDK5引入的,程序员通过修改方法的字节码实现动态修改类代码.这通常是在类的main方法调用之前进行预处理的操作,通过java指定该类的代理类来实现.在类的字节码载入JVM前会调用ClassFileTransformer的transform方法,从而实现修改原类方法的功能,实现AOP,这个的好处是不会像动态代理或者CGLIB技术实现AOP那样会产生一个新类,也不需要原类要有接口. (1)

  • Java JDK动态代理的基本原理详细介绍

    JDK动态代理详解 本文主要介绍JDK动态代理的基本原理,让大家更深刻的理解JDK Proxy,知其然知其所以然.明白JDK动态代理真正的原理及其生成的过程,我们以后写JDK Proxy可以不用去查demo,就可以徒手写个完美的Proxy.下面首先来个简单的Demo,后续的分析过程都依赖这个Demo去介绍,例子采用JDK1.8运行. JDK Proxy HelloWorld package com.yao.proxy; /** * Created by robin */ public inter

  • Java中的main函数的详细介绍

    Java中的main函数的详细介绍 JAVA中的主函数是我们再熟悉不过的了,相信每个学习过JAVA语言的人都能够熟练地写出这个程序的入口函数,但对于主函数为什么这么写,其中的每个关键字分别是什么意思,可能就不是所有人都能轻松地答出来的了.我也是在学习中碰到了这个问题,通过在网上搜索资料,并加上自己的实践终于有了一点心得,不敢保留,写出来与大家分享. 主函数的一般写法如下: public static void main(String[] args){-} 下面分别解释这些关键字的作用: (1)p

  • Java 类加载过程与类加载器详细介绍

    目录 1. 类加载过程 加载 链接 初始化 2. 类加载器 启动类加载器 扩展类加载器 应用类加载器 自定义类加载器 双亲委派模型 1. 类加载过程 加载 通过类的全限定名(包名 + 类名),获取到类的.class文件,加载到元空间. 链接 验证:检验.class文件的安全性 准备:为静态类型变量分配内存并设置默认值 解析:将常量池内的符号引用转换为直接引用,符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载 初始化 执行类的构造器方法init()的过程,若该类

  • 反向代理缓存的详细介绍

    反向代理缓存的详细介绍  传统代理: 用户隐藏在代理服务器之后.代理服务器工作在应用层,它只转发它支持的协议的数据.    反向代理(Reverse Proxy): 这种机制是Web服务器隐藏在代理服务器之后,实现这种机制的服务器称作反向代理服务器(Reverse Proxy Server).此时,Web服务器成为后端服务器,反向代理服务器称为前端服务器. 引入反向代理服务器的目的之一就是基于缓存的加速.我们可以将内容缓存在反向代理服务器上,所有缓存机制的实现仍然采用HTTP/1.1协议. 反向

  • Java中的同步与异步详细介绍

    进程同步用来实现程序并发执行时候的可再现性. 一.进程同步及异步的概念 1.进程同步:就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事.就像早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭.按照这个定义,其实绝大多数函数都是同步调用(例如sin,isdigit等).但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务.最常见的例子就是 sendmessage.该函数发送一个消

  • Java中批处理框架spring batch详细介绍

    spring batch简介 spring batch是spring提供的一个数据处理框架.企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作. 这些业务运营包括: 无需用户交互即可最有效地处理大量信息的自动化,复杂处理. 这些操作通常包括基于时间的事件(例如月末计算,通知或通信). 在非常大的数据集中重复处理复杂业务规则的定期应用(例如,保险利益确定或费率调整). 集成从内部和外部系统接收的信息,这些信息通常需要以事务方式格式化,验证和处理到记录系统中. 批处理用于每天为企业处

  • Java关于List集合去重方案详细介绍

    1 常规去重 碰到List去重的问题,除了遍历去重,我们常常想到利用Set集合不允许重复元素的特点,通过List和Set互转,来去掉重复元素. // 遍历后判断赋给另一个List集合,保持原来顺序 public static void ridRepeat1(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new A

  • Java 动态代理与CGLIB详细介绍

    静态代理模式 因为需要对一些函数进行二次处理,或是某些函数不让外界知道时,可以使用代理模式,通过访问第三方,间接访问原函数的方式,达到以上目的. interface Hosee{ String sayhi(); } class Hoseeimpl implements Hosee{ @Override public String sayhi() { return "Welcome oschina hosee's blog"; } } class HoseeProxy implement

  • Java中的静态绑定和动态绑定详细介绍

    一个Java程序的执行要经过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言.当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法还是子类的重写方法呢,这应该是我们在初学Java时遇到的问题.这里首先我们将确定这种调用何种方法实现或者变量的操作叫做绑定. 在Java中存在两种绑定方式,一种为静态绑定,又称作早期绑定.另一种就是动态绑定,亦称为后期绑定. 区别对比 1.静态绑定发生在编译时期,动态绑定发生在运行时 2.使用private或stati

随机推荐