javaassist使用

之前使用到了Instrumentation来做字节码修改,用到了javaassist,顺便做个笔记,记录一下。

对于动态扩展现有类或接口的二进制字节码,有比较成熟的开源项目提供支持,如CGLib、ASM、Javassist等。其中,CGLib的底层基于ASM实现,是一个高效高性能的生成库;而ASM是一个轻量级的类库,但需要涉及到JVM的操作和指令;相比而言,Javassist要简单的多,完全是基于Java的API,但其性能相比前二者要差一些。

一个简单的示例,如下的代码是动态创建Java类二进制字节码并通过反射调用的示例,可供参考:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.CtField.Initializer;
 
public class JavassistGenerator {
	
	public static void main(String[] args) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
		// 创建类
		ClassPool pool = ClassPool.getDefault();
		CtClass cls = pool.makeClass("cn.ibm.com.TestClass");
		
		// 添加私有成员name及其getter、setter方法
		CtField param = new CtField(pool.get("java.lang.String"), "name", cls);
		param.setModifiers(Modifier.PRIVATE);
		cls.addMethod(CtNewMethod.setter("setName", param));
		cls.addMethod(CtNewMethod.getter("getName", param));
		cls.addField(param, Initializer.constant(""));
		
		// 添加无参的构造体
		CtConstructor cons = new CtConstructor(new CtClass[] {}, cls);
		cons.setBody("{name = \"Brant\";}");
		cls.addConstructor(cons);
		
		// 添加有参的构造体
		cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, cls);
		cons.setBody("{$0.name = $1;}");
		cls.addConstructor(cons);
		
		// 打印创建类的类名
		System.out.println(cls.toClass());
		
		// 通过反射创建无参的实例,并调用getName方法
		Object o = Class.forName("cn.ibm.com.TestClass").newInstance();
		Method getter = o.getClass().getMethod("getName");
		System.out.println(getter.invoke(o));
		
		// 调用其setName方法
		Method setter = o.getClass().getMethod("setName", new Class[] {String.class});
		setter.invoke(o, "Adam");
		System.out.println(getter.invoke(o));
		
		// 通过反射创建有参的实例,并调用getName方法
		o = Class.forName("cn.ibm.com.TestClass").getConstructor(String.class).newInstance("Liu Jian");
		getter = o.getClass().getMethod("getName");
		System.out.println(getter.invoke(o));
	}
 
}

参考:https://www.cnblogs.com/sunfie/p/5154246.html

  1. 读取和输出字节码
        ClassPool pool = ClassPool.getDefault();
        pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        CtClass cl = null;
        try {
            // 从classpath中查询该类
            cl = pool.get("com.zero.test.TestMainInJar");
            // ...
            // 获取二进制格式
            byte[] byteArr = cl.toBytecode();
            //输出.class文件到该目录中
            cl.writeFile("/Users/zero/git/xxx/src/main/java/");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cl != null) {
                //把CtClass object 从ClassPool中移除
                cl.detach();
            }
        }

2.插入source 文本在方法体前或者后
  CtMethod 和CtConstructor 提供了 insertBefore()、insertAfter()和 addCatch()方法,它们可以插入一个souce文本到存在的方法的相应的位置。javassist 包含了一个简单的编译器解析这souce文本成二进制插入到相应的方法体里。
  Javassist提供了一些特殊的变量来代表方法参数:$1, 2 , 2, 2,args…

2.1 $0, $1, $2, …
  $0代码的是this,$1代表方法参数的第一个参数、$2代表方法参数的第二个参数,以此类推,$N代表是方法参数的第N个。例如:

//实际方法
void move(int dx, int dy) 
//javassist
CtMethod m = cc.getDeclaredMethod("move");
//打印dx,和dy
m.insertBefore("{ System.out.println($1); System.out.println($2); }");

  注意:如果javassist改变了$1的值,那实际参数值也会改变。

2.2 $args
  $args 指的是方法所有参数的数组,类似Object[],如果参数中含有基本类型,则会转成其包装类型。需要注意的时候,$args[0]对应的是$1,而不是$0,$0!=$args[0],$0=this。

2.3 $$
  $$是所有方法参数的简写,主要用在方法调用上。例如:

//原方法
move(String a,String b)
move($$) 相当于move($1,$2)

  如果新增一个方法,方法含有move的所有参数,则可以这些写:exMove($$, context) 相当于exMove($1, $2, context)

2.4 $_
  $_代表的是方法的返回值。

2.5 $sig
  $sig指的是方法参数的类型(Class)数组,数组的顺序为参数的顺序。

2.6 $class
  $class 指的是this的类型(Class)。也就是$0的类型。

2.7 addCatch()
  addCatch() 指的是在方法中加入try catch 块,需要主要的是,必须在插入的代码中,加入return 值。$e代表 异常值。比如:

CtMethod m = ...;
CtClass etype = ClassPool.getDefault().get("java.io.IOException");
m.addCatch("{ System.out.println($e); throw $e; }", etype);

  实际代码如下:

try {
    the original method body
}
catch (java.io.IOException e) {
    System.out.println(e);
    throw e;
}

3、修改方法体
CtMethod 和CtConstructor 提供了 setBody() 的方法,可以替换方法或者构造函数里的所有内容。注意 $_变量不支持。

