Анализ исходного кода Dubbo — механизм расширения фреймворка

Dubbo
Анализ исходного кода Dubbo — механизм расширения фреймворка

1. Загрузочный механизм

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

1.1 Java SPI и Dubbo SPI

**API (интерфейс прикладного программирования)** В большинстве случаев разработчик создает интерфейс и завершает реализацию интерфейса. Вызывающая сторона полагается только на вызов интерфейса и не имеет права выбирать другую реализацию. С точки зрения пользователя API используется непосредственно разработчиками приложений.

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

Ниже приведен простой пример использования Java SPI: в каталоге META-INF/Services/ необходимо создать файл с полным путем к интерфейсу, например com.seewo.dubbo.demo.spi.printService. файл.Содержимое файла — это конкретная реализация.Полный путь к классу, разделенный символами новой строки, если существует несколько классов реализации.

META-INF/services/com.seewo.dubbo.demo.spi.printService文件内容为实现类的全路径名
com.seewo.dubbo.demo.spi.PrintServiceImpl
com.seewo.dubbo.demo.spi.PrintServiceNewImpl

public interface PrintService {
    void printInfo();
}

public class PrintServiceNewImpl implements PrintService {
    @Override
    public void printInfo() {
        System.out.println("new hello world");
    }
}

public class PrintServiceImpl implements PrintService {
    @Override
    public void printInfo() {
        System.out.println("hello world");
    }
}

public class jdkDemo {
     public static void main(String[] args) {
        ServiceLoader<PrintService> serviceServiceLoader =
            ServiceLoader.load(PrintService.class);
        //获取所有的SPI实现,循环调用printInfo方法
        for (PrintService printService : serviceServiceLoader) {
            //打印出"new hello world" "hello world"
            printService.printInfo();        }
    }
}

Часть базовой реализации ServiceLoader показана ниже.Здесь указывается путь сохранения конфигурационного файла, здесь реализуется итератор.При выполнении итерации цикла используется загрузчик класса ClassLoader текущего потока.

public final class ServiceLoader<S> implements Iterable<S> {

    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";
    // 被加载的类或接口
    private final Class<S> service;
    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;
    // 上下文对象
    private final AccessControlContext acc;
    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;

    public static <S> ServiceLoader<S> load(Class<S> service) {
        //使用当前线程的类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }    

    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;

        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取扫描的路径
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

Как высоко расширяемая среда RPC, Dubbo также полагается на SPI Java, а Dubbo расширил собственный механизм SPI Java, чтобы сделать его более мощным. Эффект SPI также может быть достигнут с помощью ExtensionLoader в среде Dubbo, но в класс интерфейса необходимо добавить аннотацию @SPI и соответствующее имя класса реализации «impl». ) в соответствующем файле конфигурации.

META-INF/dubbo/com.seewo.dubbo.demo.spi.printService文件内容如下
impl=com.seewo.dubbo.demo.spi.PrintServiceImpl

@SPI("impl")
public interface PrintService {
    void printInfo();
}

PrintService printService = 
    ExtensionLoader.getExtensionLoader(PrintService.class).getDefaultExtension();
printService.printInfo();

Из приведенного выше принципа Java SPI мы можем знать, что механизм Java SPI имеет следующие недостатки:

  • Вы можете только просмотреть все реализации и создать их все.Если есть расширенные реализации, инициализация которых занимает много времени, но которые нельзя использовать позже, это приведет к пустой трате ресурсов.
  • Файл конфигурации z просто перечисляет все реализации расширений, не называя их. Точно указать их в программе сложно.
  • Если расширение зависит от других расширений, оно не может быть автоматически внедрено и собрано. Расширение сложно интегрировать с другими фреймворками, например, расширение зависит от bean-компонента Spring, который не поддерживается собственным Java SPI.

Dubbo SPI имеет следующие концепции:

  • точка расширения: интерфейс;

  • расширение: реализация расширения;

  • Адаптивный экземпляр расширения: фактически это прокси-сервер расширения, который реализует интерфейс точки расширения. При вызове метода интерфейса точки расширения он решает, какое расширение использовать на основе фактических параметров. Фреймворк dubbo автоматически решит, какую реализацию выбрать на основе параметров в интерфейсе;

  • @SPI: эта аннотация действует на интерфейс точки расширения, указывая, что интерфейс является точкой расширения;

