java.lang.Instrument 代理Agent使用

java.lang.Instrument包是在JDK5引入的,程序員經過修改方法的字節碼實現動態修改類代碼。這一般是在類的main方法調用以前進行預處理的操做,經過java指定該類的代理類來實現。在類的字節碼載入JVM前會調用ClassFileTransformer的transform方法,從而實現修改原類方法的功能,實現AOP,這個的好處是不會像動態代理或者CGLIB技術實現AOP那樣會產生一個新類,也不須要原類要有接口java

(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_06app

而後把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。jvm

(3)全部的這些Agent的jar包,都會自動加入到程序的classpath中。因此不須要手動把他們添加到classpath。除非你想指定classpath的順序。函數

(4)一個java程序中-javaagent這個參數的個數是沒有限制的,因此能夠添加任意多個java agent。全部的java agent會按照你定義的順序執行。 例如:this

java -javaagent:MyAgent1.jar -javaagent:MyAgent2.jar -jar MyProgram.jarspa

假設MyProgram.jar裏面的main函數在MyProgram中。MyAgent1.jar, MyAgent2.jar, 這2個jar包中實現了premain的類分別是MyAgent1, MyAgent2 程序執行的順序將會是:代理

MyAgent1.premain -> MyAgent2.premain -> MyProgram.maincode

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

相關文章
相關標籤/搜索