4、新增一个方法或者field
  Javassist 允许开发者一个新的方法或者构造方法。新增一个方法,例如:

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int xmove(int dx) { x += dx; }",
                 point);
point.addMethod(m);

  在方法中调用其他方法,例如:

CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make(
                 "public int ymove(int dy) { $proceed(0, dy); }",
                 point, "this", "move");

  其效果如下:public int ymove(int dy) { this.move(0, dy); }

   新增field

CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
f.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
point.addField(f);
//point.addField(f, "0");    // initial value is 0.

  或者:

CtClass point = ClassPool.getDefault().get("Point");
CtField f = CtField.make("public int z = 0;", point);
point.addField(f);

  移除方法或者field,调用removeField()或者removeMethod()即可。


简单示例,统计方法执行时间:

public class TestMain {
    public static void main(String[] args) {
        ClassPool pool = ClassPool.getDefault();
        pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        CtClass cl = null;
        try {
            cl = pool.get("com.zero.test.TestMainInJar");
            CtMethod[] methods = cl.getDeclaredMethods();

            for (CtMethod method : methods) {
                doMethod(method);
            }
//            byte[] byteArr = cl.toBytecode();
            cl.writeFile("/Users/zero/git/javaagenttest/src/main/java/");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cl != null) {
                cl.detach();
            }
        }
    }

    private static void doMethod(CtMethod ctMethod) throws NotFoundException, CannotCompileException {
        String methodName = ctMethod.getName();

        if(methodName.equals("main")){
            return;
        }
        ctMethod.addLocalVariable("startTimeAgent", CtClass.longType);
        ctMethod.insertBefore("startTimeAgent = System.currentTimeMillis();");

        String systemPrintStr = null;
        LinkedHashMap<String, String> parmAndValue = AssistUtil.getParmAndValue(ctMethod);
        if (parmAndValue != null) {
            systemPrintStr = AssistUtil.parmSystemPrint(parmAndValue);
        }

        System.out.println(systemPrintStr);
        if (systemPrintStr != null) {
            ctMethod.insertAfter("System.out.println(\"cost:\" + (System.currentTimeMillis() - startTimeAgent) + \"ms, \" + " + systemPrintStr + ");");
        }
        else {
            ctMethod.insertAfter("System.out.println(\"cost:\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");");
        }
    }
}

public class AssistUtil {

    public static String replaceClassName(String className) {
        return className.replace("/", ".");
    }

    public static LinkedHashMap<String, String> getParmAndValue(CtMethod ctMethod) throws NotFoundException {
        int parmLength = ctMethod.getParameterTypes().length;
        if (parmLength == 0) {
            return null;
        }

        boolean isStatic = Modifier.isStatic(ctMethod.getModifiers());

        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            throw new NotFoundException("LocalVariableAttribute not found in " + ctMethod.getName());
        }

        LinkedHashMap<String, String> parmAndValue = new LinkedHashMap<>();
        String[] paramNames = new String[parmLength];
        for (int i = 0; i < paramNames.length; i++) {
            if (isStatic) {
                // 静态方法第一个 $0 是 null
                parmAndValue.put(attr.variableName(i), "$" + (i + 1));
            }
            else {
                // 非静态方法第一个 $0 是 this
                parmAndValue.put(attr.variableName(i + 1), "$" + (i + 1));
            }
        }
        return parmAndValue;
    }

    public static String parmSystemPrint(LinkedHashMap<String, String> parmAndValue) {
        List<String> formatStringlines = new ArrayList<>();
        parmAndValue.forEach((parm, value) ->
                        formatStringlines.add("\"" + parm + ": \" + " + value)
                            );
        StringBuilder stringBuilder = new StringBuilder(32);
        stringBuilder.append(String.join(" + \", \" + ", formatStringlines));
        return stringBuilder.toString();
    }
}

执行后生成的.class反编译结果:

package com.zero.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestMainInJar {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestMainInJar.class);

    public TestMainInJar() {
    }

    public static void main(String[] args) throws InterruptedException {
        boolean var1 = false;

        while(true) {
            Thread.sleep(1000L);
            int number = 3;
            test(number);
            (new TestMainInJar()).test2();
            (new TestMainInJar()).test3(3L, "zero");
        }
    }

    public static void test(int a) throws InterruptedException {
        long startTimeAgent = System.currentTimeMillis();
        Thread.sleep(1000L);
        LOGGER.info("test a");
        Object var4 = null;
        System.out.println("cost:" + (System.currentTimeMillis() - startTimeAgent) + "ms, " + "a: " + a);
    }

    public void test2() throws InterruptedException {
        long startTimeAgent = System.currentTimeMillis();
        System.out.println("test2");
        Object var4 = null;
        System.out.println("cost:" + (System.currentTimeMillis() - startTimeAgent) + "ms");
    }

    public void test3(long a, String b) throws InterruptedException {
        long startTimeAgent = System.currentTimeMillis();
        System.out.println("test3: --- " + a + "," + b);
        Object var7 = null;
        System.out.println("cost:" + (System.currentTimeMillis() - startTimeAgent) + "ms, " + "a: " + a + ", " + "b: " + b);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值