四、ExtensionFactory的实现原理
ExtensionLoader是整个SPI的核心,但是ExtensionLoader本身如何被创建的呢?
我们知道RegistryFactory工厂类是通过@SPI(“protocol”)注解动态查询注册中心实现,根据URL中的protocol参数动态选择对应的注册中心工厂,并初始化具体的注册中心客户端。而实现这个特性的ExtensionLoader类,本身又是通过工厂方法ExtensionFactory创建的,并且这个工厂接口上也有SPI注解,还有多个实现。
@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);
}
共有三个实现类,除了从Dubbo SPI管理的容器中获取扩展点实例,还可从Spring容器中获取。
- 1、该工厂类提供了保存Spring上下文的静态方法,可以把Spring上线文保存在set集合
- 2、调用getExtension获取扩展类时,会遍历set集合中所有的Spring上下文,先根据名字一次从每个Spring容器中进行匹配.
- 3、如果根据名字没有匹配到,在根据类型去匹配
- 4、如果没匹配到返回null
public class SpringExtensionFactory implements ExtensionFactory {
//set集合保存Spring上下文
private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
//保存入set
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 should be get from 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;
}
}
logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
return null;
}
}
Spring的上下文是什么时候被保存的呢,在ReferenceBean和ServiceBean中会调用静态方法保存Spring上下文,即一个服务被发布或被引用时,对应的Spring上下文会被保存下来。
SpiExtensionFactory主要是获取扩展点接口对应的Adaptive实现类。如某个扩展点实现类ClassA上有@Adaptive注解,则调用SpiExtensionFactory#getExtension会直接返回ClassA实例。
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
//如果缓存的扩展点类不为空,则直接返回Adaptive实例
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
经过一番流转,最终还是回到了默认实现AdaptiveExtensionFactory上,因为该工厂上有 @Adaptive注解。这个默认工厂在构造方法中就获取了所有扩展工厂并缓存起来,包括SpiExtensionFactory和SpringExtensonFactory。
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
//TODO:Collections.unmodifiableList(list);后续详细了解
factories = Collections.unmodifiableList(list);
}
- 1、被缓存的工厂会通过TreeSet进行排序,SPI排在前面,Spring排在后面。
- 2、当调用getExtension方法时,会遍历所有的工厂,先从SPI容器中获取扩展类;
- 3、如果没找到,则再从Spring容器中查找。
- 4、可以理解为,AdaptiveExtensionFactory持有了所有的具体工厂实现,它的getExtension方法中只是遍历了它持有的所有工厂,最终还是调用SPI或者Spring工厂实现的getExtension方法。
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
五、扩展点动态编译的实现
Dubbo SPI的自适应特性让整个架构非常灵活,而动态编译又是自适应特性的基础,因为动态生成的自适应类知识字符串,需要通过编译才能得到真正的Class。虽然我们可以使用反射来带动态代理一个类,但是在性能上和直接编译好的Class会有一定的差距。Dubbo SPI通过代码的动态生成,并配合动态编译器,灵活地在原始类基础上创建新的自适应类。
1、总体结构
Dubbo目前有三种代码编译器,JDK编译器,javassist编译器和AdaptiveCompiler编译器。这几种都实现了Compiler接口,关系如下:
@Override
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
//通过ExtensionLoader获取对应的编译器扩展类实现,并调用真正的compile做编译。
return compiler.compile(code, classLoader);
}
AdaptiveCompiler#setDefaultCompiler方法会在ApplicationConfig中被调用,也就是Dubbo启动时,会解析配置中的<dubbo:application compiler="jdk"/>标签,获取设置的值,初始化对应的编辑器,如果没有标签设置,则使用@SPI("javassist")中的设置。 AbstractCompiler,是一个抽象类,无法实例化。但是封装了通用的模板逻辑。定义了一个抽象方法doCompile,留给子类来实现具体的逻辑。
- 1、通过正则匹配出包路径、类名,然后根据包路径、类名拼接处全路径类型。
- 2、尝试通过Class.forName加载该类并返回,防止重复编译。如果类加载器中没有这个类。
- 3、调用doCompile方法进行编译。由子类实现。
2、javassist
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("TestSsist");
CtMethod ctMethod = CtNewMethod.make(
"public static void test(){ System.out.println(\"Hello\");}"
, ctClass);
ctClass.addMethod(ctMethod);
Class aClass = ctClass.toClass();
Object o = aClass.newInstance();
Method m = aClass.getDeclaredMethod("test", null);
m.invoke(o,null);
- 1、初始化javassist的池类
- 2、创建一个TestSsist的类
- 3、添加一个test方法,打印Hello
- 4、生成类
- 5、通过反射调用这类实例
3、JDK动态代理
- 1、JavaFileObject接口,ForwardingJavaFileManager接口,JavaCompilerCompilationTask方法。
- 2、初始化一个JavaFileObject对象,并把代码字符串作为参数传入构造方法,然后调用JavaCompiler.CompilationTask方法编译出具体的类。
- 3、JavaFileManager负责管理类文件的输入/输出位置。
总结:
首先介绍了Dubbo SPI的一些概要信息,包括Java SPI的区别、Dubbo SPI的新特性、配置规范和内部缓存等。其次介绍了三个注解:@SPI、@Adaptive、@Activate,讲解了注解的作用和实现原理。然后结合ExtensionLoader类的源码介绍了整个Dubbo SPI中最关键的三个入口:getExtension、getAdaptiveExtension、getActivateExtension,并讲解了创建ExtensionFactory的工厂的作用原理。最后还有javassist和JDK动态编译的原理。