特性 instrumentation 实践

java的特性instrumentation是在java5之后加入的,开发者可以开发一个代理类,对JVM上的程序进行监测,监测可以在一个类文件在加载入JVM之前,对类文件的字节码进行适当的修改,达到动态注入代码的目的,再将修改后的类文件传入JVM,则JVM在执行原类也执行了修改的程序。这种方法相当在JVM级别上做了AOP。

一、代理原理图

特性 instrumentation 实践_第1张图片

二、程序实现

每个代理的实现类都实现一个接口java.lang.instrument.ClassFileTransformer

public byte[] transform(ClassLoader loader, String className,
			Class classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException;
transform方法返回值为null时,表示不对类的字节码进行转化。

代理类需要有一个静态方法premain

public static void premain(String agentArgs, Instrumentation inst)
			 throws ClassNotFoundException, UnmodifiableClassException;
代理需要打包到jar包,且jar包的MANIFEST.MF需要包含代理类的信息。通过命令行启动:

java -javaagent:[jar包名][="参数"] main方法的类

现在假设有个需要监测的类:

public class Worker {
	
	public void doWork() throws InterruptedException{
		System.out.println("I am working.");
		Thread.sleep(500);
	}
	
	public int getPaid(){
		return caculatePaid();
	}
	
	private int caculatePaid(){
		int i = new Random().nextInt(10);
		if(i % 2 == 0){
			throw new RuntimeException("test ocurrs exception,i:" + i);
		}else{
			return i;
		}
	}

}
代理的实现类:

这里使用javassist对类的字节码进行修改,当然也有ASM同样可以修改字节码,javassist需要有个javassist.jar

public class WorkerTransformer implements ClassFileTransformer{
	
	private String className;
	
	private String[] methodNames;
	
	public WorkerTransformer(){
		this.className = "org/lam/detector/core/premain/Worker";
		this.methodNames = new String[]{"doWork", "caculatePaid"};
	}

	@Override
	public byte[] transform(ClassLoader loader, String className,
			Class classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException {
		if(this.className.equals(className)){
			className = className.replace("/", ".");
			CtClass ctClass = null;
			StringBuilder sb = null;
			try {
				//使用全称类名,使用javassist取得字节码类
				ctClass = ClassPool.getDefault().get(className);
				for(String methodName : methodNames){
					//得到方法实例
					CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
					//定义一个新方法
					String newMethodName = methodName + "$impl";
					//修改原来的方法->新的方法名
					ctMethod.setName(newMethodName);
					//创建新的方法,复制原来的方法 ,名字为原来的名字
					CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctClass, null);
					//获取方法的返回值类型
					String returnType = ctMethod.getReturnType().getName();
					//创建新的方法体
					sb = new StringBuilder();
					sb.append("{");
					if(!returnType.equals("void")){
						sb.append("\n " + returnType + " returnObj;");
					}
					sb.append("\n long startTime = System.currentTimeMillis();");
					sb.append("\n try{");
					if(returnType.equals("void")){
						//调用原有代码,类似于method();($$)表示所有的参数 
						sb.append("\n " + newMethodName + "($$);");
					}else{
						sb.append("\n returnObj = " + newMethodName + "($$);");
					}
					sb.append("\n }catch(Exception e){");
					sb.append("\n throw e;");
					sb.append("\n }");
					sb.append("\n long endTime = System.currentTimeMillis();");
					sb.append("\n System.out.println(\"method:" + methodName + ", cost:\" + (endTime - startTime) + \"ms\");");
					if(!returnType.equals("void")){
						//方法有返回值则要返回
						sb.append("\n return returnObj;");
					}
					sb.append("}");
					
					newMethod.setBody(sb.toString());
					ctClass.addMethod(newMethod);
				}
				return ctClass.toBytecode();
			} catch (NotFoundException e) {
				e.printStackTrace();
			} catch (CannotCompileException e) {
				//打印出方法体,方便看出错误之处
				System.err.print(sb.toString());
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

}
代理类:

package org.lam.detector.core.premain;

public class Premain {
	
	public static void premain(String agentArgs, Instrumentation inst)
			 throws ClassNotFoundException, UnmodifiableClassException{
		inst.addTransformer(new WorkerTransformer());
	}

}
main类:

package org.lam.detector.core.premain;

public class MainWorker {
	
	public static void main(String[] args){
		Worker worker = new Worker();
		try {
			worker.doWork();
			int paid = worker.getPaid();
			System.out.println("paid:" + paid);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}
打在jar包里的MANIFEST.MF:

Manifest-Version: 1.0
Created-By: 1.6.0_06
Premain-Class: org.lam.detector.core.premain.Premain
打好jar包,命令行执行:

java -cp d:\javassist.jar -javaagent:detector.jar org.lam.detector.core.premain.MainWorker



你可能感兴趣的:(java)