博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java.lang.Instrument 代理Agent使用
阅读量:4290 次
发布时间:2019-05-27

本文共 5843 字,大约阅读时间需要 19 分钟。

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://lxggi.baihongyu.com/

你可能感兴趣的文章
网站建设之域名注册和域名备案
查看>>
解决bootstrap时间输入框总被浏览器记住的记录遮挡住的问题
查看>>
git将一个分支完全覆盖另外一个分支如:dev分支代码完全覆盖某一个开发分支
查看>>
elasticsearch7.3版本环境搭建(二)可视化管理后台kibana的安装和配置
查看>>
elasticsearch7.3版本环境搭建(三)可视化管理后台kibana的汉化(设置中文界面)
查看>>
记录一次DDos攻击实战
查看>>
分享一首小诗--《致程序员》
查看>>
为什么百度只抓取了首页而不抓取我的网站的内页的原因分析
查看>>
年薪170万的阿里P8级员工征婚有感--话说阿里真有钱,这员工要求的条件真多
查看>>
又是一年桂花飘香时,祝我们伟大的祖国70年华诞更加繁荣昌盛,祝大家国庆节快乐
查看>>
谷歌浏览器chrome即将在2020年底停止支持flash,我们程序员该怎么办
查看>>
如何将数据采集到 Elasticsearch 服务
查看>>
面试官:mysql表设计要注意什么?
查看>>
一些精致小众网站收集录
查看>>
计算机科学探秘之linux发展史
查看>>
程序员每天早上早来10分钟的好处
查看>>
互联网30年,泡沫如梦,一个个泡沫和风口过后,会是什么样的结局
查看>>
升级centos 6.8 服务器的gcc
查看>>
API网关在微服务架构中的应用,这一篇就够了
查看>>
JVM发生内存溢出的8种原因、及解决办法
查看>>