Анализ исходного кода Dubbo (2) Механизм расширения Dubbo SPI

Dubbo

Механизм расширения Dubbo SPI

предыдущий пост«Анализ исходного кода Dubbo (1) Привет, Dubbo»Это общее введение ко всему проекту dubbo, и начиная с этой статьи я буду интерпретировать принципы и характеристики dubbo и каждого модуля из исходного кода, так как вся интерпретация исходного кода по скриншотам приведет к тому, что статья быть очень грязным, поэтому я буду показывать только некоторые скриншоты, а все интерпретации будут синхронно обновляться в исходном коде dubbo моего форка на github, В то же время я также добавлю гиперссылки на некоторые ключевые части статьи для удобство читателей.

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


Цель: Познакомить читателя с идеей SPI JDK, идеей SPI dubbo, принципом механизма расширения dubbo SPI и иметь возможность прочитать исходный код, реализующий механизм расширения.

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

SPI идея JDK

Полное название SPI — интерфейс поставщика услуг.В объектно-ориентированном проектировании между модулями рекомендуется программирование на основе интерфейса вместо жесткого кодирования класса реализации.Это также относится к подключаемому принципу проектирования модулей. Чтобы не указывать, какая реализация есть в программе при сборке модуля, нужен механизм обнаружения сервисов, суть jdk в том, чтобы найти реализацию сервиса для интерфейса. JDK предоставляет класс инструмента для поиска реализации службы: java.util.ServiceLoader, который загружает файлы конфигурации в каталог META-INF/service/. Конкретная внутренняя логика реализации здесь не раскрывается, в основном для объяснения принципа реализации dubbo о spi.

Принцип механизма расширения Dubbo SPI

dubbo самостоятельно реализовал набор механизмов SPI, улучшив стандартный механизм SPI JDK:

  1. Стандартный SPI JDK может только находить точки расширения и создавать их экземпляры путем обхода, что может привести к загрузке всех точек расширения одновременно.Если не все точки расширения используются, это приведет к пустой трате ресурсов. Каждая точка расширения dubbo имеет несколько реализаций, например, интерфейс com.alibaba.dubbo.rpc.Protocol имеет такие реализации, как InjvmProtocol, DubboProtocol, RmiProtocol, HttpProtocol, HessianProtocol и т. д. Если используется только одна из реализаций, но все реализации загружены, приведет к пустой трате ресурсов.
  2. Измените формат реализации в файле конфигурации, например, meta-inf/dubbo/com.xx.protocol, формат com.foo.xxprotocol изменен на XXX = com.foo.xxxprotocol. форма значения ключа, поэтому цель состоит в том, чтобы упростить поиск проблем, например, из-за невозможности существования из-за сторонних библиотек, что приводит к невозможности загрузки расширения («a»), когда пользователь настраивает A , Dubbo не будет загружать ошибку расширения, вместо реализации которой реализация расширений не удалась и причина ошибки, потому что исходный формат конфигурации не записывает запись идентификатора расширения, в результате чего Dubbo выдает более точные исключения, это увеличит сложность вопросов следствия. Поэтому он изменяется на форму Key-Value для настройки.
  3. Механизм SPI Dubbo добавляет поддержку IOC и AOP, а точка расширения может быть напрямую внедрена в другие точки расширения через сеттеры.

Давайте сначала посмотрим на каталог структуры, реализованный механизмом расширения SPI:

extension目录

(1) Аннотация @SPI

После добавления аннотации @SPI к интерфейсу это указывает, что интерфейс является расширяемым интерфейсом. В качестве примера я использую протокол расширения протокола. по умолчанию.Потому что в протоколе есть аннотация @SPI("dubbo"). Значение свойства протокола или значение по умолчанию будет использоваться в качестве ключа в классе реализации интерфейса, и dubbo перейдет к файлу META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol, чтобы найти значение, соответствующее к ключу см. рисунок ниже:

protocol的配置文件

Значение представляет собой класс реализации DubboProtocol интерфейса протокола, так что достигается расширение SPI.

(2) Примечание @ адаптивный

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

