InstrumentationInstrumentation是JDK1.5的新特性。通过java.lang.instrument包,可以实现一个独立于应用程序的Agent程序,可以替换和修改类定义。通过这样的功能,开发者可以实现灵活的运行时虚拟机监控和Java类增强,实际上提供了虚拟机级别的AOP实现。下面的JavaAgentDemo介绍了通过java.lang.instrument编写Agent的一般方法。实现Agent启动方法JavaAgent支持在目标JVM启动时加载,也支持在目标JVM运行时加载。这两种不同的加载方式使用不同的入口函数。如果需要在目标JVM启动时加载Agent,可以选择Implementthefollowingmethod:[1]publicstaticvoidpremain(StringagentArgs,Instrumentationinst);[2]publicstaticvoidpremain(StringagentArgs);JVM会先搜索[1],如果找不到[1],再搜索[2]。如果要在目标JVM运行时加载Agent,需要实现如下方法:[1]publicstaticvoidagentmain(StringagentArgs,Instrumentationinst);[2]publicstaticvoidagentmain(StringagentArgs);前两组方法参数agentArgs是随--javaagent一起传递的程序参数。如果这个字符串表示多个参数,则需要自己解析这些参数。Instrumentation是Instrumentation类型的对象,由JVM自动传入。我们可以使用此参数进行类增强和其他操作。publicclassAgent{publicstaticvoidpremain(StringagentArgs,Instrumentationinstrumentation){System.out.println("Agentpremainstart...");System.out.println("代理参数:"+agentArgs);instrumentation.addTransformer(newAppInitTramsformer());}publicstaticvoidagentmain(StringagentArgs,Instrumentationinstrumentation){System.out.println("Agentagentmainstart...");System.out.println("代理参数:"+agentArgs);instrumentation.addTransformer(newAppInitTramsformer());}}实现Transformer在Instrumentation接口中,通过addTransformer方法添加一个类transformer,类transformer由ClassFileTransformer接口实现。ClassFileTransformer接口中唯一的方法transform用于实现类转换。当加载类时,会调用transform方法进行类转换。在运行时,我们可以通过Instrumentation的redefineClasses方法重新定义类。实现时注意不要增删改名字段和方法,改变方法的签名或类的继承关系。可以使用ASM、javassist、bytebuddy等工具修改字节码。在以下示例中,使用了javassist。公共类AppInitTramsformer实现ClassFileTransformer{privatestaticfinalStringINJECTED_CLASS="com.github.godshang.agent.Agent";@Overridepublicbyte[]transform(ClassLoaderloader,StringclassName,Class>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)抛出IllegalClassFormatException{StringrealClassName=className.replace("/",".");if(realClassName.equals(INJECTED_CLASS)){System.out.println("摘抄到的类名:"+realClassName);CtClassctClass;尝试{ClassPoolclassPool=ClassPool.getDefault();ctClass=classPool.get(realClassName);CtMethod[]declaredMethods=ctClass.getDeclaredMethods();for(CtMethodmethod:declaredMethods){System.out.println(method.getName()+"方法被截取");方法d.addLocalVariable("时间",CtClass.longType);method.insertBefore("System.out.println(\"---开始执行---\");");method.insertBefore("time=System.currentTimeMillis();");method.insertAfter("System.out.println(\"---结束执行---\");");method.insertAfter("System.out.println(\"运行消耗时间:\"+(System.currentTimeMillis()-time));");}返回ctClass.toBytecode();}catch(Throwablee){System.out.println(e.getMessage());e.printStackTrace();}}返回新字节[0];}}写在MANIFEST.MF文件中的Agent怎么才能被外部应用程序知道呢?依赖MANIFEST.MF文件文件具体路径为:src/main/resources/META-INF/MANIFEST.MF。Manifest-Version:1.0Premain-Class:com.github.godshang.agent.AgentAgent-Class:com.github.godshang.agent.AgentCan-Redefine-Classes:trueCan-Retransform-Classes:trueMANIFEST.MF文件Premain-Class对应premain入口是在启动时加载Agent,Agent-Class对应agentmain入口,是在运行时加载Agent。Can-Redefine-Classes表示是否重新定义这个代理需要的类,默认值为false。Can-Retransform-Classes表示是否可以重新转换此代理所需的类,默认值为false。除了直接创建MANIFEST.MF文件外,还可以在Maven中配置编译打包插件。
