Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类(包括接口和枚举)。Javaassist 是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加或者是修改已有的方法,也可以
生成一个新的 .class类对象。难能可贵的是Javassist使用起来非常简单,不需要对Java字节码有深入的了解,就能快速上手。
本文演示使用的Javassist GAV信息:
参考文档:http://www.javassist.org/tutorial/tutorial2.html
<dependency> <groupId>org.javassistgroupId> <artifactId>javassistartifactId> <version>3.25.0-GAversion>dependency>
1. 创建类对象
为了演示方便,我们先来创建一个全新的 .class类对象。
@Test public void test() { try { ClassPool pool = ClassPool.getDefault(); // 1. 创建一个空类 CtClass cc = pool.makeClass("com.github.jimbean0615.test.Person"); // 2. 新增一个字段 private String name; // 字段名为name CtField param = new CtField(pool.get("java.lang.String"), "name", cc); // 访问级别是 private param.setModifiers(Modifier.PRIVATE); // 初始值是zhangsan cc.addField(param, CtField.Initializer.constant("zhangsan")); // 3. 生成 getter/setter 方法 cc.addMethod(CtNewMethod.setter("setName", param)); cc.addMethod(CtNewMethod.getter("getName", param)); // 4. 添加无参的构造函数 CtConstructor cons = new CtConstructor(new CtClass[]{}, cc); cons.setBody("{name = \"zhangsan\";}"); cc.addConstructor(cons); // 5. 添加有参的构造函数 cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc); // $0代表this // $1,$2,$3... 依次代表第几个方法参数 // $$表示所有参数 cons.setBody("{$0.name = $1;}"); cc.addConstructor(cons); // 6. 创建一个名为printName方法,无参数,无返回值,输出name值 CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc); ctMethod.setModifiers(Modifier.PUBLIC); ctMethod.setBody("{System.out.println(name);}"); cc.addMethod(ctMethod); //这里会将这个创建的类对象编译为.class文件 cc.writeFile("/Users/zhangjianbin/Downloads/java"); } catch (Exception e) { e.printStackTrace(); } }
不出意外的话,我们到
/Users/zhangjianbin/Downloads/java/com/github/jimbean0615/test
目录下会看到新创建的Person.class文件。
使用idea可以打开该class文件,看看内容是否符合预期。
接下来我们开始尝试调用以上class文件的方法。
@Test public void test1() { try { ClassPool pool = ClassPool.getDefault(); pool.appendClassPath("/Users/zhangjianbin/Downloads/java"); // 加载class文件 CtClass ctClass = pool.get("com.github.jimbean0615.test.Person"); // 创建类实例 Object person = ctClass.toClass().newInstance(); // 设置值 Method setName = person.getClass().getMethod("setName", String.class); setName.invoke(person, "lisi"); // 输出值 Method execute = person.getClass().getMethod("printName"); execute.invoke(person); } catch (Exception e) { e.printStackTrace(); } }
@Test public void test2() throws Exception { ClassPool pool = ClassPool.getDefault(); pool.appendClassPath("/Users/zhangjianbin/Downloads/java/"); pool.appendClassPath(new ClassClassPath(this.getClass())); // 获取接口 CtClass codeClassI = pool.get("com.github.jimbean0615.test.PersonI"); // 获取上面生成的类 CtClass ctClass = pool.get("com.github.jimbean0615.test.Person"); // 使代码生成的类,实现 PersonI 接口 ctClass.setInterfaces(new CtClass[]{codeClassI}); // 以下通过接口直接调用 强转 PersonI person = (PersonI) ctClass.toClass().newInstance(); System.out.println(person.getName()); person.setName("王二麻子"); person.printName(); }
/** * @author zhangjb */public interface PersonI { void setName(String name); String getName(); void printName();}
一般实际工作中遇到的场景更多的是修改已有的类。比如常见的日志切面。利用Javassist来实现类似的功能。
@Test public void test3() throws Exception { ClassPool pool = ClassPool.getDefault(); pool.appendClassPath("/Users/zhangjianbin/Downloads/java/"); pool.appendClassPath(new ClassClassPath(this.getClass())); // 获取生成的类 CtClass ctClass = pool.get("com.github.jimbean0615.test.Person"); CtMethod ctmethod = ctClass.getDeclaredMethod("printName"); ctmethod.insertBefore("{ $0.name = \"王二麻子\"; }"); // 以下通过接口直接调用 强转 Object person = ctClass.toClass().newInstance(); Method printNameMethod = person.getClass().getMethod("printName"); printNameMethod.invoke(person); }
4. 新增一个方法
@Test public void test4() throws Exception { ClassPool pool = ClassPool.getDefault(); pool.appendClassPath("/Users/zhangjianbin/Downloads/java/"); pool.appendClassPath(new ClassClassPath(this.getClass())); // 获取生成的类 CtClass ctClass = pool.get("com.github.jimbean0615.test.Person"); //新增一个方法 CtMethod ctMethod = new CtMethod(CtClass.voidType, "setPersonName", new CtClass[]{pool.get("java.lang.String")}, ctClass); ctMethod.setModifiers(Modifier.PUBLIC); ctMethod.setBody("{ System.out.println(\"the name after modified is \" + $1);}"); ctClass.addMethod(ctMethod); // 以下通过接口直接调用 强转 Object person = ctClass.toClass().newInstance(); Method setPersonNameMethod = person.getClass().getMethod("setPersonName", String.class); setPersonNameMethod.invoke(person, "王二麻子"); }
$
开头的几个标识符的含义:
符号 | 含义 |
---|---|
$0 , $1 , $2 , ... | this and 方法的参数 |
$args | 方法参数数组.它的类型为 Object[] |
$$ | 所有实参。例如, m($$) 等价于 m($1,$2, ...) |
$cflow( ...) | cflow 变量 |
$r | 返回结果的类型,用于强制类型转换 |
$w | 包装器类型,用于强制类型转换 |
$_ | 返回值 |
$sig | 类型为 java.lang.Class 的参数类型数组 |
$type | 一个 java.lang.Class 对象,表示返回值类型 |
$class | 一个 java.lang.Class 对象,表示当前正在修改的类 |