  • @Adaptive: аннотация @Adaptive используется в методах, расширяющих интерфейс. Указывает, что метод является адаптивным. Когда Dubbo создает адаптивный экземпляр для точки расширения, если метод имеет аннотацию @Adaptive, он сгенерирует соответствующий код для метода.

1.2 Спецификация конфигурации точки расширения

Dubbo SPI похож на Java SPI. Соответствующий файл конфигурации SPI необходимо поместить в путь META-INF/dubbo/. Имя файла представляет собой полное имя интерфейса, которому необходимо дать имя. Содержимое файла конфигурации ключ=точка расширения Имя пути или новая строка в качестве разделителя, если имеется несколько классов реализации. Среди них ключевым является параметр, передаваемый в аннотации Dubbo SPI. Кроме того, Dubbo SPI также совместим с путем конфигурации и методом конфигурации содержимого Java SPI.При запуске платформы Dubbo он будет сканировать META-INF/services, META-INF/dubbo/, META-INF/dubbo/internal/ по умолчанию три пути.

  • Путь к файлу конфигурации SPI: META-INF/services, META-INF/dubbo/, META-INF/dubbo/internal/
  • Имя файла конфигурации SPI: полное имя класса пути
  • Формат содержимого файла: ключ=значение, несколько разделены символами новой строки.

1.3 Классификация и кэширование точек расширения

Dubbo SPI разделен на кеш класса и кеш экземпляра. Эти два вида кешей можно дополнительно разделить на обычные классы расширения, классы расширения-оболочки (класс-оболочка), адаптивные расширения (класс Adaptive) и т. д. в соответствии с типами классов расширения.

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

Кэшированные экземпляры класса и объекта можно разделить на разные категории в соответствии с различными характеристиками:

  • Общий класс расширения, реализация класса расширения, настроенная в файле конфигурации SPI;
  • Класс расширения Wrapper, этот класс Wrapper не имеет конкретной реализации, только абстрагирует общую логику и должен передавать конкретную реализацию интерфейса расширения в конструкторе.
  • Адаптивный класс расширения, интерфейс расширения будет иметь несколько классов реализации, класс реализации для использования определяется динамически и настраивается во время выполнения в соответствии с некоторыми параметрами входящего URL-адреса.
  • Другие кеши, такие как кеш загрузчика классов расширений, кеш расширений и т. д.

Конкретная реализация кэша реализована в ExtensionLoader следующим образом:

public class ExtensionLoader<T> {

    //扩展类与对应的扩展类加载器缓存
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>>
         EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

    //扩展类与类实例缓存
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES 
        = new ConcurrentHashMap<>(64);

    //扩展类与扩展名缓存
    private final ConcurrentMap<Class<?>, String> cachedNames         = new ConcurrentHashMap<>();

    //普通扩展类缓存,不包括自适应扩展类和Wrapper类
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    //扩展名与有@Activate注解对象的缓存
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
   
     //扩展类名与扩展对象缓存
    private final ConcurrentMap<String, Holder<Object>> cachedInstances 
        = new ConcurrentHashMap<>();

    //实例化后的自适应(Adaptive)扩展对象,只能同时存在一个
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();

    //Wrapper类缓存
    private Set<Class<?>> cachedWrapperClasses;
}

1.4 Характеристики точек расширения

1.4.1 Автоматическая упаковка

Когда ExtensionLoader загружает расширение, если он обнаруживает, что класс расширения содержит другие точки расширения в качестве параметров своего конструктора, класс расширения будет считаться классом-оболочкой. Возьмем в качестве примера класс ProtocolFilterWrapper. Этот класс наследует интерфейс Protocol. Конструктор этого класса вводит параметр типа Protocol. Поэтому ProtocolFilterWrapper идентифицируется как класс Wrapper. Это режим декоратора, который может инкапсулировать общие логические абстракции или Усовершенствуйте его подклассы, чтобы подклассы могли больше сосредоточиться на собственной реализации.

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    ....
}

1.4.2 Автозагрузка

В дополнение к передаче других экземпляров расширения в конструкторе метод установки часто используется для установки значения свойства.Когда ExtensionLoader выполняет инициализацию точки расширения, он автоматически внедряет соответствующий класс реализации через метод установки. В определении следующей точки расширения транспорта в @Adaptive передаются два параметра, а именно «сервер» и «транспорт». Когда метод Transport#bind вызывается извне, значение параметра «сервер» будет динамически извлекаться из переданного параметра «URL». Если он соответствует расширенному классу реализации, соответствующий расширенный класс реализации будет использоваться напрямую. Если совпадения нет, значение value извлекается через второй параметр «транспорт», и если совпадения нет, генерируется исключение. То есть, если в @Adaptive передается несколько параметров, сопоставление классов реализуется по очереди, пока в конце не будет выдано исключение.

@SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
  
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

1.4.3 Адаптивный

В Dubbo SPI аннотация @Adaptive используется для динамического определения того, какой конкретный класс реализации использовать с помощью параметров в URL-адресе.

1.4.4 Автоматическая активация

Используя аннотацию @Activate, вы можете пометить соответствующую точку расширения для активации по умолчанию.Аннотация также может передавать различные параметры, чтобы точка расширения автоматически активировалась при различных условиях.

2. Использование аннотаций точек расширения

2.1 Аннотация @SPI

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

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
    //默认实现的key名称
    String value() default "";
}

2.2 @Адаптивная аннотация

Аннотацию @Adaptive можно пометить на классах, интерфейсах, перечислениях и методах.На уровне класса доступны только AdaptiveExtensionFactory и AdaptiveCompiler, остальные помечены на уровне метода. Аннотация на уровне метода автоматически сгенерирует и скомпилирует динамический адаптивный класс, когда вы получитеExtension в первый раз, чтобы добиться эффекта динамической реализации класса. Параметр массива значений может быть передан в аннотации.При инициализации интерфейса Адаптивной аннотации сначала будет сопоставляться значение ключа входящего URL.Если не совпадает первый ключ, будет сопоставляться второй, пока не будут найдены все ключи Если совпадения нет, для сопоставления используется значение по умолчанию аннотации @SPI.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    //可以设置多个,会被依次匹配
    String[] value() default {};

}

2.3 @Активировать аннотацию

@Activate может быть отмечен на классах, интерфейсах, перечислениях и методах. Он в основном используется при реализации нескольких точек расширения и должен быть активирован в соответствии с различными условиями. Например, фильтр необходимо активировать одновременно, потому что каждый Фильтрующие приспособления имеют разные функции.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    //URL中的分组如果匹配则激活,可以设置多个
    String[] group() default {};
    //如果URL中含有该key值,则会被激活
    String[] value() default {};
    //排序信息
    int order() default 0;
}

3. Как работает ExtensionLoader

ExtensionLoder — основной класс реализации всего механизма расширения, в этом классе реализована загрузка конфигурации, кэширование класса расширения и кэширование адаптивной генерации объектов.

3.1 Создание ExtensionLoader

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

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

Существует несколько конкретных реализаций класса фабрики.Вы можете увидеть три конкретных класса реализации фабрики: SpringExtensionFactory, AdaptiveExtensionFactory и SpiExtensionFactory.

spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

Среди них контейнеры Dubbo и Spring подключены через SpringExtensionFactory Класс реализации фабрики сохраняет статические методы контекста Spring и может сохранять контекст Spring в коллекцию Set, чтобы при многократном вызове можно было добавлять повторяющиеся ссылки на контекст. SpringExtensionFactory предназначен для сохранения контекста Spring при вызове статических методов при инициализации ReferenBean и ServiceBean.

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }
}

public class SpringExtensionFactory implements ExtensionFactory {

    //用来保存Spring上下文的Set集合
    private static final Set<ApplicationContext> CONTEXTS 
            = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }
    ....
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍历所有Spring上下文,根据名字和类型从Spring容器中查找
        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }
        return null;
    }
}

Реализация SpiExtensionFactory относительно проста: непосредственно используйте ExtensionLaoder для получения возвращаемого объекта.

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

AdaptiveExtensionFactory — это реализация по умолчанию, с аннотацией @Adaptive все получат все классы расширения для создания фабрик и их кэширования. Кэшированные фабричные классы сортируются по TreeSet, SPI впереди, а Spring позади. Когда используется getExtension, все фабрики будут Сначала получите класс расширения из контейнера SPI, а если он не найден, получите класс расширения из контейнера Spring.

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    
    //用来缓存所有的工厂类实现,包括SpiExtensionFactory/SpringExtensionFactory
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        //工厂列表是也通过SPI实现的,这里可以获取到所有的工厂实现类
        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;
    }
}

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

