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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。