Разговор о Dubbo (5): Исходный код ядра — расширение SPI

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

0 Предисловие

С точки зрения автора фреймворка определить интерфейс и дать несколько классов реализации интерфейса по умолчанию, и в то же времяПозволяет пользователям фреймворка также настраивать реализацию интерфейса. Теперь простой вопрос:Как элегантно получить все классы реализации интерфейса в соответствии с интерфейсом?

JDK SPI был создан для элегантного решения этой проблемы.Полное название SPI (Интерфейс поставщика услуг), которое является интерфейсом поставщика услуг., — это механизм обнаружения поставщиков услуг, встроенный в JDK. В настоящее время многие платформы используют его для обнаружения расширений службы.Говоря простым языком, это механизм динамической замены исполнителя службы обнаружения..

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

Некоторые люди подумают, что это спецификация, основанная на интерфейсно-ориентированном программировании, чтобы сделать компоненты расширяемыми или динамически изменять реализации., распространенные конструкции, подобные SPI,JDBC, JNDI, JAXPи т. д., внутренняя реализация многих фреймворков с открытым исходным кодом также использует SPI. Например, архитектура JDBC состоит из набора API-интерфейсов, которые используются для предоставления Java-приложениям возможности доступа к различным базам данных, а программное обеспечение драйверов поставщиков баз данных отличается.JDBC предоставляет набор API-интерфейсов общего поведения, а нижний уровень может свободно реализовываться провайдерами., хотя структура JDBC не определяет SPI, она аналогична структуре SPI.

1 расширение JDK SPI

JDK предоставляет инструментальный класс для реализации SPI, а именно java.util.ServiceLoader В спецификации SPI, определенной в ServiceLoader, нет ничего особенного.Нужно только иметь файл конфигурации провайдера (файл конфигурации провайдера), файл должен находиться в каталоге ресурсовMETA-INF/services, имя файла является полным именем интерфейса службы..

  1. Содержимое файла представляет собой список полных имен классов провайдеров., очевидно, что класс провайдера должен реализовывать сервисный интерфейс;
  2. Файл должен быть закодирован в UTF-8.;

1.1 Пример JDK SPI

代码示例

/**
 * SPI服务接口
 */
public interface Cmand {
    public void execute();
}
public class ShutdownCommand implements Cmand {
    public void execute() {
        System.out.println("shutdown....");  
    }
}
public class StartCommand implements Cmand {
    public void execute() {
        System.out.println("start....");
    }
}
public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
        System.out.println(loader);
 
        for (Cmand Cmand : loader) {
            Cmand.execute();
        }
    }
}

META-INF/services/com.unei.serviceloader.CmandКонфигурация в файле:

com.unei.serviceloader.impl.ShutdownCommand  
com.unei.serviceloader.impl.StartCommand 

результат операции:

java.util.ServiceLoader[com.unei.serviceloader.Cmand]
shutdown....
start....

1.2 Принцип JDK SPI

  1. Почему файл конфигурации должен быть помещен вMETA-INF/servicesпод?

    Класс ServiceLoader определяется следующим образом:

    private static final String PREFIX = "META-INF/services/"; (JDK已经写死了)
    

    ноЕсли ServiceLoader предоставляет Classloader при загрузке, вы можете читать из других каталогов.

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

    
    private static final String PREFIX = "META-INF/services/";
    
    // 要加载的接口
    private Class<S> service;
    
    // The class loader used to locate, load, and instantiate providers
    private ClassLoader loader;
    
    // 用于缓存已经加载的接口实现类,其中key为实现类的完整类名
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    // 用于延迟加载接口的实现类
    private LazyIterator lookupIterator;
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    private class LazyIterator implements Iterator<S> {
    
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;
    
        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
    
        public boolean hasNext() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                       configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
    
        public S next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 遍历时,查找类对象
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,  "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                // 遍历时,才会初始化类实例对象
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service, "Provider " + cn + " could not be instantiated", x);
            }
            throw new Error();
        }
    
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    

