Анализ исходного кода Dubbo - Dubbo SPI

задняя часть исходный код Dubbo Ruby

Даббо принимаетМикроядро + ПлагиныПринцип конструкции мода, за сборку плагина отвечает микроядро, то есть все функциональные точки Dubbo могут быть заменены пользовательскими расширениями. Микроядро реализовано механизмом Dubbo SPI, поэтому очень важно понимать Dubbo SPI.

Введение в Дуббо SPI

Dubbo SPI улучшен по сравнению со стандартным механизмом обнаружения точек расширения JDK SPI (интерфейс поставщика услуг) и устраняет следующие проблемы стандартного SPI JDK:

  • Не удалось получить указанную реализацию расширения.The JDK standard SPI will instantiate all the implementations of the extension point at one time. If there is an extension implementation, it is time-consuming to initialize, but if it is not used, it will be loaded, which will be a waste of Ресурсы.

  • Если точка расширения не загружается, даже имя точки расширения будет недоступно. Например: стандартный JDK ScriptEngine, получить имя типа сценария через getName(), но если RubyScriptEngine не может загрузить класс RubyScriptEngine, потому что jruby.jar, от которого он зависит, не существует, причина этой ошибки съедена, и она не соответствует ruby.Когда пользователь выполняет скрипт ruby, он сообщит, что ruby ​​не поддерживается, а не реальная причина сбоя.

  • Добавлена ​​поддержка точек расширения IoC и AOP. Точка расширения может напрямую внедрять сеттеры в другие точки расширения.

Если вы не понимаете механизм JDK SPI, вы можете прочитать, что я написалПодробное объяснение исходного кода JDK SPI.

Анализ исходного кода механизма Dubbo SPI

Механизм JDK SPI реализуется классом инструмента java.util.ServiceLoader, и Dubbo также предоставляет аналогичный класс (com.alibaba.dubbo.common.extension.ExtensionLoader) для реализации механизма Dubbo SPI.

Следующее содержимое в основном объясняет, как ExtensionLoader получает указанную точку расширения и адаптивную точку расширения.

Получить указанную точку расширения

Взяв в качестве примера DubboProtocol, код выглядит следующим образом:

Protocol protocol  = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

Посредством специального анализа исходного кода процесс работы выглядит следующим образом:

1. Создайте экземпляр ExtensionLoader

// Dubbo SPI只支持接口并且被@SPI修饰
ExtensionLoader loader = new ExtensionLoader(
    Protocol.class,
    ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
)

2. Получите точки расширенияВсе классы реализацииобъект класса

 /**
  * 加载META-INF/services/、META-INF/dubbo/internal/、META-INF/dubbo/目录下type.getName文件
  * 并解析内容,然后将type基本实现类(不包括包装类,没有Adaptive注解)存储在extensionClasse中。
  */
private Map<String, Class<?>> loadExtensionClasses() {
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

3. Создайте объект DubboProtocol и соберите его зависимые объекты.

// 获取名为dubbo对应的扩展点实现类
Class<?> clazz = getExtensionClasses().get(name);
// 创建对象
T instance = clazz.newInstance();
// 自动装配
injectExtension(instance);

4. Верните объект ProtocolListenerWrapper.

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
// 循环遍历初始化包装类
if (wrapperClasses != null && wrapperClasses.size() > 0) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type)
            .newInstance(instance));
    }
}
return instance;

Процесс автоматического внедрения получает объекты, от которых необходимо зависеть, которые получаются через объект ExtensionLoader.objectFactory.

Получите адаптивные точки расширения

Взяв в качестве примера приобретение адаптивной точки расширения Procotol, код выглядит следующим образом:

Protocol protocol  = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Процесс выглядит следующим образом:

// 创建Protocol自适应扩展点实例
private T createAdaptiveExtension() {
    return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}

/**
  * 之所以贴出这段代码,因为获取自适应扩展点会触发获取扩展点所有实现类的Class对象。目前
  * dubbo只有被@Adaptive注解的类仅有AdaptiveCompiler和AdaptiveExtensionFactory,因此除了
  * Complie和ExtensionFactory外都需要动态创建其自适应扩展点的Class
  */
private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
    
// 创建自适应扩展点类Class对象
private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = 
    ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class)
    .getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

Динамически сгенерированный исходный код адаптивной точки расширения Protocol$Adpative выглядит следующим образом:

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {

    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
        if (arg1 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
       
        com.alibaba.dubbo.rpc.Protocol extension = 
        (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
            .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
            .getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null)
         throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        
        com.alibaba.dubbo.rpc.Protocol extension = 
        (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
            .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
            .getExtension(extName);
        return extension.export(arg0);
    }
}

Можно обнаружить, что функция адаптивной точки расширения состоит в том, чтобы получить соответствующую точку расширения с помощью информации о параметрах URL-адреса, чтобы реализовать динамический вызов метода. Соответствует базовому дизайну Dubbo, оригинальному два:

URL-адрес используется в качестве унифицированного формата для информации о конфигурации, и все точки расширения передают информацию о конфигурации, передавая URL-адрес.