Начальная конфигурация ExtensionLoader определяет несколько стратегий загрузки файла ресурсов, а стратегию загрузки файла конфигурации можно задать с помощью ExtensionLoader#setLoadingStrategies. Если вы хотите загружать классы из других файлов каталога, вы можете настроить стратегию загрузки ресурсов и установить ее.

//加载策略接口
public interface LoadingStrategy extends Prioritized {
    //返回加载配置的路径
    String directory();
    //预处理
    default boolean preferExtensionClassLoader() {
        return false;
    }
    //排除在外的包路径
    default String[] excludedPackages() {
        return null;
    }
}

//自定义的资源加载策略
public class DubboExternalLoadingStrategy implements LoadingStrategy {
    @Override
    public String directory() {
        //配置文件保存路径
        return "META-INF/dubbo/external/";
    }
    @Override
    public boolean overridden() {
        return true;
    }
    @Override
    public int getPriority() {
        //加载优先级
        return MAX_PRIORITY + 1;
    }
}

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

private Map<String, Class<?>> loadExtensionClasses() {
        ....
        //遍历多个配置文件加载策略
        for (LoadingStrategy strategy : strategies) {
            //从配置文件保存路径中读取配置并加载
            loadDirectory(extensionClasses, strategy.directory(), 
                type.getName(), strategy.preferExtensionClassLoader(), 
                strategy.overridden(), strategy.excludedPackages());
             ....
        }
        return extensionClasses;
}


private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, 
                                boolean overridden, String... excludedPackages) {        String fileName = dir + type;
        try {
            ....
            //通过getResources()或者getSystemResources得到配置文件
            if (urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }

            if (urls != null) {
                //遍历urls,解析字符串,得到扩展实现类,并缓存起来
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
                }
            }
        } catch (hrowable t) {
            ...
        }
    }
}

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

private void loadClass(Map<String, Class<?>> extensionClasses, 
                        java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        //如果类上有@Adaptive注解,则缓存为自适应类
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        //如果是扩展包装类,则直接缓存起来
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            ...
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }
}

3.2 Разбор основных функций и методов ExtensionLoader

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

3.2.1 Принцип реализации getExtension

При вызове метода getExtension(String name) он сначала проверит, есть ли существующие данные в кэше cachedInstance, и если нет, вызовет createExtension, чтобы начать создание.

public T getExtension(String name) {

        //先从缓存中看有没有现成的数据
        final Holder<Object> holder = getOrCreateHolder(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;
    }
}

private T createExtension(String name) {
        
        ...
        try {
            //从缓存中获取扩展类实例的情况
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向扩展类注入其依赖的属性,如扩展类A又依赖类扩展类B
            injectExtension(instance);

            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                //遍历扩展点包装类,用于初始化包装类实例
                for (Class<?> wrapperClass : wrapperClasses) {
                    //找到构造方法的参数类型为type的包装类,为其注入扩展类实例
                    instance = injectExtension((T) wrapperClass
                            .getConstructor(type)
                            .newInstance(instance));
                }
            }

            //初始化扩展实例
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            ...
        }
}

injectExtension обычно реализует функцию Spring IOC, получает все определенные методы класса посредством отражения, проходит метод в начале метода set и получает тип параметра метода set, а затем использует ExtensionFactory для поиска экземпляра класса расширения. того же типа.Если найдено, то используйте настройки отражения для внедрения в него, что чаще встречается при получении классов-оболочек Wrapper.

private T injectExtension(T instance) {

        ....
        try {
            for (Method method : instance.getClass().getMethods()) {
                //如果不是set开头的方法则跳过
                if (!isSetter(method)) {
                    continue;
                }

                //获取方法第一个参数的类型
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                ....
                try {
                    //通过字符串截取,获取小写开头的类名,如setTestService,截取testService
                    String property = getSetterProperty(method);
                    //通过ExtensionFactory获取实例
                    Object object = objectFactory.getExtension(pt, property);
                    //如果获取了这个扩展类实现,则调用set方法,将实例注入进去
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                 .....
                }

            }
        } catch (Exception e) {
            .....
        }
        return instance;
    }

3.2.2 Принцип реализации getAdaptiveExtension

В методе getAdaptiveExtension автоматически генерируется строка реализации для интерфейса точки расширения.Основная логика реализации такова: для каждого метода, аннотированного @Adaptive в интерфейсе, генерируется реализация по умолчанию (метод без аннотации является пустым реализация), каждая реализация по умолчанию Реализации извлекают значения адаптивных параметров из URL-адреса и динамически загружают точки расширения на их основе. Затем платформа использует другой компилятор для компиляции строки реализации в адаптивный класс и ее возврата.