Dubbo предоставляет два способа реализации адаптеров для интерфейсов:

  1. Добавьте аннотацию @Adaptive к классу реализации, чтобы указать, что класс реализации является адаптером интерфейса.

    Например, интерфейс ExtensionFactory в dubbo имеет класс реализации AdaptiveExtensionFactory с аннотацией @Adaptive, AdaptiveExtensionFactory не обеспечивает конкретной бизнес-поддержки и используется для адаптации к двум реализациям SpiExtensionFactory и SpringExtensionFactory ExtensionFactory. AdaptiveExtensionFactory выберет конкретную реализацию ExtensionFactory для вызова в соответствии с некоторыми состояниями во время выполнения. Конкретный выбор можно увидеть в анализе кода Adaptive ниже.

  2. Добавьте аннотацию @Adaptive к методу интерфейса, и dubbo динамически сгенерирует класс адаптера.

    Мы объясняем этот метод из исходного кода интерфейса Transporter:

    Transporter源码

    Мы видим, что на этом интерфейсе есть аннотация @adaptive, и параметры метода этой аннотации должны содержать URL.ExtensLoader динамически генерирует класс Transporter$Adaptive через метод createAdaptiveExtensionClassCode.Сгенерированный код выглядит следующим образом:

    package com.alibaba.dubbo.remoting;
    import com.alibaba.dubbo.common.extension.ExtensionLoader;
    public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
        
    	public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
            //URL参数为空则抛出异常。
    		if (arg0 == null) 
    			throw new IllegalArgumentException("url == null");
    		
    		com.alibaba.dubbo.common.URL url = arg0;
            //这里的getParameter方法可以在源码中具体查看
    		String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
    		if(extName == null) 
    			throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
            //这里我在后面会有详细介绍
    		com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
    		
    		(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
    		return extension.connect(arg0, arg1);
    	}
    	public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
    		if (arg0 == null) 
    			throw new IllegalArgumentException("url == null");
    		com.alibaba.dubbo.common.URL url = arg0;
    		String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
    		if(extName == null) 
    			throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
    		com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
    		(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
    		
    		return extension.bind(arg0, arg1);
    	}
    }
    

    Вы можете видеть, что два метода этого класса являются двумя методами, аннотированными в интерфейсе Transporter.Позвольте мне объяснить первый метод подключения:

    1. Все точки расширения несут информацию о конфигурации путем передачи URL-адресов, поэтому методы в адаптере должны содержать параметры URL-адреса для выбора соответствующей реализации расширения в соответствии с конфигурацией в URL-адресе.
    2. В аннотации @Adaptive есть некоторые ключевые значения. Например, в аннотации метода подключения есть два ключа, а именно «клиент» и «транспортер». URL-адрес сначала извлечет значение, соответствующее клиенту, как мой выше **(1) аннотация@ Значение ключа, записанное в SPI**, если оно пустое, будет взято значение, соответствующее транспортеру, если оно все еще пустое, будет вызван расширенный класс реализации по ключу по умолчанию SPI, то есть netty.Если @SPI не установлен Если установлено значение по умолчанию, будет выдано исключение IllegalStateException.

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

(3) Аннотация @Activate

Аннотация для автоматической активации и загрузки точек расширения является использование условий для управления, активируется ли реализация точки расширения автоматически и загружена. Он используется в классе реализации расширения для реализации функции автоматической активации точек расширения. Это может Установите два параметра, а именно группу и значение. Подробнее, пожалуйста, обратитесь к официальной документации.

Адрес автоматической активации точки расширения:Хироши Ватанабэ.apache.org/this-capable/docs/…

(4) Фабрика расширений интерфейса

Давайте сначала посмотрим на его исходный код:

ExtensionFactory源码

Этот интерфейс является классом интерфейса фабрики расширений, который сам является интерфейсом расширения с аннотациями SPI. Фабричный интерфейс предоставляет экземпляр для получения класса реализации, а также две реализации расширений, а именно SpiExtensionFactory и SpringExtensionFactory, которые представляют два разных способа получения экземпляров. А какой способ выбрать для получения экземпляра класса реализации, делается в правилах AdaptiveExtensionFactory адаптера. Конкретные правила см. в анализе исходного кода ниже.

