解读Dubbo源码——Javassist

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/85395997

一、背景

编程语言有静态语言和动态语言之分,例如: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 = $1;this.field2 = $2;}";
		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 = $1;\r\n" + 
				"		this.field2 = $2;\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 \" + $1;\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生成动态代理可以使用两种方式:

  1. 代理工厂(ProxyFactory)创建,跟普通的JDK动态代理和CGLIB类似
  2. 使用动态代码创建,跟上面的"17、修改方法体"类似

2、这里有对javassist更详细的介绍:https://www.cnblogs.com/sunfie/p/5154246.html

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/85395997