[Серия чтения исходного кода Dubbo] Механизм Dubbo SPI

Dubbo

Недавно я нашел время, чтобы начать путешествие по чтению исходного кода Dubbo, надеясь записать и поделиться своим пониманием Dubbo, написав статьи. Если в этой статье есть какие-то ошибки или ошибки, я надеюсь, вы укажете.

Внедрение Dubbo SPI

Java SPI

Прежде чем читать эту статью, вам может понадобиться краткое понимание механизма Java SPI (интерфейс поставщика услуг). Вот краткое введение: В объектно-ориентированном проектировании мы выступаем за интерфейсное программирование между модулями. Разные модули могут иметь разные конкретные реализации, но чтобы избежать чрезмерной связи между модулями, нам нужен эффективный механизм обнаружения сервиса (сервисной реализации) для выбора конкретных модулей. SPI - это такая программа, основанная на программировании интерфейса + режиме стратегии + файле конфигурации, и в то же время пользователи могут включать/заменять конкретную реализацию модуля в соответствии со своими реальными потребностями.

Улучшения Dubbo SPI

Далее выдержка изHiroshi Watanabe.git books.IO/ Hiroshi Watanabe-Dev-no…
Дабботочка расширенияЗагрузка улучшена благодаря стандартному механизму обнаружения точки расширения SPI (интерфейс поставщика услуг) JDK. Стандартный SPI JDK будет создавать экземпляры всех реализаций точек расширения одновременно.Если есть реализации расширений, инициализация занимает много времени, но если они не используются, они будут загружены, что будет пустой тратой ресурсов. Если точка расширения не загружается, даже имя точки расширения будет недоступно. Например: стандартный JDK ScriptEngine, получить имя типа сценария через getName(), но если RubyScriptEngine не может загрузить класс RubyScriptEngine, потому что jruby.jar, от которого он зависит, не существует, причина этой ошибки съедена, и она не соответствует ruby.Когда пользователь выполняет скрипт ruby, он сообщит, что ruby ​​не поддерживается, а не настоящая причина сбоя. Добавлена ​​поддержка точек расширения IoC и AOP, точка расширения может напрямую внедрять сеттеры в другие точки расширения.

В Dubbo, если интерфейс помечен аннотацией @SPI, то мы считаем его точкой расширения в Dubbo. Точка расширения является ядром Dubbo SPI.Давайте поговорим о конкретной реализации загрузки точки расширения, автоматической упаковки точки расширения и автоматической сборки точки расширения.

Подробное объяснение механизма Dubbo SPI

Загрузка точек расширения Dubbo

Перед прочтением этой статьи, если вы читали о Java SPI, вы, возможно, помните, что существует такой каталог, как /META-INF/services. В этом каталоге есть файл, названный в честь интерфейса, и содержимое файла представляет собой полное имя конкретного класса реализации интерфейса. Мы также можем найти подобный дизайн в Dubbo.

  • META-INF/services/ (совместим с JAVA SPI)
  • META-INF/dubbo/ (реализация пользовательской точки расширения)
  • META-INF/dubbo/internal/ (реализация внутренней точки расширения Dubbo)

Очень хорошо ~ теперь мы знаем, куда загружать точки расширения, давайте вспомним, как загружается JAVA SPI.

ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);

Точно так же есть такой класс ExtensionLoader для загрузки точек расширения в Dubbo. В этой главе мы сосредоточимся на том, как этот класс помогает нам загружать точки расширения. Начнем с небольшого фрагмента кода.

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

В реализации Dubbo используется большое количество подобных фрагментов кода, нам нужно только предоставить тип, чтобы получить адаптивный (понимание адаптации будет упомянуто позже) класс расширения типа. При получении соответствующего адаптивного класса расширения мы сначала получаем ExtensionLoader этого типа. Видя это, мы должны подсознательно чувствовать, что для каждого типа должен быть соответствующий объект ExtensionLoader. Давайте сначала посмотрим, как получается ExtensionLoader.

получитьExtensionLoader()

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    // 是否被 SPI 注解标识 
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //EXTENSION_LOADERS 为一个 ConcurrentMap集合,key 为 Class 对象,value 为ExtenLoader 对象
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

Код в предыдущем абзаце относительно прост.В соответствии с типом загрузчик получается из коллекции EXTENSION_LOADERS.Если возвращаемое значение равно null, создается новый объект ExtensionLoader. Получение objectFactory здесь также использует аналогичный метод для получения адаптивного класса расширения ExtensionFactory.

getAdaptiveExtension()