1.3 Недостатки JDK SPI ServiceLoader

  1. Хотя ServiceLoader также может использовать отложенную загрузку,Но в основном его можно получить только путем обхода всех, то есть все классы реализации интерфейса загружаются и создаются один раз.. Если вы не хотите использовать какой-либо класс реализации, он также загружается и создается экземпляр, что расточительно.
  2. Способ получения класса реализации недостаточно гибкий,Можно получить только в виде Iterator, соответствующий класс реализации не может быть получен по параметру.

2 Расширение Dubbo SPI

Dubbo расширил JDK SPI и изменил содержимое файла конфигурации поставщика услуг,Полный список имен исходного класса провайдера был изменен на список в форме KV, что также привело к невозможности прямого использования JDK ServiceLoader в Dubbo., соответственно в Dubbo есть ExtensionLoader,ExtensionLoader — это загрузчик точек расширения, который используется для загрузки различных настраиваемых компонентов в Dubbo, таких как: режим динамического прокси (ProxyFactory), стратегия балансировки нагрузки (LoadBalance), протокол RCP (Protocol), перехватчик (Filter), тип контейнера (Container ), режим кластера (Cluster) и тип реестра (RegistryFactory) и т. д..

Короче говоря, чтобы справиться с различными сценариями, Dubbo,Все его внутренние компоненты управляются через этот SPI., поэтому Dubbo необходимо разработать файл конфигурации поставщика услуг в виде пары ключ-значение KV,Этот K — это K, который мы используем в файле конфигурации или аннотации Dubbo.Dubbo напрямую получает класс реализации, предоставляемый службой, от ExtensionLoader через интерфейс службы (упомянутые выше ProxyFactory, LoadBalance, Protocol, Filter и т. д.) и настроенный К..

В то же время, поскольку Dubbo использует конструкцию шины URL,То есть многие параметры передаются через объект URL. На практике, какое значение использовать, можно указать значением параметра в URL..

2.1 Введение расширенных функций

Расширение Dubbo для SPIРеализовано через ExtensionLoader, проверьте исходный код ExtensionLoader, вы увидите, что Dubbo расширил JDK SPI в трех аспектах:

  1. Легкий доступ к расширенным реализациям: JDK SPI получает все реализации только по имени класса интерфейса,ExtensionLoader получает реализацию через имя класса интерфейса и значение ключа.;

  2. Функция внедрения зависимостей IOC: адаптивная реализация, заключается в создании прокси-класса,Таким образом, вызываемый класс может быть динамически определен в соответствии с некоторыми параметрами фактического вызова..

    Например: интерфейс А, реализаторы А1, А2. Интерфейс B, реализаторы B1, B2.

    Теперь средство реализации A1 содержит метод setB(), который автоматически внедрит средство реализации интерфейса B. На данный момент следует внедрить B1 или B2? ни один,Вместо этого внедрите динамически сгенерированный интерфейс B-реализатор B$Adpative, который может автоматически ссылаться на B1 или B2 для выполнения соответствующей функции в соответствии с различными параметрами.;

  3. Режим декоратора используется для расширения функционала и реализации автоматической упаковки, реализованные классы, как правило, активируются автоматически., обычно используемые в классах-оболочках, таких как: два класса реализации протокола: ProtocolFilterWrapper, ProtocolListenerWrapper.

    Или второй пример,Другой разработчик интерфейса A, AWrapper1. Общее содержание следующее:

    private A a;
    AWrapper1(A a){
    	  this.a=a;
    }
    

    следовательно,При получении реализатора A1 интерфейса A он был автоматически обернут AWrapper1..

2.2 Расширенный анализ исходного кода

2.2.1 Инициализация ExtensionLoader

Возьмите получение DubboProtocol в качестве примера.:

@SPI("dubbo")
public interface Protocol {
    
    /**
     * 获取缺省端口,当用户没有配置端口时使用。
     * 
     * @return 缺省端口
     */
    int getDefaultPort();

