Статья о приложении Springboot Пример реализации FactoryBean и прокси-сервера SPI

Java задняя часть

Другие весенние статьи, добро пожаловать, нажмитеСерый блог-весенняя тема

FactoryBean — интересное существование в Spring.Хотя он редко используется в ежедневной разработке бизнеса, в некоторых сценариях, если его правильно использовать, он может достичь многих интересных вещей.

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

Знания, раскрытые в статье

  • Механизм SPI
  • FactoryBean
  • JDK динамический агент

I. Соответствующие точки знаний

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

1. Описание фона демо

Прежде чем мы начнем, необходимо понять, какие сценарии мы собираемся делать.

В электронной коммерции есть более подходящий пример — отображение страниц сведений о продукте. Возьмите страницу сведений отдела Taobao в качестве фона для иллюстрации (не работал в Ali, следующие вещи предназначены исключительно для иллюстрации сценария приложения)

商品详情页

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

Как показано на рисунке выше, мы предполагаем, что есть несколько мест с небольшими различиями.

Место расположения Детали Таобао Детали Tmall Детали соленой рыбы инструкция
banner Показать фоновую стену Taobao Показать рекламное место Tmall Яма из соленой рыбы Структура данных трех файлов одинакова, различается только URL-адрес изображения.
рекомендовать Рекомендованные похожие товары Рекомендуемый магазин другие продукты Рекомендовать тот же тип подержанных товаров Структура данных та же, но содержание другое.
Оценка обзор продукта обзор продукта Нет комментариев, вместо этого оставьте сообщение
повышение купон Купон на баллы Tmall нет купона -

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

2. Краткое введение в SPI

А. Основные определения

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

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

  • ссылка на интерфейс
  • Во время конкретного выполнения, в соответствии с определенными условиями, выберите фактический подкласс для выполнения

Из вышеприведенного описания видно, что одним из самых больших преимуществ является:

  • Расширяя реализацию интерфейса, можно добиться расширения услуг; нет необходимости изменять исходный бизнес-код.

б) демонстрационные вспомогательные инструкции

Простой сценарий применения выглядит следующим образом

报警系统demo

В этой сигнализации, для пользователя, черезIAlarm#sendMsg(level, msg)Однако конкретный исполнитель этой строки неизвестен (игнорировать, регистрировать тревогу, тревогу по электронной почте или тревогу по SMS), и метод реализации через SPI будет следующим.

  • Если уровень равен 1, игнорировать содержание тревоги
  • Если уровень равен 2, используйте метод регистрации аварийных сигналов для аварийного оповещения.
  • ...

Что, если мы хотим добавить новый метод будильника? Это тоже очень просто, создайте новую реализацию будильника

  • уровень == 5, используется будильник WeChat

Тогда для пользователя нет необходимости менять другие места, просто замените параметр входящего уровня на 5.

3. Краткое описание режима прокси

Можно сказать, что режим прокси является очень, очень, очень распространенным шаблоном проектирования в Spring. Знаменитый АОП является классическим случаем этой реализации. У обычных прокси есть два метода реализации.

  • Метод JDK
  • Метод CGLIB

Вкратце определение и описание режима прокси выглядят следующим образом.Внедрение MVC: 3. Шаблон прокси для подготовки реализации АОП

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

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

II. Разработка схемы и ее реализация

Поняв вышеприведенную предпосылку, мы можем рассмотреть, как реализовать набор инструментов SPI в контейнере Spring.

1. Целевое разделение

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

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

  • Программа: при инъекции зависимости, непосредственно в соответствии с критериями выбора, экземпляр, удовлетворяющий инъекции, все последующие вызовы SPI, примет этот конкретный пример выполнения вызова
  • Вариант 2: при внедрении зависимостей вместо внедрения конкретного экземпляра зарегистрируйте прокси-класс.В прокси-классе для выполнения выбирается конкретный подходящий экземпляр в соответствии с параметрами вызова.Поэтому конкретный экземпляр, выбранный для последующих вызовов, будет быть таким же, как входящий параметр, связанный

Сравнение схем

Вариант первый Вариант 2
Близко к использованию JDK SPI Метод прокси выбирает соответствующий экземпляр
Достоинства: Простота, удобство использования и обслуживания. Гибкость для поддержки более творческих расширений
Недостатки: один к одному, недостаточно многоразовости, не может поддерживать предыдущий случай Методы реализации и вызова немного сложнее, и вам нужно передать параметры для выбора конкретных условий экземпляра.
Каждый раз, когда выбирается подкласс, требуются дополнительные вычисления.

После сравнения двух вышеприведенных схем выбираем вторую (разумеется, основная причина — демонстрация FactoryBean и прокси для реализации механизма SPI. Если выбрать первую схему, таких двух не бывает)

После того, как план выбран, целевое разделение становится более четким

  • Определить интерфейс SPI и режим использования SPI (предпосылка)
  • FactoryBean, который генерирует прокси-классы (ядро)

2. Схематический дизайн

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

А. Определение интерфейса

Ядро разработанной микроструктуры SPI:При выполнении определить конкретный экземпляр для выполнения в соответствии с входящими параметрами, так что в нашем дизайне интерфейса есть как минимум один интерфейс, определяющий, выбирать ли данный инстанс по входящим параметрам

public interface ISpi<T> {
    boolean verify(T condition);
}

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

public interface ISpi<T> {
    boolean verify(T condition);

