java的特性instrumentation是在java5之后加入的,开发者可以开发一个代理类,对JVM上的程序进行监测,监测可以在一个类文件在加载入JVM之前,对类文件的字节码进行适当的修改,达到动态注入代码的目的,再将修改后的类文件传入JVM,则JVM在执行原类也执行了修改的程序。这种方法相当在JVM级别上做了AOP。
一、代理原理图
二、程序实现
每个代理的实现类都实现一个接口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