0x01、javassist介绍
什么是javassist,这个词一听起来感觉就很懵,对吧~
public void DynGenerateClass() {ClassPool pool = ClassPool.getDefault();CtClass ct = pool.makeClass("com.ideaGenerateClass");//创建类ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口try {CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段f.setModifiers(AccessFlag.PUBLIC);//将字段设置为publicct.addField(f);//将字段设置到类上//添加构造函数CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);ct.addConstructor(constructor);//添加方法CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);ct.addMethod(helloM);ct.writeFile();//将生成的.class文件保存到磁盘//下面的代码为验证代码Field[] fields = ct.toClass().getFields();System.out.println("属性名称:" + fields[0].getName() + " 属性类型:" + fields[0].getType());} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (NotFoundException e) {e.printStackTrace();}}
参考该篇文章java编程-javassist,
0x02 Javassist 使用
这里主要讲一下主要的几个类:
1、ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用。
从实现的角度来看,ClassPool 是一个存储 CtClass 的 Hash 表,类的名称作为 Hash 表的 key。ClassPool 的 get() 函数用于从 Hash 表中查找 key 对应的 CtClass 对象。如果没有找到,get() 函数会创建并返回一个新的 CtClass 对象,这个新对象会保存在 Hash 表中。
需要注意的是ClassPool会在内存中维护所有被它创建过的CtClass,当CtClass数量过多时,会占用大量的内存,API中给出的解决方案是重新创建ClassPool 或 有意识的调用CtClass的detach()方法以释放内存。
常用方法:
//返回默认的ClassPool,一般通过该方法创建我们的ClassPool;static ClassPool getDefault()//在搜索路径的开头插入目录或jar(或zip)文件。ClassPath insertClassPath(java.lang.String pathname)//ClassPath在搜索路径的开头插入一个对象。ClassPath insertClassPath(ClassPath cp)//获取类加载器toClass(),getAnnotations()在 CtClass等java.lang.ClassLoader getClassLoader()//从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。CtClass get(java.lang.String classname)//将ClassPath对象附加到搜索路径的末尾。ClassPath appendClassPath(ClassPath cp)//创建一个新的public类CtClass makeClass(java.lang.String classname)
2、CtClass
CtClass
类表示一个class文件,每个
CtClass对象
都必须从
ClassPool
中获取,CtClass需要关注的方法:
常用方法:
//更改超类,除非此对象表示接口。void setSuperclass(CtClass clazz)//将此类转换为java.lang.Class对象。java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup)//将该类转换为类文件。byte[] toBytecode()//将由此CtClass 对象表示的类文件写入当前目录。void writeFile()//将由此CtClass 对象表示的类文件写入本地磁盘。void writeFile(java.lang.String directoryName)//在当前类中创建了一个静态代码块CtConstructor makeClassInitializer()
freeze:冻结一个类,使其不可修改;isFrozen:判断一个类是否已被冻结;defrost:解冻一个类,使其可以被修改;prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;detach:将该class从ClassPool中删除;writeFile:根据CtClass生成.class文件;toClass:通过类加载器加载该CtClass;addField,removeField:添加/移除一个CtField;addMethod,removeMethod:添加/移除一个CtMethod;addConstructor,removeConstructor:添加/移除一个CtConstructor。
3、CtMethod
CtMethod
:表示类中的方法。
insertBefore:在方法的起始位置插入代码;insterAfter: 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;insertAt: 在指定的位置插入代码;setBody: 将方法的内容设置为要写入的代码,当方法被abstract修饰时,该修饰符被移除;make: 创建一个新的方法。
4、CtConstructor
CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数(类初始化器)。可以通过
CtConstructor.make
方法创建
常用方法
//设置构造函数主体。void setBody(java.lang.String src)//从另一个构造函数复制一个构造函数主体。void setBody(CtConstructor src, ClassMap map)//复制此构造函数并将其转换为方法。CtMethod toMethod(java.lang.String name, CtClass declaring)
5、ClassPath
该类作用是用于通过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径。
构造方法:
//创建一个搜索路径。ClassClassPath(java.lang.Class<?> c)
常见方法:
//获取指定类文件的URL。java.net.URL find (java.lang.String classname)//通过获取类文getResourceAsStream()。java.io.InputStream openClassfile(java.lang.String classname)
代码实例:
ClassPool pool = ClassPool.getDefault();
在默认系统搜索路径获取
ClassPool
对象。
如果需要修改类搜索的路径需要使用
insertClassPath
方法进行修改。
pool.insertClassPath(new ClassClassPath(this.getClass()));
将本类所在的路径插入到搜索路径中
6、oBytecode
package com.demo;import javassist.*;import java.io.IOException;import java.util.Arrays;public class testssit {public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(demo.class.getClass()));CtClass ctClass = pool.get("com.demo.test");ctClass.setSuperclass(pool.get("com.demo.test"));// System.out.println(ctClass);byte[] bytes = ctClass.toBytecode();String s = Arrays.toString(bytes);System.out.println(s);}}
7、toClass
toClass:将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。
Hello类:public class Hello {public void say() {System.out.println("Hello");}}Test 类public class Test {public static void main(String[] args) throws Exception {ClassPool cp = ClassPool.getDefault();//在默认系统搜索路径获取ClassPool对象。CtClass cc = cp.get("com.demo.Hello"); //获取hello类的CtMethod m = cc.getDeclaredMethod("say"); //获取hello类的say方法m.insertBefore("{ System.out.println(\\"Hello.say():\\"); }");//在正文的开头插入字节码Class c = cc.toClass();//将此类转换为java.lang.Class对象Hello h = (Hello)c.newInstance(); //反射创建对象并进行强转h.say();调用方法say}}
0x03、创建Class文件
public class App {public static void main(String[] args) {try {createPerson();} catch (Exception e) {e.printStackTrace();}}private static void createPerson() throws Exception {ClassPool pool = ClassPool.getDefault();// 1. 创建一个空类CtClass cc = pool.makeClass("com.hearing.demo.Person");// 2. 新增一个字段 private String name = "hearing";CtField param = new CtField(pool.get("java.lang.String"), "name", cc);param.setModifiers(Modifier.PRIVATE);cc.addField(param, CtField.Initializer.constant("hearing"));// 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 = \\"hearing\\";}");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("../");}}
创建的class文件如下
public class Person {private String name = "hearing";public void setName(String var1) {this.name = var1;}public String getName() {return this.name;}public Person() {this.name = "hearing";}public Person(String var1) {this.name = var1;}public void printName() {System.out.println(this.name);}}
0x04、调用生成的类对象
1.通过反射的方式调用:
Object person = cc.toClass().newInstance();Method setName = person.getClass().getMethod("setName", String.class);setName.invoke(person, "hearing1");Method execute = person.getClass().getMethod("printName");execute.invoke(person);
2.通过读取class文件的方式调用:
ClassPool pool = ClassPool.getDefault();// 设置类路径pool.appendClassPath("../");CtClass ctClass = pool.get("com.hearing.demo.Person");Object person = ctClass.toClass().newInstance();// 下面和通过反射的方式一样去使用