java代理-javassist

jopen 10年前發布 | 21K 次閱讀 javassist Java開發

javaAgent是從JDK1.5及以后引入的,在1.5之前無法使用,也可以叫做java代理。

      代理 (agent) 是在你的main方法前的一個攔截器 (interceptor),也就是在main方法執行之前,執行agent的代碼。agent的代碼與你的main方法在同一個JVM中運行,并被同一個system classloader裝載,被同一的安全策略 (security policy) 和上下文 (context) 所管理。

在java5和java6中只需要實現premain這個方法:

package monitor;

import java.lang.instrument.Instrumentation;

public class MyAgent {

public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new MonitorTransformer());
}

}</pre>

premain方法的參數里有一個Instrumentation,使用instrumentation開發者可以構建獨立于應用程序的java agent(代理)程序,用來監測運行在JVM上的程序,甚至可以動態的修改和替換類的定義。給力的說,這種方式相當于在JVM級別做了AOP支持,這樣我們可以在不修改應用程序的基礎上就做到了AOP.你不必去修改應用程序的配置,也不必重新打包部署驗證。

JDK5中只能通過命令行參數在啟動JVM時指定javaagent參數來設置代理類,而JDK6中已經不僅限于在啟動JVM時通過配置參數來設置代理類,JDK6中通過 Java Tool API 中的 attach 方式,我們也可以很方便地在運行過程中動態地設置加載代理類,以達到 instrumentation 的目的
Instrumentation 的最大作用,就是類定義動態改變和操作

最簡單的一個例子,計算某個方法執行需要的時間,不修改源代碼的方式,使用Instrumentation 代理來實現這個功能。

建立一個 Transformer 類:MonitorTransformer 

這個類實現了接口public interface ClassFileTransformer實現這個接口的目的就是在class被裝載到JVM之前將class字節碼轉換掉,從而達到動態注入代碼的目的。那么首先要了解MonitorTransformer 這個類的目的,就是對想要修改的類做一次轉換,這個用到了javassist對字節碼進行修改,可以暫時不用關心jaavssist的原理,用ASM同樣可以修改字節碼,只不過比較麻煩些。

package monitor;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

public class MonitorTransformer implements ClassFileTransformer {

    final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
    final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";
    final static List<String> methodList = new ArrayList<String>();

    public MonitorTransformer() {
        methodList.add("main.TimeTest.sayHello");
        methodList.add("main.TimeTest.sayHello2");
    }

    @Override
    public byte[] transform(ClassLoader loader, String className,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
            byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.startsWith("main")) {//判斷加載的class的包路徑是不是需要監控的類
            className = className.replace("/", ".");
            CtClass ctclass = null;
            try {
                ctclass = ClassPool.getDefault().get(className);//使用全稱,用于取得字節碼類<使用javassist>
                for (String method : methodList) {
                    if (method.startsWith(className)) {
                        String methodName = method.substring(
                                method.lastIndexOf('.') + 1, method.length());

                        String outputStr = "\nSystem.out.println(\"this method "
                                + methodName
                                + " cost:\" +(endTime - startTime) +\"ms.\");";

                        CtMethod ctmethod = ctclass
                                .getDeclaredMethod(methodName);//得到這方法實例  
                        String newMethodName = methodName + "$impl";//新定義一個方法叫做比如sayHello$impl   
                        ctmethod.setName(newMethodName);//原來的方法改個名字   

                        CtMethod newMethod = CtNewMethod.copy(ctmethod,
                                methodName, ctclass, null);//創建新的方法,復制原來的方法 ,名字為原來的名字  
                        //構建新的方法體  
                        StringBuilder bodyStr = new StringBuilder();
                        bodyStr.append("{");
                        bodyStr.append(prefix);
                        bodyStr.append(newMethodName + "($$);\n");//調用原有代碼,類似于method();($$)表示所有的參數   
                        bodyStr.append(postfix);
                        bodyStr.append(outputStr);
                        bodyStr.append("}");

                        newMethod.setBody(bodyStr.toString());//替換新方法   
                        ctclass.addMethod(newMethod);//增加新方法   
                    }
                }
                return ctclass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

代碼結構:
java代理-javassist

Manifest-Version: 1.0
Premain-Class: monitor.MyAgent
Can-Redefine-Classes: true
Boot-Class-Path: javassist.jar

注意有一行空格


下面把代理打成一個jar包
java代理-javassist
導出的時候注意:將MANIFEST.MF打包進去

導出的jar放入:D:\javaagentTest\agentMethod.jar  供后面測試使用,將javassist.jar也放入相同路徑

測試代碼:
package main;

public class TimeTest {

    public static void main(String[] args) {
        sayHello();
        sayHello2("hello world222222222");
    }

    public static void sayHello() {
        try {
            Thread.sleep(2000);
            System.out.println("hello world!!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sayHello2(String hello) {
        try {
            Thread.sleep(1000);
            System.out.println(hello);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
測試代碼在運行的時候加上VM arguments: -javaagent:D:\javaagentTest\agentMethod.jar

hello world!!
this method sayHello cost:2000ms.
hello world222222222
this method sayHello2 cost:1000ms.

來自:http://my.oschina.net/OutOfMemory/blog/309283
參考:http://blog.csdn.net/qyongkang/article/details/7765255

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!