public T getAdaptiveExtension() {
    //cachedAdaptiveInstance用于缓存自适应扩展类实例
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        // ...
                    }
                }
            }
    }

    return (T) instance;
}

Метод getAdaptiveExtension() используется для получения текущего экземпляра класса адаптивного расширения. Сначала он будет получен из объекта cachedAdaptiveInstance. Если значение равно null, а createAdaptiveInstanceError пуст, будет вызван метод createAdaptiveExtension для создания экземпляра класса расширения. Обновите cachedAdaptiveInstance после создания.

createAdaptiveExtension()

private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        // 省略异常
    }
}

Здесь нашего внимания заслуживают два метода: injectExtension() и getAdaptiveExtensionClass(). injectExtension() выглядит как метод, реализующий функцию внедрения, а getAdaptiveExtensionClass() используется для получения конкретногоАдаптивный класс расширения. Давайте рассмотрим эти два метода по очереди.

injectExtension()

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    //如果存在 DisableInject 注解则跳过
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    //获取 method 第一个参数的类型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

Кратко подытожим, что делает этот метод: проходит метод set текущего экземпляра, использует строку с четвертой позиции до конца метода set в качестве ключа и пытается получить соответствующую реализацию класса расширения через objectFactory. Если есть соответствующий класс расширения, он будет внедрен в текущий экземпляр посредством отражения. Этот метод эквивалентен выполнению простой функции внедрения зависимостей, Мы часто говорим, что IOC в Dubbo на самом деле отражен здесь.

getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

Затем посмотрите на метод getAdaptiveExtensionClass(). Сначала вызовите метод getExtensionClasses(), верните значение, если cachedAdaptiveClass() не равно null, и вызовите метод createAdaptiveExtensionClass(), если он равен null. Посмотрите на эти два метода по очереди.

getExtensionClasses()

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

Содержание относительно простое, давайте посмотрим непосредственно на метод loadExtensionClasses().

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            // 只能有一个默认扩展实例
            if (names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

После стольких хождений я наконец добрался до темы. Почему вы говорите, что это по теме? Посмотрите на значения этих переменных

  • DUBBO_INTERNAL_DIRECTORY: META-INF/dubbo/internal/
  • DUBBO_DIRECTORY: META-INF/dubbo/
  • SERVICES_DIRECTORY: META-INF/услуги/

Знакомые рецепты, знакомые ингредиенты. . Правильно, мы собираемся начать чтение файлов в этих трех каталогах, а затем начать загрузку наших точек расширения.

loadDirectory()

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        // ...
    }
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                //#对应注释位置,剔除存在的注释
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        //文件中的内容以 key=value 的形式保存,拆分 key 和 vlaue
                        int i = line.indexOf('=');
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } catch (Throwable t) {
                        // ...
                    }
                }
            }
        } finally {
            reader.close();
        }
    } catch (Throwable t) {
        // ...
    }
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 用于判断 class 是不是 type 接口的实现类
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error when load extension class(interface: " +
                type + ", class line: " + clazz.getName() + "), class "
                + clazz.getName() + "is not subtype of interface.");
    }
    // 如果当前 class 被 @Adaptive 注解标记,更新 cachedAdaptiveClass 缓存对象
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // 省略异常
        }
    } else if (isWrapperClass(clazz)) {
    // 这里涉及到了 Dubbo 扩展点的另一个机制:包装,在后文介绍
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        // 如果 name 为空,调用 findAnnotationName() 方法。如果当前类有 @Extension 注解,直接返回 @Extension 注解value;
        // 若没有 @Extension 注解,但是类名类似 xxxType(Type 代表 type 的类名),返回值为小写的 xxx
        if (name == null || name.length() == 0) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
        String[] names = NAME_SEPARATOR.split(name);
        if (names != null && names.length > 0) {
            // @Activate 注解用于配置扩展被自动激活条件
            // 如果当前 class 包含 @Activate ,加入到缓存中
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            } else {
                // support com.alibaba.dubbo.common.extension.Activate
                com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                if (oldActivate != null) {
                    cachedActivates.put(names[0], oldActivate);
                }
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    // 还记得文件内容长啥样吗?(name = calssvalue),我们最后将其保存到了 extensionClasses 集合中
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    // ...
                }
            }
        }
    }
}

