三、Apache Dubbo学习整理---扩展点加载机制(3)

390 阅读5分钟

四、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接口,关系如下:

Compiler接口含有一个SPI,默认值是“javassist”,如果想改变,通过<dubbo:application compiler="jdk"/>改变 AdaptiveCompiler有个@Adaptive注解,说明AdaptiveCompiler会固定为默认实现,主要作用和AdaptiveExtensionFactory相似,就是为了管理其他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动态编译的原理。