    /**
     * 排序,数字越小,优先级越高
     * @return
     */
    default int order() {
        return 10;
    }
}

После того, как интерфейс определен, как пользователь должен его использовать?

Б. Использование ограничений

Ограничения реализации spi

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

Во-вторых, в экосистеме Spring все реализации SPI должны быть Beans, которые должны автоматически сканироваться или объявляться аннотациями конфигурации, иначе прокси-класс не сможет получить все реализации SPI.

Ограничения, используемые spi

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

Глядя только на приведенное выше описание, может быть нелегко понять.Рекомендуется объединить следующие примеры, чтобы продемонстрировать и сравнить

c. Генерация прокси-класса

Это самое основное место (хотя по важности №1, реализация на самом деле очень и очень простая)

Основная цель прокси-класса — выбрать конкретного исполнителя по входящим параметрам при выполнении конкретного вызова, а затем вернуть соответствующий результат после выполнения.

  • Получить все классы реализации SPI (org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(java.lang.Class<T>))
  • Создайте прокси-класс через jdk, в прокси-классе пройдите все реализации SPI, сопоставьте в соответствии с первым параметром, переданным в качестве условия, найдите первый класс реализации SPI и выполните

Относительно просто реализовать описанные выше шаги в деталях.

public class SpiFactoryBean<T> implements FactoryBean<T> {
    private Class<? extends ISpi> spiClz;

    private List<ISpi> list;

    public SpiFactoryBean(ApplicationContext applicationContext, Class<? extends ISpi> clz) {
        this.spiClz = clz;

        Map<String, ? extends ISpi> map = applicationContext.getBeansOfType(spiClz);
        list = new ArrayList<>(map.values());
        list.sort(Comparator.comparingInt(ISpi::order));
    }

    @Override
    @SuppressWarnings("unchecked")
    public T getObject() throws Exception {
        // jdk动态代理类生成
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                for (ISpi spi : list) {
                    if (spi.verify(args[0])) {
                        // 第一个参数作为条件选择
                        return method.invoke(spi, args);
                    }
                }

                throw new NoSpiChooseException("no spi server can execute! spiList: " + list);
            }
        };

        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{spiClz},
                invocationHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return spiClz;
    }
}

3. Пример демонстрации

После проектирования схемы ее следует реализовать, однако, поскольку реализация слишком проста, в процессе проектирования также прописывается, что является определением интерфейса выше.ISpiи один для создания динамических прокси-классовSpiFactoryBean

Затем напишите простой пример для демонстрации функции, определитеIPrintдля вывода текста и дает две реализации: одну для вывода на консоль и одну для вывода журнала.

public interface IPrint extends ISpi<Integer> {

    default void execute(Integer level, Object... msg) {
        print(msg.length > 0 ? (String) msg[0] : null);
    }

    void print(String msg);
}

Конкретный класс реализации выглядит следующим образом: внешние пользователи проходятexecuteвызов реализации метода, гдеlevel<=0выберите вывод консоли, когда он выбран; в противном случае выберите вывод файла журнала

@Component
public class ConsolePrint implements IPrint {
    @Override
    public void print(String msg) {
        System.out.println("console print: " + msg);
    }

    @Override
    public boolean verify(Integer condition) {
        return condition <= 0;
    }
}

@Slf4j
@Component
public class LogPrint implements IPrint {
    @Override
    public void print(String msg) {
        log.info("log print: {}", msg);
    }

    @Override
    public boolean verify(Integer condition) {
        return condition > 0;
    }
}

Предыдущие шаги ничем не отличаются от обычного метода письма.Какая используется поза?

@SpringBootApplication
public class Application {

    public Application(IPrint printProxy) {
        printProxy.execute(10, " log print ");
        printProxy.execute(0, " console print ");
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

см. вышеApplicationКонструктор , требует передачиIPrintпараметр, Spring найдет bean-компонент из контейнера для передачи в качестве параметра, и этот bean-компонент является сгенерированным нами прокси-классом, чтобы конкретный класс реализации можно было выбрать в соответствии с различными параметрами.

Итак, вопрос в том, как объявить этот прокси-класс, конфигурация следующая, Bean объявляется FactoryBean, а@PrimaryАннотация, чтобы мы могли убедиться, что объявленный нами прокси-класс внедряется.

@Configuration
public class PrintAutoConfig {

    @Bean
    public SpiFactoryBean printSpiPoxy(ApplicationContext applicationContext) {
        return new SpiFactoryBean(applicationContext, IPrint.class);
    }

    @Bean
    @Primary
    public IPrint printProxy(SpiFactoryBean spiFactoryBean) throws Exception {
        return (IPrint) spiFactoryBean.getObject();
    }
}

Вышеупомянутая логика использования и задействованные точки знаний были представлены в предыдущих сообщениях блога.

Следующий шаг — выполнить его и посмотреть, каков будет результат.

演示demo

III. Другое

0. Связанный с проектом

А. Больше сообщений в блоге

Основы

Применение

б. Исходный код проекта

1. Серый блог

  • One Grey BlogПерсональный блогblog.hhui.top
  • Серый блог - специальный весенний блогspring.hhui.top

Серый личный блог, записывающий все посты блога по учебе и работе, приглашаю всех в гости

2. Заявление

Это не так хорошо, как письмо. Вышеупомянутое содержание чисто из семьи. Из-за ограниченных личных способностей неизбежно будут упущения и ошибки. Если вы обнаружите ошибки или у вас есть лучшие предложения, вы можете критиковать и поправьте их.