Этот фрагмент кода очень длинный. . Разобравшись, я обнаружил, что то, что он сделал, было на самом деле очень просто:

  1. Сращивание для создания имени файла: dir + type, прочитать файл
  2. Прочитайте содержимое файла, разделите содержимое файла на строки имени и класса.
  3. Если класс clazz содержит аннотацию @Adaptive, добавьте ее в кэш cachedAdaptiveClass.
    Если класс clazz является классом-оболочкой, добавьте его в обертки.
    Если файл не в форме ключ=класс, он попытается получить имя через аннотацию @Extension.
    Если clazz содержит аннотацию @Activate (совместимую с аннотацией com.alibaba.dubbo.common.extension.Activate), добавьте ее в кэш cachedActivates.
  4. Наконец, с name в качестве ключа и clazz в качестве vlaue добавьте его в коллекцию extensionClasses и верните

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

getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

Хорошо, мы профилировали метод getExtensionClasses, и реализация точки расширения загружена в кеш. Этот метод является производным от метода getAdaptiveExtensionClass(), который выглядит так, как будто он создает адаптивный класс расширения. Здесь мы сначала оценим, будет ли объект кеша cachedAdaptiveClass пустым, и когда инициализируется cachedAdaptiveClass? Просмотрите предыдущий код:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 省略...
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            // 省略...
        }
    }
}

В методе loadClass(), если обнаруживается, что текущий clazz содержит аннотацию @Adaptive, текущий clazz будет сохранен как адаптивный класс кэша. Например, в классе AdaptiveExtensionFactory мы будем кэшировать класс AdaptiveExtensionFactory как адаптивный класс типа ExtensionFactory.

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory

Мы продолжаем анализ последней части метода. Если cachedAdaptiveClass имеет значение null, будет вызван метод createAdaptiveExtensionClass() для динамического создания класса адаптивного расширения.

private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    System.out.println(code);
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

Этот кусок кода не предназначен для описания в этом обмене, можно просто понять, что dubbo помог мне сгенерировать адаптивный класс. Я извлек кусок кода, который был сгенерирован следующим образом:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);
    private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);

    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getInvoker(arg0, arg1, arg2);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0, arg1);
    }
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getProxy(arg0);
    }
}
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);

Этот фрагмент кода на самом деле является сущностью класса адаптивной адаптации. Давайте посмотрим, откуда берется extName?

String extName = url.getParameter("proxy", "javassist");

extName получается из url.На самом деле url является очень важным носителем передачи контекста для Dubbo, что будет постепенно ощущаться в последующих сериях статей.

public T getExtension(String name) {
    if (name == null || name.length() == 0) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // 从缓存中读取扩展实现类
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

Приведенная выше логика относительно проста, поэтому я не буду вдаваться в подробности, а просто взгляну на метод createExtension().

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

Метод getExtensionClasses() был проанализирован в предыдущей статье, но следует отметить, что:То, что возвращает нам getExtensionClasses, — это просто классы, загруженные с помощью Class.forName() , в лучшем случае выполняющие статический сегмент кода внутри, не получая реальный экземпляр. Объекту реального экземпляра по-прежнему необходимо вызвать метод class.newInstance(), чтобы получить его.
Поняв это, давайте продолжим смотреть, мы пытаемся получить объект класса, который был загружен системой, через getExtensionClasses(), а затем переходим к кэшу экземпляра расширения, чтобы получить его через объект класса. Если экземпляр расширения имеет значение null, вызовитеnewInstance()Метод инициализирует экземпляр и помещает его в кеш EXTENSION_INSTANCES. Затем вызовите метод injectExtension() для внедрения зависимостей. Последний абзац посвящен использованию классов-оболочек, которые будут представлены в следующей главе.

Обертка для класса расширения

В методе createExtension() есть следующий фрагмент кода:

private T createExtension(String name) {
    // ···省略···
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    // ···省略···
}

Помните, где были инициализированы wrapperClasses? Мы уже представили его в методе loadClass() выше. Резюме:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // ···省略···
    if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    }
    // ···省略···
}