Аннотация может передаваться в параметре value, который является массивом. Adaptived может передавать несколько значений ключа. При инициализации интерфейса, аннотированного @Adaptive, значение ключа входящего URL будет сопоставляться первым. Если первый ключ не совпадает, сопоставляется второй ключ и так далее. Пока все ключи не будут сопоставлены, если они не были сопоставлены, значение по умолчанию, указанное в аннотации @SPI, будет использоваться для повторного сопоставления.Если аннотация @SPI не имеет значения по умолчанию, будет выдано исключение.

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) {
                       ...
                    }
                }
            }
        }
        return (T) instance;
}

//动态编译生成Class
private Class<?> createAdaptiveExtensionClass() {
    //生成类文件定义字符串
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

3.2.3 Принцип реализации getActivateExtension

Метод getActiveExtension(URL-адрес, строковый ключ, строковая группа) получает все точки расширения автоматической активации. Параметрами являются URL-адрес, несколько ключей, указанных в URL-адресе (разделенных несколькими запятыми), и информация о группе (группа), указанная в URL-адресе.

**
**

public List<T> getActivateExtension(URL url, String[] values, String group) {
        //初始化所有扩展类实现的集合
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);

        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //先加载所有扩展实现类
            getExtensionClasses();
            //遍历map缓存
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();
                String[] activateGroup, activateValue;
                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else {
                    continue;
                }

                //如果满足分组
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    activateExtensions.add(getExtension(name));
                }
            }
            //根据用户URL
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }
        ...
    }
}

4. Реализация динамической компиляции точки расширения

Среди них динамическая компиляция является основой для реализации адаптивных функций.Динамически генерируемый адаптивный класс — это просто строка, которую необходимо скомпилировать, чтобы действительно преобразовать в класс. Динамическая компиляция предназначена для повышения производительности.Вместо того, чтобы каждый раз использовать отражение для прокси-класса, класс создается путем прямой компиляции.Dubbo SPI динамически генерируется с помощью кода и в сочетании с динамическим компилятором может быть гибко создан на основе исходный класс Новый адаптивный класс. Существует три типа компиляторов кода Dubbo: JDK, Javassist и AdaptiveCompiler, каждый из которых реализует интерфейс Compiler.

Интерфейс Compiler имеет аннотацию @SPI, а класс реализации компилятора кода по умолчанию — Javassist. Среди них есть много способов динамического создания классов в Java.Обычные генерируются методами байт-кода, такими как CGLIB/ASM, а Javassist дополняется путем компиляции строковых кодов в классы.

//生成类定义字符串
String getSimpleCodeWithSyntax0(){
     StringBuilder code = new StringBuilder();
     code.append("package org.apache.dubbo.common.compiler.support;");
     code.append("public class HelloServiceImpl_0 implements HelloService {");
     code.append("   public String sayHello() { ");
     code.append("       return \"Hello world!\"; ");
     code.append("   }");
    return code.toString();
}

//使用Javassist编译器编译类定义字符串后生成class
public void testCompileJavaClass1() throws Exception {
   JavassistCompiler compiler = new JavassistCompiler();
   Class<?> clazz = compiler.compile(getSimpleCodeWithSyntax0(), JavassistCompiler.class.getClassLoader());
   Object instance = clazz.newInstance();
   Method sayHello = instance.getClass().getMethod("sayHello");
   sayHello.invoke(instance);
}

5. Резюме

В этой статье представлена ​​некоторая общая информация о Dubbo SPI, в том числе разница между Java SPI, принцип реализации, новые функции Dubbo SPI, спецификация конфигурации и внутренний кеш и т. д. Более важными являются три аннотации @SPI/@Adaptive/@Activate, функции и принципы реализации этих трех аннотаций, а затем в сочетании с основным классом реализации ExtensionLoader, объяснили его механизм генерации / механизм инициализации / реализацию трех основных используемых методов getExtension / getAdaptiveExtension / getActivateExtension и, наконец, объяснили связанную с динамической компиляцией Содержание.

использованная литература

Блог Woohoo.cn on.com/suggestion 107600/afraid/…Принципиальный анализ SPI

Woohoo.Baidu.com/Lincoln?URL=RY…Сравнение производительности отражения и динамического прокси