Понимание механизма SPI в Java

Java задняя часть база данных открытый источник
Понимание механизма SPI в Java
文章首发于Hollis公众号
作者 陈彩华
文章转载交流请联系 caison@aliyun.com

В этой статье анализируется механизм Java SPI, предоставляемый JDK и обычно используемый в проектах с открытым исходным кодом, в надежде предоставить справку для всех, кто занимается реальной практикой разработки и изучает проекты с открытым исходным кодом.

1 Что такое СПИ

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

Общая схема механизма выглядит следующим образом:

Java SPI на самом деле "Интерфейсное программирование + режим стратегии + конфигурационный файл«Механизм динамического нагружения реализован композицией.

Часто существует множество различных схем реализации для каждой абстракции проектирования системы.В объектно-ориентированном проектировании обычно рекомендуется программирование интерфейсов между модулями, а классы реализации не жестко закодированы между модулями. Как только в код вовлекается конкретный класс реализации, нарушается принцип подключаемости: если нужно заменить реализацию, нужно модифицировать код. Для того, чтобы понять, что его нельзя динамически указать в программе при сборке модуля, нужен механизм обнаружения сервисов. Java SPI предоставляет такой механизм: механизм поиска реализации службы для интерфейса. Это чем-то похоже на идею IOC, которая заключается в том, чтобы вынести управление сборкой за пределы программы.Этот механизм особенно важен в модульном дизайне. Итак, основная идея SPI заключается вразъединение.

2 сценария использования

Вкратце, это относится к:Вызывающий объект включает, расширяет или заменяет стратегию реализации фреймворка в соответствии с фактическими потребностями использования.

Более распространенные примеры:

  • Интерфейс загрузки на основе базы данных реализует загрузку классов JDBC загружает драйверы для разных типов баз данных
  • Интерфейс бревенчатого фасада реализует загрузку классов SLF4J загружает классы реализации журнала от разных поставщиков
  • Весна Spring использует множество SPI, таких как: реализация спецификации servlet3.0 для ServletContainerInitializer, автоматическое преобразование типов Type Conversion SPI (Converter SPI, Formatter SPI) и т. д.
  • Даббо Dubbo также использует много SPI для расширения инфраструктуры, но он инкапсулирует собственный SPI, предоставляемый Java, что позволяет пользователям расширять реализацию интерфейса Filter.

3 Введение

Чтобы использовать Java SPI, необходимо соблюдать следующие соглашения:

  • 1. После того, как поставщик услуг предоставляет конкретную реализацию интерфейса, в каталоге META-INF/services пакета jar создается файл с именем «полное имя интерфейса», а его содержимое представляет собой полное имя класса реализации. ;
  • 2. Пакет jar, в котором находится класс реализации интерфейса, помещается в путь к классам основной программы;
  • 3. Основная программа динамически загружает модуль реализации через java.util.ServiceLoder, который находит полное имя класса реализации путем сканирования файлов конфигурации в каталоге META-INF/services и загружает класс в JVM;
  • 4. Класс реализации SPI должен содержать конструктор без параметров;

образец кода

шаг 1, определите набор интерфейсов (скажем, org.foo.demo.IShout) и запишите одну или несколько реализаций интерфейса (скажем, org.foo.demo.animal.Dog, org.foo.demo.animal. Кот).

public interface IShout {
    void shout();
}
public class Cat implements IShout {
    @Override
    public void shout() {
        System.out.println("miao miao");
    }
}
public class Dog implements IShout {
    @Override
    public void shout() {
        System.out.println("wang wang");
    }
}

Шаг 2, Создайте каталог /META-INF/services в папке src/main/resources/, добавьте файл с именем интерфейса (файл org.foo.demo.IShout), а его содержимое — класс реализации, который необходимо применить (вот org .foo .demo.animal.Dog и org.foo.demo.animal.Cat, по одному классу в строке).

местоположение файла

- src
    -main
        -resources
            - META-INF
                - services
                    - org.foo.demo.IShout

содержание документа

org.foo.demo.animal.Dog
org.foo.demo.animal.Cat

Шаг 3, Используйте ServiceLoader для загрузки реализации, указанной в файле конфигурации.

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
        for (IShout s : shouts) {
            s.shout();
        }
    }
}

Вывод кода:

wang wang
miao miao

4 Принцип анализа

Сначала посмотрите на переменные-члены класса подписи класса ServiceLoader:

public final class ServiceLoader<S> implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";

    // 代表被加载的类或者接口
    private final Class<S> service;

    // 用于定位,加载和实例化providers的类加载器
    private final ClassLoader loader;

    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;

    // 缓存providers,按实例化的顺序排列
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private LazyIterator lookupIterator;
  
    ......
}

Ссылаясь на конкретный исходный код конкретного ServiceLoader, объем кода невелик, а всего строк с комментариями 587. После разбора процесс реализации выглядит следующим образом:

  • 1 Приложение вызывает метод ServiceLoader.load В методе ServiceLoader.load сначала создайте новый ServiceLoader и создайте экземпляр переменных-членов в классе, в том числе:

    • загрузчик (тип ClassLoader, загрузчик классов)
    • acc (тип AccessControlContext, контроллер доступа)
    • провайдеры (тип LinkedHashMap, используемый для кэширования успешно загруженных классов)
    • lookupIterator (реализующий функцию итератора)
  • 2 Приложение получает экземпляр объекта через интерфейс итератора ServiceLoader сначала определяет, есть ли кэшированный объект экземпляра в объекте поставщиков переменных-членов (тип LinkedHashMap), и напрямую возвращает значение, если есть кэш. Если кеша нет, выполнить загрузку класса, которая реализована следующим образом:

  • (1) Прочитайте файл конфигурации в META-INF/services/, чтобы получить имена всех классов, которые могут быть созданы.Стоит отметить, что ServiceLoaderФайлы конфигурации в META-INF можно получить через пакеты jar., код реализации конкретной конфигурации загрузки выглядит следующим образом:

        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);
        }
  • (2) Загрузите объект класса с помощью метода отражения Class.forName() и создайте экземпляр класса с помощью метода instance().
  • (3) Кэшировать созданный класс в объекте провайдеров (тип LinkedHashMap) Затем верните объект экземпляра.

5 Резюме

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

недостаток:

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

Более интересно, добро пожаловать на официальный аккаунт автора [архитектура распределенной системы]

Ссылаться на

Базовая технология Java 36 лекций

Учебники по Java™

Java Doc

Service Provider Interface: Creating Extensible Java Applications

Service provider interface

Использование и разрешение Java ServiceLoader

Механизм SPI на основе Java

Углубленный анализ исходного кода механизма SPI в Java

Введение в механизм SPI