Java代理-Javassist

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

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

package monitor;

import java.lang.instrument.Instrumentation;

public class MyAgent {

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

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

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

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

創建一個 Transformer 類:MonitorTransformer 
app

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

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;
	}
}

代碼結構:
post

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

注意有一行空格 測試


下面把代理打成一個jar包

導出的時候注意:將MANIFEST.MF打包進去

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

測試代碼:
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://blog.csdn.net/qyongkang/article/details/7765255 this

相關文章
相關標籤/搜索