(5) ExtensionLoader

Этот класс является загрузчиком расширений, который является ядром реализации dubbo механизма расширения SPI, и почти вся реализованная логика инкапсулирована в ExtensionLoader.

Подробные комментарии к коду см. на github:GitHub.com/crazy Гц М/в…

  1. Атрибуты (выберите ключевые атрибуты, чтобы расширить объяснение, остальные см. в комментариях github)
    1. О переменной пути для хранения файла конфигурации:

          private static final String SERVICES_DIRECTORY = "META-INF/services/";
          private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
          private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
      

      Три значения «META-INF/services/», «META-INF/dubbo/» и «META-INF/dubbo/internal/» — это все пути хранения файла конфигурации, где dubbo ищет расширенный класс реализации, то есть я указал выше **(1) Путь, по которому хранится файл конфигурации с полным именем интерфейса, упомянутого в аннотации @SPI**. Разница в том, что «META-INF/services/» создается dubbo, чтобы быть совместимым с идеей механизма расширения SPI jdk, «META-INF/dubbo/internal/» — это расширенный путь к файлу конфигурации, предоставляемый dubbo внутри, и "META-INF/dubbo/" предназначен для хранения файлов конфигурации для пользовательских расширений.

    2. Набор загрузчиков расширений, ключом является интерфейс расширения, например протокол и т.д.:

          private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
      
    3. Набор классов реализации расширения, ключ — класс реализации расширения, значение — объект расширения, например, ключ — класс, значение — объект DubboProtocol

          private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
      
    4. Все следующие свойства начинаются с кеша и кешируются для оптимизации производительности и ресурсов.После чтения расширенной конфигурации она сначала будет кешироваться, а когда реализация действительно понадобится, кешируется класс реализации.Инициализируется объект, а затем объект также кэшируется.

          //以下提到的扩展名就是在配置文件中的key值,类似于“dubbo”等
      
          //缓存的扩展名与拓展类映射,和cachedClasses的key和value对换。
          private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
          //缓存的扩展实现类集合
          private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
          //扩展名与加有@Activate的自动激活类的映射
          private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
          //缓存的扩展对象集合,key为扩展名,value为扩展对象
          //例如Protocol扩展,key为dubbo,value为DubboProcotol
          private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object
          //缓存的自适应( Adaptive )扩展对象,例如例如AdaptiveExtensionFactory类的对象
          private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
          //缓存的自适应扩展对象的类,例如AdaptiveExtensionFactory类
          private volatile Class<?> cachedAdaptiveClass = null;
          //缓存的默认扩展名,就是@SPI中设置的值
          private String cachedDefaultName;
          //创建cachedAdaptiveInstance异常
          private volatile Throwable createAdaptiveInstanceError;
          //拓展Wrapper实现类集合
          private Set<Class<?>> cachedWrapperClasses;
          //拓展名与加载对应拓展类发生的异常的映射
          private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
      
      

      Здесь упоминается концепция класса Wrapper. Тогда позвольте мне объяснить: класс Wrapper также реализует интерфейс расширения, но назначение класса Wrapper в том, что когда ExtensionLoader возвращает точку расширения, она заворачивается за пределы реальной реализации точки расширения, которая реализует функцию автоматической упаковки точка расширения. С точки зрения непрофессионала, интерфейс имеет много классов реализации, и эти классы реализации будут иметь некоторую общую логику.Если вы напишите эту общую логику в каждом классе реализации, код будет повторяться, поэтому этот класс-оболочка добавляется для его обертки. логика в классе Wrapper чем-то похожа на идею АОП-аспектного программирования. Эту часть объяснения также можно совместить с официальной документацией:

      Адрес функции автоматической упаковки точки расширения:Хироши Ватанабэ.apache.org/this-capable/docs/…

  2. getExtensionLoader (тип класса): получить загрузчик расширения в соответствии с интерфейсом точки расширения.
        public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
            //扩展点接口为空,抛出异常
            if (type == null)
                throw new IllegalArgumentException("Extension type == null");
            //判断type是否是一个接口类
            if (!type.isInterface()) {
                throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
            }
            //判断是否为可扩展的接口
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type(" + type +
                            ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
            }
    
            //从扩展加载器集合中取出扩展接口对应的扩展加载器
            ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    
            //如果为空,则创建该扩展接口的扩展加载器,并且添加到EXTENSION_LOADERS
            if (loader == null) {
               EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            }
            return loader;
        }
    

    Анализ исходного кода этого метода показан выше, и его не так уж сложно интерпретировать. Это просто вопрос разъяснения значения нескольких атрибутов.

  3. Метод getActivateExtension: получение коллекции объектов класса реализации расширения, соответствующих условиям автоматической активации.
    	/**
         * 获得符合自动激活条件的扩展实现类对象集合(适用没有group条件的自动激活类)
         * @param url
         * @param key
         * @return
         */
        public List<T> getActivateExtension(URL url, String key) {
            return getActivateExtension(url, key, null);
        }
    	//弃用
        public List<T> getActivateExtension(URL url, String[] values) {
            return getActivateExtension(url, values, null);
        }
    	/**
         * 获得符合自动激活条件的扩展实现类对象集合(适用含有value和group条件的自动激活类)
         * @param url
         * @param key
         * @param group
         * @return
         */
        public List<T> getActivateExtension(URL url, String key, String group) {
            String value = url.getParameter(key);
            // 获得符合自动激活条件的拓展对象数组
            return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
        }
    
        public List<T> getActivateExtension(URL url, String[] values, String group) {
            List<T> exts = new ArrayList<T>();
            List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    
            //判断不存在配置 `"-name"` 。
            //例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
            if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
    
                //获得扩展实现类数组,把扩展实现类放到cachedClasses中
                getExtensionClasses();
                for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                    String name = entry.getKey();
                    Activate activate = entry.getValue();
                    //判断group值是否存在所有自动激活类中group组中,匹配分组
                    if (isMatchGroup(group, activate.group())) {
                        //通过扩展名获得拓展对象
                        T ext = getExtension(name);
                        //不包含在自定义配置里。如果包含,会在下面的代码处理。
                        //判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
                        //判断是否激活
                        if (!names.contains(name)
                                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                                && isActive(activate, url)) {
                            exts.add(ext);
                        }
                    }
                }
                //排序
                Collections.sort(exts, ActivateComparator.COMPARATOR);
            }
            List<T> usrs = new ArrayList<T>();
            for (int i = 0; i < names.size(); i++) {
                String name = names.get(i);
                //还是判断是否是被移除的配置
                if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
                    //在配置中把自定义的配置放在自动激活的扩展对象前面,可以让自定义的配置先加载
                    //例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
                    if (Constants.DEFAULT_KEY.equals(name)) {
                        if (!usrs.isEmpty()) {
                            exts.addAll(0, usrs);
                            usrs.clear();
                        }
                    } else {
                        T ext = getExtension(name);
                        usrs.add(ext);
                    }
                }
            }
            if (!usrs.isEmpty()) {
                exts.addAll(usrs);
            }
            return exts;
        }
    

    Видно, что getActivateExtension имеет перегруженные четыре метода, по сути окончательная реализация находится в последнем перегруженном методе, потому что условия класса автоматической активации можно разделить на три типа: безусловные, только значение и групповое значение.Подробнее , вы можете просмотреть приведенный выше **(3) Annotate @Activate**.

    Последний метод getActivateExtension имеет несколько ключевых моментов:

    1. Значение группы является допустимым, поскольку группа может быть необязательным "поставщиком" или "потребителем".
    2. Определите, была ли удалена конфигурация.
    3. Если имеется пользовательская конфигурация, и ее необходимо разместить до того, как расширение автоматической активации осуществит загрузку объекта, то сначала необходимо сохранить пользовательскую конфигурацию.
  4. Метод getExtension: получить объект расширения через расширение
        /**
         * 通过扩展名获得扩展对象
         * @param name
         * @return
         */
        @SuppressWarnings("unchecked")
        public T getExtension(String name) {
            if (name == null || name.length() == 0)
                throw new IllegalArgumentException("Extension name == null");
            //查找默认的扩展实现,也就是@SPI中的默认值作为key
            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;
        }
    

    Этот метод включает в себя метод getDefaultExtension и метод createExtension, которые будут обсуждаться позже. Другая логика относительно проста, то есть взять из кеша, если нет, то создать, а потом положить в кеш.

  5. Метод getDefaultExtension: найти реализацию расширения по умолчанию.
        public T getDefaultExtension() {
            //获得扩展接口的实现类数组
            getExtensionClasses();
            if (null == cachedDefaultName || cachedDefaultName.length() == 0
                    || "true".equals(cachedDefaultName)) {
                return null;
            }
            //又重新去调用了getExtension
            return getExtension(cachedDefaultName);
        }
    

    Это включает в себя метод getExtensionClasses, который будет обсуждаться позже. Получение объекта класса реализации расширения по умолчанию означает получение объекта класса реализации через имя расширения по умолчанию в кэше.

  6. Метод addExtension: класс реализации интерфейса расширения
        public void addExtension(String name, Class<?> clazz) {
            getExtensionClasses(); // load classes
    
            //该类是否是接口的本身或子类
            if (!type.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Input type " +
                        clazz + "not implement Extension " + type);
            }
            //该类是否被激活
            if (clazz.isInterface()) {
                throw new IllegalStateException("Input type " +
                        clazz + "can not be interface!");
            }
    
            //判断是否为适配器
            if (!clazz.isAnnotationPresent(Adaptive.class)) {
                if (StringUtils.isBlank(name)) {
                    throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
                }
                if (cachedClasses.get().containsKey(name)) {
                    throw new IllegalStateException("Extension name " +
                            name + " already existed(Extension " + type + ")!");
                }
    
                //把扩展名和扩展接口的实现类放入缓存
                cachedNames.put(clazz, name);
                cachedClasses.get().put(name, clazz);
            } else {
                if (cachedAdaptiveClass != null) {
                    throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!");
                }
    
                cachedAdaptiveClass = clazz;
            }
        }
    
  7. Метод getAdaptiveExtension: получить объект адаптивного расширения, который является объектом адаптера интерфейса.
       @SuppressWarnings("unchecked")
        public T getAdaptiveExtension() {
            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) {
                                createAdaptiveInstanceError = t;
                                throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                            }
                        }
                    }
                } else {
                    throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
                }
            }
    
            return (T) instance;
        }
    

    Идея состоит в том, чтобы сначала получить объект класса адаптера из кеша. Если нет, создать объект адаптера и поместить его в кеш. Метод createAdaptiveExtension будет объяснен позже.

  8. Метод createExtension: создать объект класса реализации интерфейса расширения через расширение
        @SuppressWarnings("unchecked")
        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);
                //创建 Wrapper 扩展对象(自动包装)
                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);
            }
        }
    

    Он использует характеристики двух расширений для автоматической сборки и автоматической упаковки соответственно. Анализ метода INJECTEXTENSION приведен ниже.

  9. Метод injectExtension: внедряет свои зависимые свойства в созданное расширение.
        private T injectExtension(T instance) {
            try {
                if (objectFactory != null) {
                    //反射获得该类中所有的方法
                    for (Method method : instance.getClass().getMethods()) {
                        //如果是set方法
                        if (method.getName().startsWith("set")
                                && method.getParameterTypes().length == 1
                                && Modifier.isPublic(method.getModifiers())) {
                            Class<?> pt = method.getParameterTypes()[0];
                            try {
                                //获得属性,比如StubProxyFactoryWrapper类中有Protocol protocol属性,
                                String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                                //获得属性值,比如Protocol对象,也可能是Bean对象
                                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, найти свойства, которые требуют внедрения зависимостей, а затем внедрить в него объект.

  10. Метод getExtensionClass: получить класс реализации расширения, соответствующий расширению.
        private Class<?> getExtensionClass(String name) {
            if (type == null)
                throw new IllegalArgumentException("Extension type == null");
            if (name == null)
                throw new IllegalArgumentException("Extension name == null");
            Class<?> clazz = getExtensionClasses().get(name);
            if (clazz == null)
                throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
            return clazz;
        }
    

    Вот метод, вызывающий getExtensionClasses, который объясняется ниже.

  11. Метод 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 приведен ниже.

  12. Метод loadExtensionClasses: из файла конфигурации загрузите массив классов реализации расширения.

       private Map<String, Class<?>> loadExtensionClasses() {
            final SPI defaultAnnotation = type.getAnnotation(SPI.class);
            if (defaultAnnotation != null) {
                //@SPI内的默认值
                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);
            loadDirectory(extensionClasses, DUBBO_DIRECTORY);
            loadDirectory(extensionClasses, SERVICES_DIRECTORY);
            return extensionClasses;
        }
    

    Первая часть логики — положить значение по умолчанию в аннотации SPI в кеш, логика загрузки массива класса реализации — в следующих нескольких строчках, ключ — метод loadDirectory (анализ приведен ниже), и здесь можно увидеть, как найти порядок путей к ресурсам конфигурации для доступа к файлам.

  13. Метод loadDirectory: из файла конфигурации загрузите массив расширенных классов реализации.
        private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
            //拼接接口全限定名,得到完整的文件名
            String fileName = dir + type.getName();
            try {
                Enumeration<java.net.URL> urls;
                //获取ExtensionLoader类信息
                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) {
                logger.error("Exception when load extension class(interface: " +
                        type + ", description file: " + fileName + ").", t);
            }
        }
    
    

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

  14. метод loadResource: загрузить содержимое в файл
       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;
                                int i = line.indexOf('=');
                                if (i > 0) {
                                    //根据"="拆分key跟value
                                    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) {
                                IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                                exceptions.put(line, e);
                            }
                        }
                    }
                } finally {
                    reader.close();
                }
            } catch (Throwable t) {
                logger.error("Exception when load extension class(interface: " +
                        type + ", class file: " + resourceURL + ") in " + resourceURL, t);
            }
        }
    
    

    Основная логика этого класса заключается в том, чтобы прочитать содержимое внутри, пропустить содержимое комментария «#», разделить его в соответствии с формой ключ=значение в файле конфигурации, а затем загрузить класс, соответствующий значению.

  15. метод loadClass: загрузить класс расширения в соответствии со значением в файле конфигурации
       private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
            //该类是否实现扩展接口
            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.");
            }
            //判断该类是否为扩展接口的适配器
            if (clazz.isAnnotationPresent(Adaptive.class)) {
                if (cachedAdaptiveClass == null) {
                    cachedAdaptiveClass = clazz;
                } else if (!cachedAdaptiveClass.equals(clazz)) {
                    throw new IllegalStateException("More than 1 adaptive class found: "
                            + cachedAdaptiveClass.getClass().getName()
                            + ", " + clazz.getClass().getName());
                }
            } else if (isWrapperClass(clazz)) {
                Set<Class<?>> wrappers = cachedWrapperClasses;
                if (wrappers == null) {
                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                    wrappers = cachedWrapperClasses;
                }
                wrappers.add(clazz);
            } else {
                //通过反射获得构造器对象
                clazz.getConstructor();
                //未配置扩展名,自动生成,例如DemoFilter为 demo,主要用于兼容java SPI的配置。
                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 activate = clazz.getAnnotation(Activate.class);
                    //如果是自动激活的实现类,则加入到缓存
                    if (activate != null) {
                        cachedActivates.put(names[0], activate);
                    }
                    for (String n : names) {
                        if (!cachedNames.containsKey(clazz)) {
                            cachedNames.put(clazz, n);
                        }
                        //缓存扩展实现类
                        Class<?> c = extensionClasses.get(n);
                        if (c == null) {
                            extensionClasses.put(n, clazz);
                        } else if (c != clazz) {
                            throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                        }
                    }
                }
            }
        }
    
    

    Сосредоточиться на этом методе совместим с ДЖДК-мышлением. Поскольку файл конфигурации, связанный с SPI JDK - xx.yyy.demoFilter, нет ключа, который является концепцией отсутствия расширения, все для того, чтобы быть совместимым, расширенным XX.yyy.demoFilter в качестве демонстрации.

  16. Метод createAdaptiveExtensionClass: создайте класс адаптера, аналогичный классу Transporter$Adpative, динамически сгенерированному dubbo.
        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);
        }
    
    

    Логика компиляции кода реализована в этом методе.Сгенерированный код находится в методе createAdaptiveExtensionClassCode.Метод createAdaptiveExtensionClassCode слишком длинный, поэтому я не буду его здесь перечислять.URL github будет указан ниже, и читатели могут проверить соответствующие анализ исходного кода самостоятельно. Логику кода, сгенерированную createAdaptiveExtensionClassCode, можно сравнить с классом Transporter$Adpative в упомянутой выше аннотации **(2) @Adaptive**.

  17. Некоторые из методов относительно просты для понимания и не влияют на основную функцию. Я не буду перечислять их все. Пожалуйста, ознакомьтесь с другими методами этого класса по следующему URL-адресу. Здесь подчеркивается, что логика несложная, но значение атрибутов должно быть полностью прочитано.После понимания и выяснения значения каждого атрибута это очень легко понять, взглянув на некоторую логику. Если вы действительно не понимаете значение атрибута, вы можете ввести место вызова и понять его в сочетании с «контекстом».

    Адрес разрешения исходного кода класса ExtensionLoader:GitHub.com/crazy Гц М/в…