private boolean isWrapperClass(Class<?> clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

Прежде чем рассматривать этот метод, давайте разберемся с определением класса-оболочки в Dubbo. Например:

class A {
    private A a;
    public A(A a){
        this.a = a;
    }
}

Мы видим, что у класса A есть конструктор, который принимает A в качестве параметра, мы называем его конструктором копирования. Класс с таким конструктором в Dubbo называется классом-оболочкой. Продолжаем смотреть на метод isWrapperClass().Этот метод относительно прост.Попробуйте получить конструктор с типом в качестве параметра в clazz.Если его удается получить,то считается,что clazz является классом-оболочкой текущего класса типа. В сочетании с приведенным выше кодом мы обнаружим, что при загрузке точки расширения мы кэшируем класс-оболочку, соответствующий типу.

private T createExtension(String name) {
    // ···省略···
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
        EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
        instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    injectExtension(instance);
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        for (Class<?> wrapperClass : wrapperClasses) {
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
    }
    return instance;
    // ···省略···
}

Чтобы лучше понять этот код, мы предполагаем, что текущее значение типа — Protocol.class, мы можем найти классы-оболочки ProtocolFilterWrapper и ProtocolListenerWrapper интерфейса Protocol в файле org.apache.dubbo.rpc.Protocol, и они будут добавляется в cachedWrapperClasses по очереди в коллекцию. По очереди просматривайте коллекцию cachedWrapperClasses. Например, если класс ProtocolFilterWrapper извлекается в первый раз, экземпляр будет заключен в оболочку путем вызова конструктора копирования ProtocolFilterWrapper. После создания экземпляра объекта ProtocolFilterWrapper вызовите injectExtension() для внедрения зависимостей. На данный момент instance уже является экземпляром ProtocolFilterWrapper, и если цикл продолжится, класс ProtocolFilterWrapper будет заключен в класс ProtocolListenerWrapper. В итоге мы получаем экземпляр ProtocolListenerWrapper. Когда будет сделан последний вызов, метод исходного экземпляра будет вызываться через послойные вызовы. Класс-оболочка здесь чем-то похож на идею АОП.Мы можем добавить некоторые пользовательские операции, такие как печать журнала и мониторинг, прежде чем вызывать реализацию расширения посредством послойной упаковки.

Механизм МОК в Даббо

Выше мы обсуждали, что Dubbo использует механизм отражения для реализации IOC-подобной функции. В этой главе мы рассмотрим метод injectExtension() и более подробно рассмотрим реализацию функции IOC в Dubbo.

createExtension()
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

private T injectExtension(T instance) {
    // ···
    Class<?> pt = method.getParameterTypes()[0];
    try {
        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
        Object object = objectFactory.getExtension(pt, property);
        if (object != null) {
            method.invoke(instance, object);
        }
    }
    // ···
}

public class StubProxyFactoryWrapper implements ProxyFactory {
    // ...
    private Protocol protocol;
    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
    //...
}

В предыдущей главе мы уже говорили о классе-оболочке, здесь мы приводим пример для иллюстрации. Например, наш текущий класс wrapperClass — StubProxyFactoryWrapper, тогда логика выполнения кода примерно такая:

  1. Создайте экземпляр StubProxyFactoryWrapper;
  2. Получить экземпляр, созданный процессом 1, в качестве параметра injectExtension() и выполнить;
  3. Метод injectExtension() перебирает метод setProtocol() StubProxyFactoryWrapper (в настоящее время pt=Protocol.class, property=protocol) и выполняет метод objectFactory.getExtension(pt,property). Объектная фабрика инициализируется в методе построения ExtensionLoader, а полученный здесь класс адаптивного расширения — AdaptiveExtensionFactory.
  4. Выполните AdaptiveExtensionFactory.getExtension(). В классе AdaptiveExtensionFactory есть фабрики переменных коллекций. Фабрики инициализируются в конструкторе AdaptiveExtensionFactory, который содержит два класса фабрик: SpiExtensionFactory и SpringExtensionFactory. Выполнение метода getExtension() класса AdaptiveExtensionFactory, в свою очередь, вызовет метод getExtension() классов SpiExtensionFactory и SpringExtensionFactory.
  5. Выполните метод getExtension() SpiExtensionFactory. Как упоминалось выше, type=Procotol.class, property=protocol в это время. Из следующего кода мы можем обнаружить, что Protocol является интерфейсным классом, а аннотация @SPI помечена. В это время объект ExtensionLoader типа Protocol будет получен, и, наконец, перейдите к вызову метода getAdaptiveExtension() загрузчика. Окончательно полученный адаптивный класс — это динамический класс Protocol$Adaptive.
  6. objectFactory.getExtension(pt, property); Последний полученный класс — это класс Protocol$Adaptive, который внедряется в экземпляр StubProxyFactoryWrapper с использованием механизма отражения.
@SPI("dubbo")
public interface Protocol {
}
public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

END

Напоследок повторим абзац в начале об улучшении Dubbo SPI на базе JAVA SPI:

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

Обобщенно следующим образом:

  1. Когда Dubbo SPI загружает точку расширения, он сохраняет класс расширения в кеше в форме ключ-значение, но класс расширения в это время является просто классом, загружаемым вызовом Class.forName(), и не создается. Классы расширения создаются при вызове метода getExtension().
  2. Dubbo реализует внедрение зависимостей через фабричный шаблон и механизм отражения.
  3. В Dubbo механизм АОП реализован через класс-обертку, что нам удобно для добавления мониторинга и печати логов.