    /**
     * 暴露远程服务:<br>
     * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
     * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
     * 
     * @param <T> 服务的类型
     * @param invoker 服务的执行体
     * @return exporter 暴露服务的引用,用于取消暴露
     * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务:<br>
     * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
     * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
     * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
     * 
     * @param <T> 服务的类型
     * @param type 服务的类型
     * @param url 远程服务的URL地址
     * @return invoker 服务的本地代理
     * @throws RpcException 当连接服务提供方失败时抛出
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 释放协议:<br>
     * 1. 取消该协议所有已经暴露和引用的服务。<br>
     * 2. 释放协议所占用的所有资源,比如连接和端口。<br>
     * 3. 协议在释放后,依然能暴露和引用新的服务。<br>
     */
    void destroy();

}

public class DubboProtocol extends AbstractProtocol {

    public static final String NAME = "dubbo";
    ...
    ...
}

// 示例:
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubboProtocol = protocolLoader.getExtension(DubboProtocol.NAME);
  1. ExtensionLoader.getExtensionLoader(Protocol.class): получить экземпляр ExtensionLoader

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
    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!");
         }
         // 0. 判断是否为通过SPI注解定义的可扩展接口
         if(!withExtensionAnnotation(type)) {
             throw new IllegalArgumentException("Extension type(" + type + 
                     ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
         }
         // 1. 先从EXTENSION_LOADERS中,根据传入可扩展类型type查找
         ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
         if (loader == null) {
             // 2. 不存在,则新建ExtensionLoader实例
             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());
     }
    
     private static <T> boolean withExtensionAnnotation(Class<T> type) {
         return type.isAnnotationPresent(SPI.class);
     }
    

    ExtensionLoader действует как фабрика плагинов, предоставляя частный конструктор. Его тип входного параметра является расширенным типом интерфейса.Dubbo определяет расширяемый интерфейс с помощью аннотаций SPI., такие как фильтр, транспортер и т. д.Каждому типу расширения соответствует ExtensionLoader. Параметр value SPI определяет реализацию расширения по умолчанию..

    Когда тип расширения — ExtensionFactory, objectFactory не указывается,В противном случае инициализируйте ExtensionLoader ExtensionFactory и получите адаптер расширения..

  2. protocolLoader.getExtension(DubboProtocol.NAME): Получите соответствующий экземпляр класса реализации расширения в соответствии с ключом

     /**
      * 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@link IllegalStateException}.
      *
      * @param name
      * @return
      */
     @SuppressWarnings("unchecked")
     public T getExtension(String name) {
         if (name == null || name.length() == 0)
             throw new IllegalArgumentException("Extension name == null");
         if ("true".equals(name)) {
             return getDefaultExtension();
         }
         // 1. 先从缓存中取相应的扩展实现类实例
         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) {
                     // 2. 创建相应的扩展实现类实例
                     instance = createExtension(name);
                     holder.set(instance);
                 }
             }
         }
         return (T) instance;
    }
    
     @SuppressWarnings("unchecked")
     private T createExtension(String name) {
         // 3. 根据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, (T) clazz.newInstance());
                 instance = (T) EXTENSION_INSTANCES.get(clazz);
             }
             injectExtension(instance);
             Set<Class<?>> wrapperClasses = cachedWrapperClasses;
             if (wrapperClasses != null && wrapperClasses.size() > 0) {
                 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);
         }
     }   
    

    Метод createExtension предназначен для создания экземпляра соответствующего класса расширения.Конкретные этапы создания будут подробно описаны ниже.Далее давайте продолжим смотреть, как метод getExtensionClasses находит конфигурацию класса соответствующего класса расширения из файла конфигурации..

2.2.2 Сканирование профиля

Dubbo по умолчанию сканирует последовательно**META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/Файлы конфигурации в трех каталогах classpath**.Файл конфигурации назван в честь полного имени конкретного интерфейса расширения.,как:com.alibaba.dubbo.rpc.Filter, содержание следующее:

