Dubbo的整个扩展机制最核心的类就是ExtensionLoader。在这个类里面实现了配置加载、扩展类缓存、自适应对象生成等所有工作。
1 工作流程
ExtensionLoader 的逻辑入口可以分为getExtension、getAdaptiveExtension、getActivateExtension三个,分别是获取普通扩展类、获取自适应扩展类、获取自动激活的扩展类。总体逻辑都是从调用这三个方法开始的,每个方法可能会有不同的重载的方法,根据不同的传入参数进行调整,流程如下
三个入口中,getActivateExtension对getExtension的依赖比较重,getAdaptiveExtension则相对独立。
- getAdaptiveExtension:根据不同的条件同时激活多个普通扩展类。因此,该方法中只会做一些通用的判断逻辑,如接口是否包含@Activate注解、匹配条件是否符合等。最终还是通过调用getExtension方法获得具体扩展点实现类。
- getExtension:整个扩张加载器中最核心的方法,实现了一个完整的普通扩展类加载过程。加载过程中的每一步,都会先检查缓存中是否已经存在所需的数据,如果存在则直接从缓存中读取,没有则重新加载。这个方法每次只会根据名称返回一个扩展点实现类。初始化的过程分为四步:
- 框架读取SPI对应路径下的配置文件,并根据配置加载所有扩展类并缓存(不初始化)。
- 根据传入的名称初始化对应的扩展类。
- 尝试查找符合条件的包装类:包含库站点的setter方法,例如setProtocol(Protocol protocol)方法会自动注入protocol扩展点实现;包含与扩展点类型相同的构造函数,为其注入扩展类实例,例如本次初始化了一个Class A,初始化完成后,会寻找构造函数中需要Class A的包装类(Wrapper),然后注入Class A实例,并初始化这个包装类。
- 返回对应的扩展类实例。
- getAdaptiveExtension:相对独立。只有加载配置信息部分与getExtension共用同一个方法。和获取普通扩展类一样,框架会先检查缓存中是否有已经初始化好的Adaptive实例,没有则调用createAdaptiveExtension重新初始化。初始化过程分为四步:
- 和getExtension一样先加载配置文件。
- 生成自适应类的代码字符串。
- 获取类加载器和编译器,并用编译器编译刚才生成的代码字符串。Dubbo一共三种类型的编译器实现,后面详细分析。
- 返回对应的自适应类实例。
接下来,我们从源码角度详细分析getExtension、getAdaptiveExtension、getActivateExtension这三个流程的实现。
2 getExtension的实现原理
当调用getExtension(String name)方法时,会先检查缓存中是否有现成的数据,没有则调用createExtension开始创建。这里有个特殊点,如果getExtension传入的name 是true,则加载并返回默认扩展类。
在调用createExtension开始创建的过程中,也会先检查缓存中是否有配置信息,如果不存在扩展类,则会从META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/这几个路径中读取所有配置文件,通过I/O读取字符流,然后通过解析字符串,得到配置文件中对应的扩展点实现类的全称。扩展点配置信息加载过程的源码如下:
private Map<String, Class<?>> getExtensionClasses() {
// 先尝试从缓存中获取classes,没有则调用
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 开始加载 Class
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private Map<String, Class<?>> loadExtensionClasses() {
// 提取并缓存默认扩展名(如果存在)
cacheDefaultExtensionName();
.....
// 加载路径下面的SPI配置文件
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
.....
}
private void cacheDefaultExtensionName() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
// 检查是否有SPI注解。如果有,则获取注解中填写的名称,并缓存为默认实现名。
// 如@SPI("impl")会保存impl为默认实现
.......
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
......
// 通过getResources或者getSystemResources得到配置文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
......
// 循环遍历urls,解析字符串,得到扩展类实现,并加入缓存
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
加载完扩展点配置后,再通过反射获得所有扩展实现类并缓存起来。注意,此处仅仅是把Class 加载到JVM中,但并没有做Class 初始化。在加载Class文件时,会根据Class上的注解来判断扩展点类型,再根据类型分类做缓存。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
.....
// 如果是自适应类(Adaptive)则缓存,缓存的自适应类只能有一个
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
// 如果是包装扩展类(Wrapper),则直接加入包装扩展类的Set集合
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
........
String[] names = NAME_SEPARATOR.split(name);
// 如果是自动激活类,则缓存到自动激活的缓存中去
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
// 不是自适应类型,也不是包装类型,剩下的就是普通扩展类了,也会缓存起来
// 注意:自动激活也是普通扩展类的一种,只是会根据不同条件同时激活罢了
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
最后根据传入的name找到对应的类并通过Class.forName方法进行初始化,并为其注入依赖的其他扩展类(自动加载特性)。当扩展类初始化后,会检查一次包装扩展类Set<Classes<?>> wrapperClasses,查找包含与扩展点类型相同的构造函数,为其注入刚初始化的扩展类,并初始化这个包装类。
private T createExtension(String name, boolean wrap) {
.......
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 遍历扩展点包装类,用于初始化包装类实例
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance =
// 找到构造方法参数类型为type(扩展类的类型)的包装类,为其注入扩展类实例
injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
........
}
在injectExtension方法中可以为类注入依赖属性,它使用了ExtensionFactory#getExtension(Class<T> type, String name)来获取对应的bean实例,这个工厂接口后面也会着重分析。
injectExtension方法总体实现了类似spring IOC机制,其实原理比较简单:首先通过反射获取类的所有方法,然后遍历以字符串set开头的方法,得到set方法的参数类型,再通过ExtensionFactory寻找参数类型相同的扩展类实例,如果找到,就设值进去。
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
// 反射遍历所有方法
for (Method method : instance.getClass().getMethods()) {
// 找到以set方法开头的方法。要求只有一个参数,并且是public方法
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
// 过滤设置不要自动注入的
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 得到参数类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 通过字符串截取,获得小写开头的类名
String property = getSetterProperty(method);
// 通过ExtensionFactory获取实例
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 如果获取了这个扩展类实例,则反射调用set方法,把实例注入进去
method.invoke(instance, object);
}
........
return instance;
}
从源码中可以知道,包装类的构造函数参数注入也是通过injectExtension方法实现的。
3 getAdaptiveExtension的实现原理
在之前的流程我们可以知道,在getAdaptiveExtension()方法中,会为扩展点接口自动生成实现类字符串,实现类主要包含以下逻辑:为接口中每个有@Adaptive注解的方法生成默认实现(没有注解的方法则生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后,框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。
生成代码的逻辑主要分为7步,具体如下:
- 生成package、import、类名称等头部信息。此处只会引入一个类ExtensionLoader。为了不写其他类的import方法,其他方法调用时全部使用全路径。类名称会变为“接口名称+ $Adaptive”的格式。例如:Transport接口会生成Transport$Adaptive。
- 遍历接口的所有方法,获取方法的返回类型、参数类型、异常类型等。为第三步判断是否为空的校验。
- 生成校验位空校验代码,如参数是否为空的校验。如果有远程调用,还会添加Invocation参数为空的校验。
- 生成默认实现类名称。如果@Adaptive注解中没有设定默认值,则根据类名称生成,如YyyInvokerWrapper会被转换为yyy.invoker.wrapper。生成的规则是不断找大写字母,并把它们用“.”连接起来。得到默认实现类名称后,还需要知道是哪个扩展点的
- 生成获取扩展点名称的代码。根据@Adaptive注解中配置的key值生成不同的获取代码。
- 生成获取具体扩展类代码。最终通过getExtension(extName)方法获取自适应扩展类的真正实现。如果根据URL中配置的key没有找到对应的实现类,则会使用第四步中生成的默认实现类名称去找。
- 生成调用结果代码。
我们用Dubbo源码中自带的一个单元测试来演示代码生成过程
// SPI配置文件中的配置
impl1=org.apache.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl1
@SPI("impl")
public interface SimpleExt{
@Adaptive
String echo(URL url,String s)
}
// 测试调用
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.Class).getAdaptiveExtension();
// 生成的自适应代码
// 只会import一个类,其他都是以全路径的方式调用的
package org.apache.dubbo.common.extensionloader.adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adpative implements org.apache.dubbo.common.extensionloader.ext1.SimpleExt {
public java.lang.String echo( org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("simple.ext", "impl1");
if (extName == null)
throw new IllegalStateException(".......");
org.apache.dubbo.common.extensionloader.ext1.SimpleExt extension = (org.apache.dubbo.common.extensionloader.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
// 最终调用的真实的扩展点方法,并返回结果
return extension.echo(arg0, arg1);
}
}
生成完代码之后就要对代码进行编译,生成一个新的Class。Dubbo中编译器也是一个自适应接口,但@Adaptive注解是加在实现类AdaptiveCompiler上的。这样一来AdaptiveCompiler就会作为该自适应类的默认实现,不需要再做代码生成和编译就可以使用了。
如果一个接口既有@SPI("impl")注解,方法上又有@Adaptive("impl2")注解,那么会以哪个key作为默认实现呢?最终动态生成的实现方法会优先通过@Adaptive注解传入的key去查找扩展实现类;如果没找到,则通过@SPI注解中的key去查找;如果@SPI注解中没有默认值,则把类名转化为key,再去查找。
3 getActivateExtension的实现原理
入口方法getActivateExtension(URL url,String key, String group)可以获取所有自动激活扩展点。参数分别是URL、URL中指定的key(多个则用逗号隔开)和URL中指定的组信息(group)。其实实现逻辑非常简单,当调用该方法时,主线流程分为4步:
- 检查缓存,如果缓存中没有,则初始化所有扩展类实现的集合。
- 遍历整个@Activate注解集合,根据传入的URL匹配条件(匹配 group、name等),得到所有符合激活条件的扩展类实现。然后根据@Activate中配置的before、after、order等参数进行排序
- 遍历所有用户自定义扩展类名称,根据URL配置顺序,调整扩展点激活顺序(遵循用户在URL中配置的顺序,例如URL为test://localhost/test?ext=order1,default,则扩展点ext激活顺序会遵循先order1再default,其中default代表所有有@Activate注解的扩展点)。
- 返回所有自动激活类集合。
获取Activate扩展类实现,也是通过getExtension得到的,因此可以认为getExtension是其他两种Extension的基石。
此处有一点需要注意,如果URL的参数中传入了-default,则所有的默认@Activate都不会被激活,只有URL参数中指定的扩展点会被激活。如果传入了“-”符号开头的扩展点名,则该扩展点也不会被自动激活。
4 ExtensionFactory的实现原理
ExtensionLoader类是整个SPI的核心,但是ExtensionLoader类本身又是 如何被创建的呢?其本身是通过工厂方法ExtensionFactory创建的,并且这个工厂接口上也有SPI注解,还有多个实现
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
......
if (loader == null) {
// new ExtensionLoader
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
// 通过SPI自适应获取扩展类实现,ExtensionFactory就是一个扩展点
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);
}
工厂接口有多个实现,那么是怎么确定使用哪个工厂实现的呢?
我们可以看到AdaptiveExtensionFactory这个实现类工厂上有@Adaptive注解。因此,AdaptiveExtensionFactory会作为一开始的默认实现。
除了AdaptiveExtensionFactory,还有SpiExtensionFactory和SpringExtensionFactory两个工厂。也就是说,我们除了可以从Dubbo SPI管理的容器中获取扩展点实例,还可以从spring容器中获取。那么Dubbo 和spring容器之间是如何打通的呢?
我们先来看看SpringExtensionFactory的实现,该工厂提供了保存spring上线文的静态方法,可以吧spring上下文保存到set集合中。当调用getExtension获取扩展类时,会遍历set集合所有spring上下文,先根据名字依次从每个spring容器中进行匹配,如果根据名字没匹配到,则根据类型去匹配,如果还没匹配到,则返回null。
public class SpringExtensionFactory implements ExtensionFactory {
// 去重的set集合保存spring上下文
private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
// 提供保存spring上下文的方法
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
}
}
// 移除
public static void removeApplicationContext(ApplicationContext context) {
CONTEXTS.remove(context);
}
public static Set<ApplicationContext> getContexts() {
return CONTEXTS;
}
// currently for test purpose
public static void clearContexts() {
CONTEXTS.clear();
}
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
//有SPI注解要走SpiExtensionFactory
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
// 遍历所有spring上下文,先根据名字从spring容器中查找
// 如果根据名字没找到,则直接通过类型查找
for (ApplicationContext context : CONTEXTS) {
T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
if (bean != null) {
return bean;
}
}
return null;
}
}
那么spring的上下文又是在什么时候被保存起来的呢?我们可以通过代码搜搜得知,在RefrenceBean和ServiceBean中会调用静态方法保存spring上下文,即一个服务被发布或者被引用的时候,对应的spring上下文会被保存起来。
接下来我们看看SpiExtensionFactory,主要就是获取扩展点接口对应的Adaptive实现类。例如:某个扩展点实现类ClassA 上有@Adaptive注解,则调用SpiExtensionFactory#getExtension会直接返回Class A实例
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
// 是接口类型并标有SPI注解
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
// 根据类型获取所有的扩展点加载器
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
// 如果缓存的扩展点类不为空,则直接返回Adaptive实例
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
public Set<String> getSupportedExtensions() {
Map<String, Class<?>> clazzes = getExtensionClasses();
return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
}
private Map<String, Class<?>> getExtensionClasses() {
// 从cachedClasses拿
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
这里作一下补充:ExtensionLoader中会缓存两个与@Adaptive有关的对象,一个缓存在cachedAdaptiveClass中,即Adaptive具体实现类的Class类型;另外一个缓存在cachedAdaptiveInstance中,即Class的具体实例化对象。在扩展点初始化时,如果发现实现类有@Adaptive注解,则直接赋值给cachedAdaptiveClass,后续实例化类的时候,就不会再动态生成代码,直接实例化cachedAdaptiveClass,并把实例缓存到cachedAdaptiveInstance中。如果注解在接口方法上,则会根据参数,动态获得扩展点的实现,生成Adaptive类,再缓存到cachedAdaptiveInstance中。
经过一番流转,最终还是回到了默认实现AdaptiveExtensionFactory上,因为该工厂上有@Adaptive注解。这个默认工厂方法在构造方法中就获取了所有扩展类工厂并缓存起来,包括SpiExtensionFactory和SpringExtensionFactory。
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
// 工厂列表也是通过SPI实现的,因此可以在这里获取所有工厂的扩展点加载器
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
遍历所有的工厂名称,获取对应的工厂,并保存到factories中
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
}
被AdaptiveExtensionFactory缓存的工厂会通过TreeSet进行排序,SPI排在前面,Spring排在后面。当调用getExtension方法时,会遍历所有的工厂,先从SPI容器中获取扩展类;如果没找到,则再从spring容器中查找。我们可以理解为,AdaptiveExtensionFactory持有了所有的具体工厂实现,它的getExtension方法中只是遍历了它持有的所有工厂,最终还是调用了SPI或spring工厂实现的getExtension方法。
@Override
public <T> T getExtension(Class<T> type, String name) {
// 遍历所有工厂进行查找,顺序是SPI->spring
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
5 扩展点动态编译的实现
Dubbo SPI的自适应特性让整个框架非常灵活,而动态编译又是自适应特性的基础,因为动态生成的自适应类只是字符串,需要通过编译才能得到真正的Class。虽然我们可以使用反射来动态代理一个类,但是性能上和直接编译好的Class 会有一定差距。Dubbo SPI 通过代码的动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。
Dubbo中有三种代码编译器,分别是JDK编译器、Javassist编译器和AdaptiveCompiler编译器。这几种编译器都实现了Compiler接口。Compiler接口有SPI注解标注,默认值是javassist,很明显Javassist编译器作为默认编译器。如果用户想改默认编译器,可以修改配置文件dubbo.application.compiler属性
AdaptiveCompiler上有@Adaptive注解,说明AdaptiveCompiler会固定为默认实现,这个Compiler的主要作用和AdaptiveExtensionFactory相似,就是为了管理其他Compiler
@Adaptive
public class AdaptiveCompiler implements Compiler {
private static volatile String DEFAULT_COMPILER;
// 设置默认的编译器名称
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
......
// 通过ExtensionLoader获取对应的编译器扩展类实现,并调用真正的compiler做编译
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
.......
return compiler.compile(code, classLoader);
}
}
AdaptiveCompiler #setDefaultCompiler方法会在ApplicationConfig中被调用,也就是Dubbo在启动时,会解析配置中dubbo.application.compiler属性,获取设置的值,初始化对应的编译器。如果没有设置,则使用@SPI("javassist")中的设置,即JavassistCompiler。
观察JavassistCompiler和JdkCompiler都继承了一个抽象类AbstractCompiler,里面封装了通用的模板逻辑。还定义了一个抽象方法doCompile,留给子类来具体实现具体的编译逻辑。
AbstractCompiler的主要抽象逻辑如下:
- 通过正则匹配出包路径、类名,再根据包路径、类名拼接出全路径类名
- 尝试通过Class.forName加载该类并返回,防止重复编译。如果类加载器中没有这个类,则进入第三步
- 调用doCompile方法进行编译。这个抽象方法由子类实现
public abstract class AbstractCompiler implements Compiler {
// 包路径正则表达式
private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");
// 类路径正则表达式
private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
.......
// 正则匹配并拼接类的全限定名
return Class.forName(className, true, org.apache.dubbo.common.utils.ClassUtils.getCallerClassLoader(getClass()));
} catch (ClassNotFoundException e) {
if (!code.endsWith("}")) {
throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
}
try {
return doCompile(className, code);
} catch (RuntimeException t) {
...............
}
}
protected abstract Class<?> doCompile(String name, String source) throws Throwable;
}
Javassist动态代码编译
Java中动态生成Class的方式有很多,可以直接基于字节码的方式生成,常见的工具库有CGLIB、ASM、Javassist等。而自适应扩展点使用了生成代码串代码再编译为Class的方式。
在JavassistCompiler中,就是不断通过正则表达式匹配不同部位的代码,然后调用Javassist库中的API生成不同部位的代码,最后得到一个完整的Class对象。具体步骤如下:
- 初始化Javassist,设置默认参数,如设置当前的classpath
- 通过正则匹配出所有的import包,并使用Javassist添加import
- 通过正则匹配出所有extends的包,创建Class对象,并使用Javassist添加extends
- 通过正则表达式匹配出所有implements包,并使用Javassist添加imples
- 通过正则匹配出类里面所有内容,即得到{}中内容,再通过正则匹配出所有方法,并使用Javassist添加类方法。
- 生成Class 对象
Jdk动态代码编译
原生JDK编译器包位于javax.tools下。主要使用了三个东西:JavaFileObject接口、ForwardingJavaFileManager接口、JavaCompiler.CompilationTask方法。整个动态编译过程可以简单地总结为:
首先初始化为一个JavaFileObject对象,并把代码字符串作为参数传入构造方法,然后调用JavaCompiler.CompilationTask方法编译出具体的类。JavaFileManager负责管理类文件的输入/输出位置。
- JavaFileObject接口。字符串代码会被包装为一个文件对象并提供获取二进制流的接口。Dubbo框架中的JavaFileObjectImpl类可以看作该接口一种扩展实现,构造方法中需要传入生成好的字符串代码,此文件对象的输入和输出都是ByteArray流。
- JavaFileManager接口。主要管理文件的读取和输出位置。JDK中没有可以直接使用的实现类,唯一的实现类ForwardingJavaFileManager构造器又是protect类型。因此Dubbo中定制化实现了一个JavaFileManagerImpl类,并通过一个自定义类加载器ClassLoaderImpl完成资源的加载
- JavaCompiler.CompilationTask把JavaFileObject对象编译成具体的类。
- 本文就到此结束了,我们下文见,谢谢
- github: honey开源系列组件作者