前言:
在Dubbo中,大量使用动态代理相关技术。动态代理主要是基于JDK的动态代理和Javassist的动态代理。
有关于JDK动态代理的使用及源码解析可以参考上文。
本文着重来介绍下Javassist的使用,及其动态代理的实现。
1.Javassist简介
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。(来自百度百科)
通过这个介绍,我们了解到javassist可以操作java字节码,而通过操作字节码,还能实现动态代理。
2.Javassist的基本使用
2.1 引入maven依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
2.2 创建实体类
package xw.demo.proxy.javassist;
public class Student {
private String name;
private int age;
// 省略get set方法
public String study(String book) {
return name + "study " + book;
}
}
2.3 基本API使用
2.3.1 ClassPool加载已知类
ClassPool pool = ClassPool.getDefault();
// 加载Student
CtClass studentClass = pool.get("xw.demo.proxy.javassist.Student");
2.3.2 CtField获取属性
CtField nameField = studentClass.getField("name");
// 获取属性类型,本例中即为java.lang.String
String nameType = nameField.getType().getName();
// 获取属性注解
Object[] annotations = nameField.getAnnotations();
...
2.3.3 CTMethod获取方法信息
CtMethod method = studentClass.getDeclaredMethod("study");
// 获取方法返回类型
CtClass returnType = method.getReturnType();
// 获取方法参数类型
CtClass[] parameterTypes = method.getParameterTypes();
2.3.4 CtConstructor获取构造方法信息
CtConstructor[] declaredConstructors = studentClass.getDeclaredConstructors();
if (null != declaredConstructors) {
for (CtConstructor ctConstuctor : declaredConstructors) {
// 获取构造方法名称
String name = ctConstuctor.getName();
// 获取构造方法参数类型
CtClass[] ctConstuctorParameterTypes = ctConstuctor.getParameterTypes();
}
}
javassist的使用还是比较简单的。但这只是开胃菜,真正重要的是其用来动态创建一个新的类。下面我们就用Javassist来创建一个新的Student类,与上述有一样的属性和方法。
2.3.5 javassist创建新的类
public static void newClassTest() {
ClassPool pool = ClassPool.getDefault();
// 创建class信息
CtClass studentClass = pool.makeClass("xw.demo.proxy.javassist.Student_copy");
try {
// 创建name属性
CtField nameField = new CtField(pool.get("java.lang.String"), "name", studentClass);
nameField.setModifiers(Modifier.PUBLIC);
studentClass.addField(nameField);
// 创建age属性
CtField ageField = new CtField(CtClass.intType, "age", studentClass);
ageField.setModifiers(Modifier.PUBLIC);
studentClass.addField(ageField);
CtMethod studyMethod = new CtMethod(pool.get("java.lang.String"),
"study", new CtClass[]{pool.get("java.lang.String")}, studentClass);
studyMethod.setModifiers(Modifier.PUBLIC);
studyMethod.setBody("return name + \"study \" + $1;");
studentClass.addMethod(studyMethod);
// 写出到文件
studentClass.writeFile("D:\\test");
} catch (CannotCompileException | IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
}
最终会生成一个Student_copy.class,具体信息如下
package xw.demo.proxy.javassist;
public class Student_copy
{
public String name;
public int age;
public String study(String paramString)
{
return this.name + "study " + paramString;
}
}
2.4 动态代理的创建
之前有写过通过JDK proxy来实现动态代理的文章,使用JDK自带的还是比较方便的。那么当我们使用Javassist该如何实现动态代理呢,实际也是很简单的。直接看示例。
2.4.1 创建示例方法
// 创建被代理类
public class EchoService {
public String echo(String msg) {
return "echo " + msg;
}
}
public static void proyTest() {
ClassPool pool = new ClassPool();
try {
CtClass echoServiceClass = pool.get("xw.demo.proxy.javassist.EchoService");
// 获取其echo方法,并在方法前后添加执行语句
CtMethod echoMethod = echoServiceClass.getDeclaredMethod("echo");
echoMethod.insertBefore("before echo...");
echoMethod.insertAfter("after echo...");
// 执行echo方法
EchoService echoService = (EchoService)echoServiceClass.toClass().newInstance();
String world = echoService.echo("world");
System.out.println(world);
} catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
// 执行结果:
before echo...
echo world
after echo...
Javassist通过对字节码的执行,在方法执行的前后添加自定义代码,就可以实现对方法的动态代理。
2.4.2 Dubbo中Javassist动态代理的使用
Dubbo使用Javassist来实现动态代理,代码如下:
public class JavassistProxyFactory extends AbstractProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 具体可见getProxy()方法
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
}
具体细节笔者就不再展示,也是通过动态生成一个Proxy类,来实现对invoker的动态代理的。