java.lang.Instrument 代理Agent使用詳細(xì)介紹
java.lang.Instrument 代理Agent使用
java.lang.Instrument包是在JDK5引入的,程序員通過修改方法的字節(jié)碼實現(xiàn)動態(tài)修改類代碼。這通常是在類的main方法調(diào)用之前進(jìn)行預(yù)處理的操作,通過java指定該類的代理類來實現(xiàn)。在類的字節(jié)碼載入JVM前會調(diào)用ClassFileTransformer的transform方法,從而實現(xiàn)修改原類方法的功能,實現(xiàn)AOP,這個的好處是不會像動態(tài)代理或者CGLIB技術(shù)實現(xiàn)AOP那樣會產(chǎn)生一個新類,也不需要原類要有接口。
(1) 代理 (agent) 是在你的main方法前的一個攔截器 (interceptor),也就是在main方法執(zhí)行之前,執(zhí)行agent的代碼。 agent的代碼與你的main方法在同一個JVM中運(yùn)行,并被同一個system classloader裝載,被同一的安全策略 (security policy)和上下文 (context)所管理。 代理(agent)這個名字有點誤導(dǎo)的成分,它與我們一般理解的代理不大一樣。java agent使用起來比較簡單。怎樣寫一個java agent? 只需要實現(xiàn)premain這個方法: public static void premain(String agentArgs, Instrumentation inst) JDK 6 中如果找不到上面的這種premain的定義,還會嘗試調(diào)用下面的這種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 如果實現(xiàn)支持 VM 啟動之后某一時刻啟動代理的機(jī)制,那么此屬性指定代理類。 即包含 agentmain 方法的類。 此屬性是必需的,如果不存在,代理將無法啟動。注:這是類名,而不是文件名或路徑。 Boot-Class-Path 設(shè)置引導(dǎo)類加載器搜索的路徑列表。路徑表示目錄或庫(在許多平臺上通常作為 JAR 或 zip 庫被引用)。查找類的特定于平臺的機(jī)制失敗后,引導(dǎo)類加載器會搜索這些路徑。按列出的順序搜索路徑。列表中的路徑由一個或多個空格分開。路徑使用分層 URI 的路徑組件語法。如果該路徑以斜杠字符(“/”)開頭,則為絕對路徑,否則為相對路徑。相對路徑根據(jù)代理 JAR 文件的絕對路徑解析。忽略格式不正確的路徑和不存在的路徑。如果代理是在 VM 啟動之后某一時刻啟動的,則忽略不表示 JAR 文件的路徑。此屬性是可選的。 Can-Redefine-Classes 布爾值(true 或 false,與大小寫無關(guān))。是否能重定義此代理所需的類。true 以外的值均被視為 false。此屬性是可選的,默認(rèn)值為 false。 Can-Retransform-Classes 布爾值(true 或 false,與大小寫無關(guān))。是否能重轉(zhuǎn)換此代理所需的類。true 以外的值均被視為 false。此屬性是可選的,默認(rèn)值為 false。 Can-Set-Native-Method-Prefix 布爾值(true 或 false,與大小寫無關(guān))。是否能設(shè)置此代理所需的本機(jī)方法前綴。true 以外的值均被視為 false。此屬性是可選的,默認(rèn)值為 false。
(3)所有的這些Agent的jar包,都會自動加入到程序的classpath中。所以不需要手動把他們添加到classpath。除非你想指定classpath的順序。
(4)一個java程序中-javaagent這個參數(shù)的個數(shù)是沒有限制的,所以可以添加任意多個java agent。所有的java agent會按照你定義的順序執(zhí)行。 例如:
java -javaagent:MyAgent1.jar -javaagent:MyAgent2.jar -jar MyProgram.jar
假設(shè)MyProgram.jar里面的main函數(shù)在MyProgram中。MyAgent1.jar, MyAgent2.jar, 這2個jar包中實現(xiàn)了premain的類分別是MyAgent1, MyAgent2 程序執(zhí)行的順序?qū)牵?/p>
MyAgent1.premain -> MyAgent2.premain -> MyProgram.main
(5)另外,放在main函數(shù)之后的premain是不會被執(zhí)行的,例如:
java -javaagent:MyAgent1.jar -jar MyProgram.jar -javaagent:MyAgent2.jar
MyAgent2 都放在了MyProgram.jar后面,所以MyAgent2的premain都不會被執(zhí)行,所以執(zhí)行的結(jié)果將是:
MyAgent1.premain -> MyProgram.main
(6)每一個java agent 都可以接收一個字符串類型的參數(shù),也就是premain中的agentArgs,這個agentArgs是通過java option中定義的。例如:
java -javaagent:MyAgent2.jar=thisIsAgentArgs -jar MyProgram.jar
MyAgent2中premain接收到的agentArgs的值將是”thisIsAgentArgs” (不包括雙引號)。
(7)參數(shù)中的Instrumentation:通過參數(shù)中的Instrumentation inst,添加自己定義的ClassFileTransformer,來改變class文件。這里自定義的Transformer實現(xiàn)了transform方法,在該方法中提供了對實際要執(zhí)行的類的字節(jié)碼的修改,甚至可以達(dá)到執(zhí)行另外的類方法的地步。例如: 寫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啟動時并會在應(yīng)用加載前會調(diào)用 PerfMonAgent.premain,然后PerfMonAgent.premain中實例化了一個定制的ClassFileTransforme即 PerfMonXformer,并通過inst.addTransformer(trans);把PerfMonXformer的實例加入Instrumentation實例(由jvm傳入),這就使得應(yīng)用中的類加載的時候, PerfMonXformer.transform都會被調(diào)用,你在此方法中可以改變加載的類,真的有點神奇,為了改變類的字節(jié)碼,我使用了jboss的javassist,雖然你不一定要這么用,但jboss的javassist真的很強(qiáng)大,讓你很容易的改變類的字節(jié)碼。
在上面的方法中我通過改變類的字節(jié)碼,在每個類的方法入口中加入了long stime = System.nanoTime();,在方法的出口加入了System.out.println(“methodClassName.methodName:”+(System.nanoTime()-stime));
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
SpringBoot結(jié)合Quartz實現(xiàn)數(shù)據(jù)庫存儲
本文主要介紹了SpringBoot+Quartz+數(shù)據(jù)庫存儲,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01java根據(jù)不同的參數(shù)調(diào)用不同的實現(xiàn)類操作
這篇文章主要介紹了java根據(jù)不同的參數(shù)調(diào)用不同的實現(xiàn)類操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09Java實戰(zhàn)之用springboot+netty實現(xiàn)簡單的一對一聊天
這篇文章主要介紹了Java實戰(zhàn)之用springboot+netty實現(xiàn)簡單的一對一聊天,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)Java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04Java8接口中引入default關(guān)鍵字的本質(zhì)原因詳析
Default方法是在java8中引入的關(guān)鍵字,也可稱為Virtual extension methods—虛擬擴(kuò)展方法,這篇文章主要給大家介紹了關(guān)于Java8接口中引入default關(guān)鍵字的本質(zhì)原因,需要的朋友可以參考下2022-01-01