扩展点动态编译的实现
Dubbo SPI的自适应特性让整个框架非常灵活,而动态编译又是自适应特性的基础,因为动态生成的自适应类只是字符串,需要通过编译才能得到真正的Class。虽然我们可以使用反射来动态代理一个类,但是在性能上和直接编译好的Class会有一定差距。Dubbo SPI通过代码的动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。
总体结构
Dubbo中有三种代码编译器,分别是JDK编译器、Javassist编译器和AdaptiveCompiler编译器。这几种编译器都实现了Compiler接口,编译器类之间的关系如下图:

Compiler接口上含有一个SPI注解,注解的默认值是@SPI("javassist"),很明显,Javassist编译器将作为默认编译器。如果用户想改变默认编译器,则可以通过<dubbo:application compiler="jdk" />标签进行设置。
AdaptiveCompiler上面有@Adaptive注解,说明AdaptiveCompiler会固定为默认实现,这个Compiler的主要作用和AdaptiveExtensionFactory相似,就是为了管理其他compiler,如下图所示:

AdaptiveCompiler#setDefaultCompiler方法会在ApplicationConfig中被调用,也就是Dubbo在启动时,救护解析<dubbo:application compiper="jdk" />标签,获取设置的值,初始化对应的编译器。如果没有标签设置,则使用@SPI("javassist")中的设置,即javassistCompiler。
然后看一下AbstractCompiler,它是一个抽象类,无法实例化,但在里面封装了通用的模板逻辑。还定义了一个抽象方法doCompile,留给子类实现的编译逻辑。JavassistCompiler和JDKCompiler都实现了这个抽象方法。
AbstractCompiler的主要抽象逻辑如下:
- 通过正则匹配出包路径、类名,再根据包路径、类名拼接处全路径类名。
- 尝试通过Class.forName记载该类并返回,防止重复编译。如果类加载器中没有该类,则进入第三步。
- 调用doCompiler方法进行编译。
Javassist动态代码编译
Java中动态生成Class的方式有恩多,可以直接基于字节码的方式生成,常见的工具库有CGLIB、ASM、Javassist等。而自适应扩展点使用了生成字符串代码再编译为Class的方式。
Javassist使用示例:
@Test
public void test_javassist() throws CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//初始化Javassist类池
ClassPool classPool = ClassPool.getDefault();
//创建一个Hello World类
CtClass ctClass = classPool.makeClass("Hello World");
//添加一个test方法,会打印Hello World,直接传入方法的字符串
CtMethod method = CtMethod.make("" +
"public static void test(){" +
"System.out.println(\"Hello World\");" +
"}", ctClass);
ctClass.addMethod(method);
//生成类
Class aClass = ctClass.toClass();
//通过反射调用这个类实例
Object object = aClass.newInstance();
Method m = aClass.getDeclaredMethod("test", null);
m.invoke(object, null);
}

由于之前已经生成了代码字符串,因此在JavassistCompiler中,就是不断通过正则表达式匹配不同部位的代码,然后调用Javassist库中的API生成不同部位的代码,最后得到一个完整的Class对象。
具体步骤如下:
- 初始化Javassist,设置默认参数,如设置当前的classpath。
- 通过正则匹配出所有import的包,并使用Javassist添加import。
- 通过正则匹配出所有extends的包,创建Class对象,并使用Javassist添加extends。
- 通过正则匹配出所有implements包,并使用Javassist添加implements。
- 通过正则匹配出类里面所有内容,即得到{}中的内容,再通过正则匹配出所有方法,并使用Javassist添加类方法。
- 生成Class对象。
JDK动态代码编译
JdkCompiler是Dubbo编译器的另一种实现,使用了JDK自带的编译器,原生JDK编译器包位于java.tools下。主要使用了三个东西:JavaFileObject接口、ForwardingJavaFileManager接口、JavaCompiler.CompilationTask方法。
整个动态编译过程可以简单地总结为:首先初始化一个JavaFileObject对象,并把字符串作为参数传入构造方法,然后调用JavaCompiler.CompilationTask方法编译出具体的类。JavaFileManager负责管理类文件输入/输出的位置。
-
JavaFileObject接口:字符串代码会被包装成一个文件对象,并提供获取二进制流的接口。Dubbo框架中的JavaFileObjectImpl类可以看做该接口的一种扩展实现,构造方法中需要传入生成好的字符串代码,此文件对象的输入和输入都是ByteArray流。 -
JavaFileManager接口:主要管理文件的读取和输出位置。JDK中没有可以直接使用的实现类,唯一的实现鳄梨ForwardingJavaFileManager构造器又是protect类型。因此Dubbo中定制化实现了一个JavaFileManagerImpl类,并通过一个自定义类加载器ClassLoaderImpl完成资源加载。 -
JavaCompiler.CompilationTask:把JavaFileObject对象编译层具体的类。
本文探讨了Dubbo框架中动态编译的实现原理,包括Javassist与JDK编译器的应用,以及AdaptiveCompiler的角色。介绍了动态编译在自适应特性中的关键作用,通过代码生成与编译提升框架灵活性。
1416

被折叠的 条评论
为什么被折叠?