(6) Фабрика адаптивных расширений

Этот класс является классом адаптера ExtensionFactory, это мой первый комментарий с использованием класса адаптера @ Adaptive ** **, упомянутого в (b). Взгляните на исходный код класса:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //扩展对象的集合,默认的可以分为dubbo 的SPI中接口实现类对象或者Spring bean对象
    private final List<ExtensionFactory> factories;

    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));
        }
        //返回一个不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @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;
    }

}

  1. Фабрики представляют собой набор объектов расширения.Когда пользователь не реализует интерфейс ExtensionFactory, в этом свойстве есть только два объекта, а именно SpiExtensionFactory и SpringExtensionFactory.
  2. В конструкторе объекты расширения всех поддерживаемых расширений добавляются в коллекцию
  3. Реализован метод интерфейса getExtension, и объект расширения получается через интерфейс и имя расширения.

(7) SpiExtensionFactory

SPI ExtensionFactory расширяет класс реализации, посмотрите исходный код:

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);
            if (!loader.getSupportedExtensions().isEmpty()) {
                //返回适配器类的对象
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

(Viii) Активировать Компаратор

В таком способе класс ExtensionLoader Petterloader применяется в качестве объекта для расширения автоматически активированного сортировщика.

public class ActivateComparator implements Comparator<Object> {

    public static final Comparator<Object> COMPARATOR = new ActivateComparator();

    @Override
    public int compare(Object o1, Object o2) {
        //基本排序
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }
        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);
        //使用Activate注解的 `after` 和 `before` 属性,排序
        if ((a1.before().length > 0 || a1.after().length > 0
                || a2.before().length > 0 || a2.after().length > 0)
                && o1.getClass().getInterfaces().length > 0
                && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            if (a2.before().length > 0 || a2.after().length > 0) {
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }
        // 使用Activate注解的 `order` 属性,排序。
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

}


Ключ заключается в сортировке по значению в аннотации @Activate.

постскриптум

Соответствующий адрес анализа исходного кода этой части:GitHub.com/crazy Гц М/в…

В этой статье объясняется принцип реализации механизма расширения SPI dubbo. Самое главное - выяснить, какие улучшения и оптимизации внесли dubbo и jdk в реализацию SPI. Ключом к интерпретации механизма расширения SPI dubbo является понимание @SPI, @ Смысл трех аннотаций Adaptive и @Activate, большая часть логики инкапсулирована в классе ExtensionLoader. Многие интерфейсы dubbo являются интерфейсами расширений.Интерпретация этой статьи также облегчит читателям понимание архитектуры dubbo в последующих статьях. Если есть какая-то часть, которую я написал недостаточно хорошо или неправильно, пожалуйста, дайте мне знать.