Механизм расширения 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:
- Стандартный SPI JDK может только находить точки расширения и создавать их экземпляры путем обхода, что может привести к загрузке всех точек расширения одновременно.Если не все точки расширения используются, это приведет к пустой трате ресурсов. Каждая точка расширения dubbo имеет несколько реализаций, например, интерфейс com.alibaba.dubbo.rpc.Protocol имеет такие реализации, как InjvmProtocol, DubboProtocol, RmiProtocol, HttpProtocol, HessianProtocol и т. д. Если используется только одна из реализаций, но все реализации загружены, приведет к пустой трате ресурсов.
- Измените формат реализации в файле конфигурации, например, meta-inf/dubbo/com.xx.protocol, формат com.foo.xxprotocol изменен на XXX = com.foo.xxxprotocol. форма значения ключа, поэтому цель состоит в том, чтобы упростить поиск проблем, например, из-за невозможности существования из-за сторонних библиотек, что приводит к невозможности загрузки расширения («a»), когда пользователь настраивает A , Dubbo не будет загружать ошибку расширения, вместо реализации которой реализация расширений не удалась и причина ошибки, потому что исходный формат конфигурации не записывает запись идентификатора расширения, в результате чего Dubbo выдает более точные исключения, это увеличит сложность вопросов следствия. Поэтому он изменяется на форму Key-Value для настройки.
- Механизм SPI Dubbo добавляет поддержку IOC и AOP, а точка расширения может быть напрямую внедрена в другие точки расширения через сеттеры.
Давайте сначала посмотрим на каталог структуры, реализованный механизмом расширения SPI:
(1) Аннотация @SPI
После добавления аннотации @SPI к интерфейсу это указывает, что интерфейс является расширяемым интерфейсом. В качестве примера я использую протокол расширения протокола. по умолчанию.Потому что в протоколе есть аннотация @SPI("dubbo"). Значение свойства протокола или значение по умолчанию будет использоваться в качестве ключа в классе реализации интерфейса, и dubbo перейдет к файлу META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol, чтобы найти значение, соответствующее к ключу см. рисунок ниже:
Значение представляет собой класс реализации DubboProtocol интерфейса протокола, так что достигается расширение SPI.
(2) Примечание @ адаптивный
Эта аннотация предназначена для того, чтобы гарантировать, что когда dubbo внутренне вызывает конкретную реализацию, она не будет жестко закодирована, чтобы указать, на какую реализацию ссылаться, то есть адаптироваться к нескольким реализациям интерфейса.Это соответствует принципу подключаемости модульного интерфейса. , а также увеличивает весь фреймворк.Гибкость данной аннотации также реализует функцию автоматической сборки точек расширения.
Dubbo предоставляет два способа реализации адаптеров для интерфейсов:
-
Добавьте аннотацию @Adaptive к классу реализации, чтобы указать, что класс реализации является адаптером интерфейса.
Например, интерфейс ExtensionFactory в dubbo имеет класс реализации AdaptiveExtensionFactory с аннотацией @Adaptive, AdaptiveExtensionFactory не обеспечивает конкретной бизнес-поддержки и используется для адаптации к двум реализациям SpiExtensionFactory и SpringExtensionFactory ExtensionFactory. AdaptiveExtensionFactory выберет конкретную реализацию ExtensionFactory для вызова в соответствии с некоторыми состояниями во время выполнения. Конкретный выбор можно увидеть в анализе кода Adaptive ниже.
-
Добавьте аннотацию @Adaptive к методу интерфейса, и dubbo динамически сгенерирует класс адаптера.
Мы объясняем этот метод из исходного кода интерфейса 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.Позвольте мне объяснить первый метод подключения:
- Все точки расширения несут информацию о конфигурации путем передачи URL-адресов, поэтому методы в адаптере должны содержать параметры URL-адреса для выбора соответствующей реализации расширения в соответствии с конфигурацией в URL-адресе.
- В аннотации @Adaptive есть некоторые ключевые значения. Например, в аннотации метода подключения есть два ключа, а именно «клиент» и «транспортер». URL-адрес сначала извлечет значение, соответствующее клиенту, как мой выше **(1) аннотация@ Значение ключа, записанное в SPI**, если оно пустое, будет взято значение, соответствующее транспортеру, если оно все еще пустое, будет вызван расширенный класс реализации по ключу по умолчанию SPI, то есть netty.Если @SPI не установлен Если установлено значение по умолчанию, будет выдано исключение IllegalStateException.
Таким образом, понятно, как адаптер выбирает, какой класс реализации вызывать на этот раз.Самое главное здесь — подчеркнуть, что dubbo использует URL в качестве шины, и вся информация о статусных данных во время запущенного процесса может быть получена через URL-адрес, например текущая информация, например, какая сериализация используется системой, какая связь используется, какая балансировка нагрузки используется и т. д., все они представлены через параметры URL-адреса, поэтому во время работы фреймворка , если на определенном этапе требуются соответствующие данные, соответствующий ключ Получить из списка параметров URL.
(3) Аннотация @Activate
Аннотация для автоматической активации и загрузки точек расширения является использование условий для управления, активируется ли реализация точки расширения автоматически и загружена. Он используется в классе реализации расширения для реализации функции автоматической активации точек расширения. Это может Установите два параметра, а именно группу и значение. Подробнее, пожалуйста, обратитесь к официальной документации.
Адрес автоматической активации точки расширения:Хироши Ватанабэ.apache.org/this-capable/docs/…
(4) Фабрика расширений интерфейса
Давайте сначала посмотрим на его исходный код:
Этот интерфейс является классом интерфейса фабрики расширений, который сам является интерфейсом расширения с аннотациями SPI. Фабричный интерфейс предоставляет экземпляр для получения класса реализации, а также две реализации расширений, а именно SpiExtensionFactory и SpringExtensionFactory, которые представляют два разных способа получения экземпляров. А какой способ выбрать для получения экземпляра класса реализации, делается в правилах AdaptiveExtensionFactory адаптера. Конкретные правила см. в анализе исходного кода ниже.
(5) ExtensionLoader
Этот класс является загрузчиком расширений, который является ядром реализации dubbo механизма расширения SPI, и почти вся реализованная логика инкапсулирована в ExtensionLoader.
Подробные комментарии к коду см. на github:GitHub.com/crazy Гц М/в…
-
Атрибуты (выберите ключевые атрибуты, чтобы расширить объяснение, остальные см. в комментариях github)
-
О переменной пути для хранения файла конфигурации:
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/" предназначен для хранения файлов конфигурации для пользовательских расширений.
-
Набор загрузчиков расширений, ключом является интерфейс расширения, например протокол и т.д.:
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
-
Набор классов реализации расширения, ключ — класс реализации расширения, значение — объект расширения, например, ключ — класс, значение — объект DubboProtocol
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
-
Все следующие свойства начинаются с кеша и кешируются для оптимизации производительности и ресурсов.После чтения расширенной конфигурации она сначала будет кешироваться, а когда реализация действительно понадобится, кешируется класс реализации.Инициализируется объект, а затем объект также кэшируется.
//以下提到的扩展名就是在配置文件中的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/…
-
-
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; }
Анализ исходного кода этого метода показан выше, и его не так уж сложно интерпретировать. Это просто вопрос разъяснения значения нескольких атрибутов.
-
Метод 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 имеет несколько ключевых моментов:
- Значение группы является допустимым, поскольку группа может быть необязательным "поставщиком" или "потребителем".
- Определите, была ли удалена конфигурация.
- Если имеется пользовательская конфигурация, и ее необходимо разместить до того, как расширение автоматической активации осуществит загрузку объекта, то сначала необходимо сохранить пользовательскую конфигурацию.
-
Метод 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, которые будут обсуждаться позже. Другая логика относительно проста, то есть взять из кеша, если нет, то создать, а потом положить в кеш.
-
Метод getDefaultExtension: найти реализацию расширения по умолчанию.
public T getDefaultExtension() { //获得扩展接口的实现类数组 getExtensionClasses(); if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) { return null; } //又重新去调用了getExtension return getExtension(cachedDefaultName); }
Это включает в себя метод getExtensionClasses, который будет обсуждаться позже. Получение объекта класса реализации расширения по умолчанию означает получение объекта класса реализации через имя расширения по умолчанию в кэше.
-
Метод 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; } }
-
Метод 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 будет объяснен позже.
-
Метод 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 приведен ниже.
-
Метод 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, найти свойства, которые требуют внедрения зависимостей, а затем внедрить в него объект.
-
Метод 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, который объясняется ниже.
-
Метод 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 приведен ниже.
-
Метод 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 (анализ приведен ниже), и здесь можно увидеть, как найти порядок путей к ресурсам конфигурации для доступа к файлам.
-
Метод 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.
-
метод 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); } }
Основная логика этого класса заключается в том, чтобы прочитать содержимое внутри, пропустить содержимое комментария «#», разделить его в соответствии с формой ключ=значение в файле конфигурации, а затем загрузить класс, соответствующий значению.
-
метод 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 в качестве демонстрации.
-
Метод 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**.
-
Некоторые из методов относительно просты для понимания и не влияют на основную функцию. Я не буду перечислять их все. Пожалуйста, ознакомьтесь с другими методами этого класса по следующему 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;
}
}
- Фабрики представляют собой набор объектов расширения.Когда пользователь не реализует интерфейс ExtensionFactory, в этом свойстве есть только два объекта, а именно SpiExtensionFactory и SpringExtensionFactory.
- В конструкторе объекты расширения всех поддерживаемых расширений добавляются в коллекцию
- Реализован метод интерфейса 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 в последующих статьях. Если есть какая-то часть, которую я написал недостаточно хорошо или неправильно, пожалуйста, дайте мне знать.