一、背景
编程语言有静态语言和动态语言之分,例如:C、C++、Java等属于静态语言,JavaScript、Ruby、Python等属于动态语言。
动态语言的灵活性非常高,遗憾的是,至今为止,作为Java程序员的我尚未享受到动态编程的乐趣。
不过,Java也逐步提供了一些有限的动态编程机制,主要有下面三个方面:
- (1) 反射
- (2) 动态编译
- (3) 调用JavaScript引擎
- (4) 动态生成字节码
(1)反射
这个就不说了,大家都会。原理也就是通过在运行时获得类型信息然后做相应的操作。
(2)动态编译
Java从1.6开始支持了动态编译,主要是通过一个JavaCompiler
接口来完成,直接对Java源码进行动态编译执行。这个我之前的博客有所介绍(传送门),也不说了。
(3)调用JavaScript引擎
这个嘛,我不会#_#。// TODO
(4)动态生成字节码
通过操作Java字节码的方式在JVM
中生成新类或者对已经加载的类动态修改。
ASM:直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。
Javassist:提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。
应用层面建议优先选择Javassist,如果后续发现Javassist成为了整个应用的效率瓶颈的话可以再考虑ASM。
如果开发的是一个基础类库,或者基础平台,还是直接使用ASM吧,相信从事这方面工作的开发者能力应该比较高。
因为后续的Dubbo涉及到Javassist,所以先研究它了。
二、简介
GitHub地址:https://github.com/jboss-javassist/javassist
不知道说什么好,直接从百度百科ctrl+c + ctrl+v过来吧(程序员的看家本领#_#)。
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
三、使用
首先定义一个实实在在的类以及一个父类,然后使用Javassist来动态创建一个同样的Java字节码。
常规的一个类:实现接口,继承父类,有各种属性,有各种构造器,有各种函数。
/**
* 实实在在的一个类
* @author z_hh
* @time 2018年12月30日
*/
public class RealClass extends SupperClass implements Cloneable {
// 常量
public static final String CLASS_NAME = "RealClass";
// 静态代码块
static {
System.out.println("我是" + CLASS_NAME + ", 我开始加载了...");
}
// 成员属性1
private int field1 = 0;
// 成员属性2
private String field2 = "";
// 默认构造器
public RealClass() {}
// 含参构造器
public RealClass(int field1, String field2) throws Exception {
super();
this.field1 = field1;
this.field2 = field2;
}
// 静态方法
public static void staticMethod() {
System.out.println("我是静态方法staticMethod!");
}
// 私有成员方法
private String method1(String name) {
return "Hello " + name;
}
// 公开成员方法
public void method2(String name) {
System.out.println(method1(name));
}
// 重写方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// getter和setter
public int getField1() {
return field1;
}
public void setField1(int field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
}
一个父类意思一下。
/**
* 父类
* @author z_hh
* @time 2018年12月30日
*/
public class SupperClass {
public SupperClass() {
System.out.println("默认构造函数执行了...");
}
}
直接开始吧。
1、创建类并继承父类(还有其它重载方法)。
ClassPool pool = ClassPool.getDefault();
// 1、创建类并继承父类(还有其它重载方法)
CtClass ctClass = pool.makeClass("cn.zhh.javassist.newClass",
pool.get("cn.zhh.javassist.SupperClass"));
2、实现接口。
// 2、实现接口
ctClass.setInterfaces(new CtClass[] {pool.get("java.lang.Cloneable")});
3、添加常量
// 3、添加常量
ctClass.addField(CtField.make("public static final String CLASS_NAME = \"RealClass\";",
ctClass));
4、添加静态代码块
// 4、添加静态代码块
// TODO
5、添加成员属性1
可以同时设置默认值,当然也可以像成员属性2那样简单粗暴。
// 5、添加成员属性1
ctClass.addField(CtField.make("private int field1;",
ctClass), "0");// initial value is 0
6、添加成员属性2
// 6、添加成员属性2
ctClass.addField(CtField.make("private String field2 = \"\";", ctClass));
7、添加默认构造器
// 7、添加默认构造器
ctClass.addConstructor(CtNewConstructor.defaultConstructor(ctClass));
8、添加含参构造器
$1表示第一个参数,$2表示第二个参数($0表示this)。基本类型的CtClass在CtClass已有静态变量定义,如CtClass.intType。
// 8、添加含参构造器
CtClass[] parameters = new CtClass[] {CtClass.intType, pool.get("java.lang.String")};
CtClass[] exceptions = new CtClass[] {pool.get("java.lang.Exception")};
String body = "{super();this.field1 = ;this.field2 = ;}";
ctClass.addConstructor(CtNewConstructor.make(parameters, exceptions, body, ctClass));
// 也可以用下面的,简单粗暴
/*ctClass.addConstructor(CtNewConstructor.make("public RealClass(int field1, String field2) {\r\n" +
" super();\r\n" +
" this.field1 = ;\r\n" +
" this.field2 = ;\r\n" +
" }", ctClass));*/
9、添加静态方法
// 9、添加静态方法
ctClass.addMethod(CtNewMethod.make("public static void staticMethod() {\r\n" +
" System.out.println(\"我是静态方法staticMethod!\");\r\n" +
" }", ctClass));
10、添加私有成员方法(也可以直接上整个方法代码)
// 10、添加私有成员方法(也可以直接上整个方法代码)
CtClass methodReturnType = pool.get("java.lang.String");
CtClass[] methodParameters = new CtClass[] {pool.get("java.lang.String")};
CtClass[] methodExceptions = new CtClass[0];
String methodBody = "{\r\n" +
" return \"Hello \" + ;\r\n" +
" }";
CtMethod method1 = CtNewMethod.make(Modifier.PRIVATE, methodReturnType, "method1",
methodParameters, methodExceptions, methodBody, ctClass);
ctClass.addMethod(method1);
11、添加公开成员方法
主要处理调用兄弟方法的问题,得这么写。其效果等同于public void method2(String name) {System.out.println(method1(name));}。"$$"替换符:All actual parameters.For example, m($$) is equivalent to m($1,$2,...)。
// 11、添加公开成员方法
ctClass.addMethod(CtNewMethod.make("public void method2(String name) {\r\n" +
" System.out.println($proceed($$));\r\n" +
" }", ctClass, "this", "method1"));
12、添加重写方法
主要是注解不太好处理,javassist还提供了创建注解的功能。
// 12、添加重写方法
ClassFile ccFile = ctClass.getClassFile();
ConstPool constpool = ccFile.getConstPool();
// get the annotation
AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
attr.addAnnotation(new Annotation("Override", constpool));
// create the method
CtMethod cloneMethod = CtNewMethod.make("protected Object clone() throws CloneNotSupportedException {\r\n" +
" return super.clone();\r\n" +
" }", ctClass);
// set the annotation to method
cloneMethod.getMethodInfo().addAttribute(attr);
ctClass.addMethod(cloneMethod);
13、添加getter和setter方法
// 13、添加getter和setter方法
ctClass.addMethod(CtNewMethod.getter("getField1", ctClass.getField("field1")));
ctClass.addMethod(CtNewMethod.getter("getField2", ctClass.getField("field2")));
ctClass.addMethod(CtNewMethod.setter("setField1", ctClass.getField("field1")));
ctClass.addMethod(CtNewMethod.setter("setField2", ctClass.getField("field2")));
14、写入文件
还有其它重载方法。
// 14、写入文件
// ctClass.writeFile
ctClass.writeFile("C:/Users/dell/Desktop/javassist");
15、获取字节码
还有其它重载方法,可以直接写入到输出流。
// 15、获取字节码
// ctClass.toBytecode
16、使用案例(基本上是基于反射),
// 16、使用案例(基本上是基于反射)
// 获取class对象
Class<?> clazz = ctClass.toClass();
// 使用默认构造函数创建对象
Object instance = clazz.newInstance();
// 获取静态方法并调用
Method staticMethod = clazz.getMethod("staticMethod", null);
staticMethod.invoke(instance, null);
// 获取公开成员方法并调用
Method method2 = clazz.getMethod("method2", String.class);
method2.invoke(instance, "zhh");
17、修改方法体
// 17、修改方法体
/*
* 解冻:
* 当CtClass 调用writeFile()、toClass()、toBytecode() 这些方法的时候,
* Javassist会冻结CtClass Object,对CtClass object的修改将不允许。
* 这个主要是为了警告开发者该类已经被加载,而JVM是不允许重新加载该类的。)
*/
ctClass.defrost();
// 重命名
ctClass.setName("cn.zhh.javassist.newClass2");
// 修改(还有其它insertXxx方法)
CtMethod oldMethod2 = ctClass.getDeclaredMethod("method2");
oldMethod2.insertBefore("System.out.println(\"哎哟\");");// 在前面插入
oldMethod2.insertAfter("System.out.println(\"哈哈哈\");", true);// 插入到最后
// 重新加载
Class<?> newClazz = ctClass.toClass();
Method newMethod2 = newClazz.getMethod("method2", String.class);
newMethod2.invoke(newClazz.newInstance(), "zhh");
将生成的class文件用jd-gui反编译出来,效果如下
四、其它
1、javassist生成动态代理可以使用两种方式:
- 代理工厂(ProxyFactory)创建,跟普通的JDK动态代理和CGLIB类似
- 使用动态代码创建,跟上面的"17、修改方法体"类似