# 等号前为扩展名,其后为扩展实现类全路径名
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
cache=com.alibaba.dubbo.cache.filter.CacheFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter

Из предыдущего раздела исходный код метода 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;
	}

    // 此方法已经getExtensionClasses方法同步过。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (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<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                // 1. 逐行读取配置文件,提取出扩展名或扩展类路径
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            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) {
                                            name = line.substring(0, i).trim();
                                            line = line.substring(i + 1).trim();
                                        }
                                        if (line.length() > 0) {
                                            // 2. 利用Class.forName方法进行类加载
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            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.");
                                            }
                                            // 3. 处理Adaptive注解,若存在则将该实现类保存至cachedAdaptiveClass属性
                                            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 {
                                                try {
                                                    // 4. 尝试获取参数类型为当前扩展类型的构造器方法,若成功则表明存在该扩展的封装类型,将封装类型存入wrappers集合;否则转入第五步
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }

                                                    // 5. 处理active注解,将扩展名对应active注解存入cachedActivates
                                                    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());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                            type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

Описанные выше шаги загрузки файла конфигурации следующие:

  1. Прочитайте файл конфигурации построчно, извлеките расширение или путь к классам расширения;

  2. Используйте метод Class.forName для загрузки класса;

    Class<?> clazz = Class.forName(line, true, classLoader);
    
  3. Обработайте аннотацию Adaptive и сохраните класс реализации в атрибуте cachedAdaptiveClass, если он существует.

    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());
       }
    }
    
  4. Попробуйте получить метод конструктора, тип параметра которого является текущим типом расширения, в случае успеха это означает, что есть тип пакета расширения, и сохраните тип пакета в коллекцию оберток, в противном случае сгенерируйте исключение и перейдите к пятому шаг;

    try {
        // 扩展类型参数的构造器是封装器的约定特征,目前dubbo中默认的只有Filter和Listener的封装器
        clazz.getConstructor(type); 
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } catch (NoSuchMethodException e) {
        // 第五步
    }
    
  5. Обработать активную аннотацию и сохранить расширение, соответствующее активной аннотации, в cachedActivates;

    Activate activate = clazz.getAnnotation(Activate.class);
    if (activate != null) {
        cachedActivates.put(names[0], activate);
    }
    

2.2.3 Адаптер расширения

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

  1. расширение статического адаптера

    Так называемое статическое расширение адаптера предназначено для предварительного определения конкретной реализации расширения посредством кодирования, а класс реализации аннотируется адаптивными аннотациями., например: AdaptiveCompiler.В методе загрузки конфигурационного файла loadFile описана логика обработки этого типа расширения, за подробностями обращайтесь к исходному коду метода loadFile() в предыдущем разделе..

    @Adaptive
    public class AdaptiveCompiler implements Compiler {
    
        private static volatile String DEFAULT_COMPILER;
    
        public static void setDefaultCompiler(String compiler) {
            DEFAULT_COMPILER = compiler;
        }
    
        public Class<?> compile(String code, ClassLoader classLoader) {
            Compiler compiler;
            ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
            String name = DEFAULT_COMPILER; // copy reference
            if (name != null && name.length() > 0) {
                compiler = loader.getExtension(name);
            } else {
                compiler = loader.getDefaultExtension();
            }
            return compiler.compile(code, classLoader);
        }
    }
    
  2. Расширение динамического адаптера

    Расширение динамического адаптера — это класс динамического прокси, который генерирует расширенный класс через динамический прокси, который генерируется технологией javassist в dubbo.. В отличие от традиционного динамического прокси-сервера jdk и cglib, javassist предоставляет инкапсулированный API для косвенной работы с байт-кодом, который прост и удобен в использовании, не заботится о конкретном байт-коде, имеет более высокую гибкость и более высокую эффективность обработки. по умолчанию компилятор dubbo.

