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中运行,并被同一个systemclassloader装载,被同一的安全策略(securitypolicy)和上下文(context)所管理。代理(agent)这个名字有点误导的成分,它与我们一般理解的代理不大一样。javaagent使用起来比较简单。怎样写一个javaagent?只需要实现premain这个方法:publicstaticvoidpremain(StringagentArgs,Instrumentationinst)JDK6中如果找不到上面的这种premain的定义,还会尝试调用下面的这种premain定义:publicstaticvoidpremain(StringagentArgs)
(2)Agent类必须打成jar包,然后里面的META-INF/MAINIFEST.MF,必须包含Premain-Class这个属性。下面是一个MANIFEST.MF的例子:
Manifest-Version:1.0Premain-Class:MyAgent1Created-By:1.6.0_06
然后把MANIFEST.MF加入到你的jar包中。以下是agentjar文件的ManifestAttributes清单: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这个参数的个数是没有限制的,所以可以添加任意多个javaagent。所有的javaagent会按照你定义的顺序执行。例如:
java-javaagent:MyAgent1.jar-javaagent:MyAgent2.jar-jarMyProgram.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-jarMyProgram.jar-javaagent:MyAgent2.jar
MyAgent2都放在了MyProgram.jar后面,所以MyAgent2的premain都不会被执行,所以执行的结果将是:
MyAgent1.premain->MyProgram.main
(6)每一个javaagent都可以接收一个字符串类型的参数,也就是premain中的agentArgs,这个agentArgs是通过javaoption中定义的。例如:
java-javaagent:MyAgent2.jar=thisIsAgentArgs-jarMyProgram.jar
MyAgent2中premain接收到的agentArgs的值将是”thisIsAgentArgs”(不包括双引号)。
(7)参数中的Instrumentation:通过参数中的Instrumentationinst,添加自己定义的ClassFileTransformer,来改变class文件。这里自定义的Transformer实现了transform方法,在该方法中提供了对实际要执行的类的字节码的修改,甚至可以达到执行另外的类方法的地步。例如:写agent类:
packageorg.toy; importjava.lang.instrument.Instrumentation; importjava.lang.instrument.ClassFileTransformer; publicclassPerfMonAgent{ privatestaticInstrumentationinst=null; /** *Thismethodiscalledbeforetheapplication'smain-methodiscalled, *whenthisagentisspecifiedtotheJavaVM. **/ publicstaticvoidpremain(StringagentArgs,Instrumentation_inst){ System.out.println("PerfMonAgent.premain()wascalled."); //Initializethestaticvariablesweusetotrackinformation. inst=_inst; //Setuptheclass-filetransformer. ClassFileTransformertrans=newPerfMonXformer(); System.out.println("AddingaPerfMonXformerinstancetotheJVM."); inst.addTransformer(trans); } }
写ClassFileTransformer类:
packageorg.toy; importjava.lang.instrument.ClassFileTransformer; importjava.lang.instrument.IllegalClassFormatException; importjava.security.ProtectionDomain; importjavassist.CannotCompileException; importjavassist.ClassPool; importjavassist.CtBehavior; importjavassist.CtClass; importjavassist.NotFoundException; importjavassist.expr.ExprEditor; importjavassist.expr.MethodCall; publicclassPerfMonXformerimplementsClassFileTransformer{ publicbyte[]transform(ClassLoaderloader,StringclassName,Class<?>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{ byte[]transformed=null; System.out.println("Transforming"+className); ClassPoolpool=ClassPool.getDefault(); CtClasscl=null; try{ cl=pool.makeClass(newjava.io.ByteArrayInputStream( classfileBuffer)); if(cl.isInterface()==false){ CtBehavior[]methods=cl.getDeclaredBehaviors(); for(inti=0;i<methods.length;i++){ if(methods[i].isEmpty()==false){ doMethod(methods[i]); } } transformed=cl.toBytecode(); } }catch(Exceptione){ System.err.println("Couldnotinstrument"+className +",exception:"+e.getMessage()); }finally{ if(cl!=null){ cl.detach(); } } returntransformed; } privatevoiddoMethod(CtBehaviormethod)throwsNotFoundException, CannotCompileException{ //method.insertBefore("longstime=System.nanoTime();"); //method.insertAfter("System.out.println(\"leave"+method.getName()+"andtime:\"+(System.nanoTime()-stime));"); method.instrument(newExprEditor(){ publicvoidedit(MethodCallm)throwsCannotCompileException{ m.replace("{longstime=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真的很强大,让你很容易的改变类的字节码。
在上面的方法中我通过改变类的字节码,在每个类的方法入口中加入了longstime=System.nanoTime();,在方法的出口加入了System.out.println(“methodClassName.methodName:”+(System.nanoTime()-stime));
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!