Dubbo源码解析-动态编译javaAssist的使用

前言:

    在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的动态代理的。

上一篇:Dubbo源码解析-Dubbo服务消费者_Injvm协议(一)


下一篇:dubbo-admin 安装