Javassist如何操作Java 字节码
一、开篇
说起AOP小伙伴们肯定很熟悉,无论是JDK动态代理或者是CGLIB等,其底层都是通过操作Java字节码来实现代理。常用的一些操作字节码的技术有ASM、AspectJ、Javassist等。
ASM其设计和实现是尽可能小而且快,更专注于性能。它在指令的层面来操作,所以使用它需要对JVM的指令有所了解,门槛较高,CGLIB就使用了ASM技术。
AspectJ扩展了Java语言,定义了一系列AOP语法,在JVM中运行需要使用特定的编译器生成遵守Java字节码规范的Class文件,SpringAOP使用了AspectJ。
Javassist直接使用Java编码的形式操作字节码,简单易上手,性能高于反射,相比于ASM稍低。
二、Javassist常用类
Javassist抽象出一个ClassPool对象来操作Java类,可以通过ClassPool.getDefault()来获取默认的ClassPool。常用的对象:
CtClass:代表一个Class的实例,可以通过类的全限定名来获取CtClass对象,其中包含了对Class的各种操作。
ClassPool:通过HashTable保存了路径下的CtClass信息,key为类的全限定名称,value为类名对应的CtClass对象。
CtMethod、CtField:抽象出类的方法和属性,可以用于定义或修改方法和字段。
三、Javassist的使用
1、依赖
org.javassist javassist 3.27.0-GA
2、代码示例
//获取默认类池 ClassPoolclassPool=ClassPool.getDefault(); //1.创建空类 CtClassctClass=classPool.makeClass("com.aysaml.demo.javassist.User"); //2.创建String类型的name字段 CtFieldfield=newCtField(classPool.get("java.lang.String"),"name",ctClass); //设置字段访问级别private field.setModifiers(Modifier.PRIVATE); //增加字段 ctClass.addField(field); //3.增加getter&setter方法 ctClass.addMethod(CtNewMethod.getter("getName",field)); ctClass.addMethod(CtNewMethod.setter("setName",field)); //4.增加无参构造方法:其中$0表示this,$1表示参数 CtConstructornoArgsCons=newCtConstructor(newCtClass[]{},ctClass); noArgsCons.setBody("{$0.name=\"mark\";}"); ctClass.addConstructor(noArgsCons); //5.增加有参构造方法 CtConstructorhasArgsCons= newCtConstructor(newCtClass[]{classPool.get("java.lang.String")},ctClass); hasArgsCons.setBody("{$0.name=$1;}"); ctClass.addConstructor(hasArgsCons); //6.创建方法 CtMethodmethod=newCtMethod(CtClass.voidType,"printName",newCtClass[]{},ctClass); method.setBody("{System.out.println($0.name);}"); ctClass.addMethod(method); //7.生成类文件:可指定路径,默认为当前项目根目录 ctClass.writeFile(); //8.创建类实例 Objectperson=ctClass.toClass().newInstance();
3、如何实现类似AOP的功能
由上可见,Javassist对于编程化的操作字节码是很简单易懂的,我们以在方法的开头结尾打印信息为例:
publicclassCat{ /**记录喵喵喵的次数*/ privateintnum; publicvoidmiao(){ this.num++; } }
我们要在miao()方法的前增加声音输出:
publicstaticvoidmain(String[]args)throwsNotFoundException,CannotCompileException{ ClassPoolclassPool=ClassPool.getDefault(); //获取Cat类的CtClass对象 CtClasscatClass=classPool.get("com.aysaml.demo.javassist.Cat"); //获取miao()方法 CtMethodmethod=catClass.getDeclaredMethod("miao"); method.insertBefore("System.out.println(\"miao~\");"); //加载修改过的类,注意必须要保证调用前这个类没有被加载过 catClass.toClass(); //测试 Catcat=newCat(); cat.miao(); }
注意到,在使用catClass.toClass()加载被修改过的类时,强调必须保证在调用前这个类没有被加载过,否则会报attemptedduplicateclassdefinitionforname异常。
我们知道一个类是不能被一个类加载器加载两次的,所以为了解决这个问题,需要制定一个没有加载过该类的Classloader,Javassist提供了一个ClassLoader,如下:
publicclassCat{ /**记录喵喵喵的次数*/ privateintnum; publicvoidmiao(){ System.out.println("调用了miao方法"); this.num++; } publicstaticvoidmain(String[]args)throwsException{ ClassPoolclassPool=ClassPool.getDefault(); //获取Cat类的CtClass对象 CtClasscatClass=classPool.get("com.aysaml.demo.javassist.Cat"); //获取miao()方法 CtMethodmethod=catClass.getDeclaredMethod("miao"); method.insertBefore("System.out.println(\"miao~\");"); //重新设置一个Classloader LoaderclassLoader=newLoader(classPool); Classclazz=classLoader.loadClass("com.aysaml.demo.javassist.Cat"); //调用修改过的类的方法 clazz.getDeclaredMethod("miao").invoke(clazz.newInstance()); } }
执行结果为:
四、结语
关于Javassist暂时就说这么多了,更多使用方法参考官方githubwiki:
以上就是Javassist如何操作Java字节码的详细内容,更多关于Javassist操作Java字节码的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。