Во-первых, метод getAdaptiveExtension() вызывается из конструктора ExtensionLoader, чтобы инициировать создание адаптера для текущего типа расширения., исходный код выглядит следующим образом:

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

    public T getAdaptiveExtension() {
        // 1. 首先,检查是否存在当前扩展类静态适配器
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 2. 创建当前扩展类动态适配器
                            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;
    }

    private T createAdaptiveExtension() {
        try {
            // IOC属性注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }
    
    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        // 得到Adaptive类代码内容,通过Compiler进行编译和类加载
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
    
    // 创建当前扩展动态适配器
    private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        // 1. 检查是否至少有一个方法有Adaptive注解,若不存在则抛出异常,即要完成动态代理,必须有方法标注了Adaptive注解
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 完全没有Adaptive方法,则不需要生成Adaptive类
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
        
        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            Class<?>[] pts = method.getParameterTypes();
            Class<?>[] ets = method.getExceptionTypes();

            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // 对于有Adaptive注解的方法,判断其入参中是否有URL类型的参数,或者复杂参数中是否有URL类型的属性,若没有则抛出异常。
                // 这里体现出了为什么dubbo要提供动态适配器生成机制。dubbo中的URL总线提供了服务的全部信息,而开发者可以定义差异化的服务配置,因此生成的URL差异化也较大,若全部靠用户硬编码静态适配器的话效率太低。
                // 有了动态代理,dubbo可以根据URL参数动态地生成适配器的适配逻辑,确定扩展实现的获取优先级。因此,URL作为参数直接或间接传入是必须的,否则失去了动态生成的凭据。
                // 有类型为URL的参数
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);
                    
                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                    code.append(s);
                }
                // 参数没有URL类型
                else {
                    String attribMethod = null;
                    
                    // 找到参数的URL属性
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if(attribMethod == null) {
                        throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                        		+ ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }
                    
                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                    code.append(s);
                }
                
                String[] value = adaptiveAnnotation.value();
                // 没有设置Key,则使用“扩展点接口名的点分隔 作为Key
                if(value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if(Character.isUpperCase(charArray[i])) {
                            if(i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        }
                        else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }
                
                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }
                
                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    if(i == value.length - 1) {
                        if(null != defaultExtName) {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                        }
                        else {
                            if(!"protocol".equals(value[i]))
                                if (hasInvocation) 
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    }
                    else {
                        if(!"protocol".equals(value[i]))
                            if (hasInvocation) 
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                		"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);
                
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);
                
                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }
            
            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i ++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i ++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(ets[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }

В соответствии со значением массива значений адаптивной аннотации и расширением по умолчанию, определенным аннотацией SPI, определите логику адаптации, то есть приоритет получения расширения., код здесь больше не указан, ниже приведен конкретный сгенерированный исходный код адаптера:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative 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.common.URL {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        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.common.URL {
        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);
    }
}

можно увидеть,Основная логика состоит в том, чтобы получить расширение extName, взяв в качестве примера метод привязки,Его приоритет приобретения: сервер, транспортер, сеть., см. исходный код метода getParameter URL-адреса. Где netty — это значение по умолчанию, определяемое аннотацией SPI интерфейса Transporter, а сервер и транспортер определяются аннотацией Adaptive метода привязки.

@SPI("netty")
public interface Transporter {
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

После получения расширения получите экземпляр расширения из ExtensionLoader и вызовите конкретный метод привязки..

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

Таким образом, ExtensionLoader предоставляет метод получения адаптеров расширения.Сначала проверьте, есть ли статический адаптер, иначе будет использоваться динамический адаптер..

2.2.4 Класс упаковки

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

    @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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // IOC 注入
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                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);
        }
    }
    
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 遍历当前实例所有方法,判断是否需要进行set属性注入
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 通过ExtensionFactory获取被注入set属性实例
                            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;
    }

Здесь есть метод injectExtension, его роль:

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

Таким образом, ExtensionLoader, как контейнер плагинов IOC, дает гарантию работы системы плагинов dubbo, которую можно назвать ядром dubbo. Освоение его основных принципов поможет нам лучше анализировать исходный код dubbo.