Серия шаблонов проектирования — прокси-шаблоны

интервью Java
Серия шаблонов проектирования — прокси-шаблоны

Есть чувства, есть галантерейные товары, поиск в WeChat【Третий принц Ао Бин] Подпишитесь на этого программиста, у которого есть кое-что.

эта статьяGitHub github.com/JavaFamilyВключено, и есть полные тестовые площадки, материалы и мой цикл статей для интервью с производителями первой линии.

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

На этот раз я хочу поделиться с вами тремя типами создания в шаблоне проектирования.прокси-режим, агентская модель может не часто использоваться в бизнес-сценариях, но интервьюер часто задает вопрос?

Не могли бы вы рассказать мне о прокси-режиме АОП в Spring? В чем разница между прокси-режимом jdk и прокси-режимом cglib?

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

Ближе к делу, давайте начнем поэтапно анализировать модель агентства.

Определение и цель

Во-первых, режим прокси можно разделить на различные типы.

  • Удаленный прокси: делегирование работы удаленному объекту (другому серверу или другому процессу) для завершения. Обычно используется в веб-сервисах. Там же наш вызов RPC тоже можно понимать как удаленный прокси.
  • Агент защиты: в этом режиме в основном выполняются проверки безопасности/разрешений. (маленький контакт)
  • Кеширование прокси: Это понятно, что для ускорения вызова через хранилище.Например, метод @Cacheable в Sping кэширует результаты, полученные по определенным параметрам.При следующем вызове метода с теми же параметрами данные возвращается непосредственно из кэша.
  • Виртуальный агент: Этот тип агента в основном добавляет функции к методу, такие как запись некоторых показателей производительности и т. д., или задержка инициализации.

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

  • Тема (общий интерфейс): существующий интерфейс, используемый клиентом.
  • RealSubject: класс реального объекта
  • ProxySubject: класс прокси

Как видно из рисунка, весь интерфейс по-прежнему очень простой, то есть реальный объект и прокси-объект.

Цель: Предоставить фактический прокси-объект для лучшего контроля над фактическим объектом. Приведенное выше определение взято из книги «Красота шаблонов проектирования».

реализация примера кода

Чтобы облегчить понимание, давайте рассмотрим пример. Я не знаю, сталкивались ли вы с процессом передачи небольших заметок в средней или старшей школе. Если однокласснику А есть о чем поговорить с одноклассником С (например, встреча после в школу, чтобы играть в игры), но поскольку сейчас во время урока, я не могу сказать это вслух, между одноклассником A и одноклассником C сидит ученик B, так что теперь одноклассник A может сначала найти одноклассника B и дать ему обратите внимание и попросите его рассказать об этом однокласснику С, но играть все же можно, играть, это может решить только сам настоящий одноклассник С.

Следовательно, агентский режим можно понимать так: одноклассник B является агентом одноклассника C. Если одноклассник A хочет найти одноклассника C, можно найти только одноклассника B, и одноклассник C будет передан однокласснику C через одноклассника B, и выполнение результат одноклассника C будет возвращен однокласснику A.

После разговора о примере давайте взглянем на реализацию кода.

public interface Subject {
   // 共同的接口
    void doSomething();
}

Определите общий интерфейс (то есть то, что все должны делать, пожалуйста: вместе играть в игры после школы)

public class RealSubject implements Subject {
  	// 真实对象
    @Override
    public void doSomething() {
        System.out.println("放学去打游戏");
    }
}

Построить реальный объект, одноклассник C в примере

public class ProxySubject implements Subject {

    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public ProxySubject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        this.realSubject = (RealSubject) this.getClass().getClassLoader().loadClass("com.ao.bing.demo.proxyPattern.RealSubject").newInstance();
    }

    @Override
    public void doSomething() {
        realSubject.doSomething();
    }


    public static void main(String[] args) {

        try {
            // 第一种方式
            new ProxySubject().doSomething();
            // 打印结果: 放学去打游戏
        } catch (Exception e) {
            // 异常情况,代理失败,
            // 传纸条的被老师抓了。或者同学C不在座位上了 等异常情况
        }

        // 第二种方式
        new ProxySubject(new RealSubject()).doSomething();
        // 打印结果: 放学去打游戏
    }
}

Создайте прокси-объект, то есть одноклассник B, тогда вы увидите, что одноклассник A на самом деле не контактировал с одноклассником C. Через прокси одноклассника B к однокласснику C вы можете узнать, может ли одноклассник C играть с ним в игры после школы.

В методе Main есть два способа вызова реального объекта

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

  • Второй: передать сериализованный объект в виде передачи по значению. (понимается как шаблон декоратора)

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

Статический прокси, динамический прокси и анализ прокси cglib

статический прокси

Реализация приведенного выше примера на самом деле представляет собой статический прокси, и вы можете видеть, что все относительно просто. Но и его недостатки очевидны

Статические прокси требуют создания прокси-класса для каждого объекта, что увеличивает затраты на обслуживание и затраты на разработку.Для решения этой проблемы выходят динамические прокси.Не создавайте прокси-класс для каждого класса, которому нужны прокси.

Динамический прокси

Динамические прокси-серверы разумно избегают статических прокси-серверов, не создавая заранее классы прокси для классов, которые будут проксироваться. Скорее, он создается во время выполнения с помощью механизмов отражения.

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

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

public interface BaseService {
    void mainService();
}

public class Cicada implements BaseService {
    @Override
    public void mainService() {
        System.out.println("主要业务,以蝉为例,当蝉出现业务调用时,螳螂监听到");
    }
}

Создавайте общие интерфейсы, а также настоящие объектные цикады

public class PrayingMantis implements InvocationHandler {

    private BaseService baseService;

		// 这里采用的是构建传参数,可以用反射,举的第一个例子有样式代码
    public PrayingMantis(BaseService baseService) {
        this.baseService = baseService;
    }

    // 螳螂主要业务,也就是监听对象
    @Override
    public Object invoke(Object listener, Method method, Object[] args) throws Throwable {
        method.invoke(baseService,args);
        secondaryMain();
        return null;
    }
    // 这里理解增强业务,即我们可以在实现InvocationHandler里面添加其他的业务,比如日志等等。
    private void secondaryMain(){
        System.out.println("螳螂捕蝉 - 次要业务");
    }
}

Создайте класс богомола, который слушает действия класса цикады.

public class BeanFactory {

    public static BaseService newInstanc(Class classFile) {
        // 1. 创建蝉,真实类对象
        BaseService trueCicada = new Cicada();
        // 2.创建代理类 螳螂
        InvocationHandler prayingMantis = new PrayingMantis(trueCicada);
        // 3.向Jvm索要代理对象 其实就是监听的对象,
        Class classArray[] = {BaseService.class};
        BaseService baseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, prayingMantis);
        return baseService;
    }
  
  	// 测试Demo
    public static void main(String[] args) {
        BaseService baseService  = newInstanc(Cicada.class);
        baseService.mainService();
        // 测试结果 :主要业务
        //           螳螂捕蝉 - 次要业务
    }
}

Из результатов видно, что когда вызывается основной бизнес Cicada, Mantis может отслеживать бизнес Cicada и обрабатывать другую бизнес-логику, поэтому AOP в Spring может обрабатывать аспекты журнала и так далее.

Характер прокси:

Я думаю, что на самом деле это своего рода мониторинг поведения, мониторинг поведения прокси-объекта ($proxy InvocationHandler).

Состав режима прокси:

  • Интерфейс: объявляет поведение, которое необходимо отслеживать
  • Класс реализации прокси (InvocationHandler): вторичный бизнес, вторичный бизнес и выполнение основной бизнес-привязки
  • Прокси-объект (объект прослушивания)

Cglib динамический прокси

Динамический прокси cglib на самом деле очень похож на динамический прокси jdk, и все это делается путем реализации интерфейса прокси.

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

public class PrayingMantis implements MethodInterceptor {

    private Cicada cicada;// 代理对象

    public Cicada getInstance(Cicada cicada) {
        this.cicada = cicada;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.cicada.getClass());
        enhancer.setCallback(this);
        return (Cicada) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object object = methodProxy.invokeSuper(o, objects);
        secondaryMain();
        return object;
    }

    private void secondaryMain() {
        System.out.println("螳螂捕蝉 - 次要业务");
    }

    public static void main(String[] args) {
        PrayingMantis prayingMantis = new PrayingMantis();
        Cicada instance = prayingMantis.getInstance(new Cicada());
        instance.mainService();
        // 结果:主要业务
        //      螳螂捕蝉 - 次要业务
    }

Поскольку цикады все одинаковые, я не буду размещать их здесь по отдельности.

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

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

Конечный класс: класс не может быть унаследован, а внутренние методы и переменные становятся конечными типами.

Различия между JDK и Cglib:

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

Динамический прокси-сервер cglib использует пакет с открытым исходным кодом ASM, загружает файл класса класса прокси-объекта и создает подкласс, изменяя его байт-код для его обработки.

ASM: среда обработки байт-кода Java. Его можно использовать для динамического создания классов или для улучшения функциональности существующих классов. ASM может напрямую генерировать бинарные файлы классов или динамически изменять поведение классов перед их загрузкой в ​​виртуальную машину Java. Классы Java хранятся в строго отформатированных файлах .class, метаданных которых достаточно для разрешения всех элементов класса: имени класса, методов, свойств и байт-кода Java (инструкций). После того, как ASM прочитает информацию из файлов классов, он может изменить поведение класса, проанализировать информацию о классе и даже создать новые классы в соответствии с требованиями пользователя. -- Приведенное выше объяснение ASM исходит от Цзяньшу.

Многоуровневый динамический прокси

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

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

public class Cardinal implements InvocationHandler {
		// 监听代理代理对象
    private Object proxyOne;

    public Cardinal(Object proxyOne) {
        this.proxyOne = proxyOne;
    }

    // 螳螂主要业务,也就是监听对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws            Throwable {
        method.invoke(proxyOne, args);
        secondaryMain();
        return null;
    }
    private void secondaryMain() {
        System.out.println("黄雀吃螳螂 - 次要业务");
    }
}

Создайте прокси-объект иволги, затем, как его реальный объект, он станет богомолом.Когда вызывается объект богомола, иволга может стоять твердо и в то же время выполнять соответствующую бизнес-логику.

public class BeanFactory {

    public static BaseService newInstanc(Class classFile) {
        // 1. 创建蝉,真实类对象
        BaseService trueCicada = new Cicada();

        // 2.创建代理类 螳螂
        InvocationHandler prayingMantis = new PrayingMantis(trueCicada);

        // 3.向Jvm索要代理对象 其实就是坚挺的对象
        Class classArray[] = {BaseService.class};
        BaseService baseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, prayingMantis);

        // 4.创建代理实现类 黄雀 二级代理
        InvocationHandler cardinal = new Cardinal(baseService);
        BaseService secondBaseService = (BaseService) Proxy.newProxyInstance(classFile.getClassLoader(), classArray, cardinal);
        
      	// 假设要实现三级,四级代理,则在黄雀类上再加一层代理即可实现。
      	// 省略其他的更多级代理对象
        return secondBaseService;
    }
  
 			 // 测试demo
      public static void main(String[] args) {
        BaseService baseService  = BeanFactory.newInstanc(Cicada.class);
        baseService.mainService();
        // 结果:主要业务
        //      螳螂捕蝉 - 次要业务
        //      黄雀吃螳螂 - 次要业务
    }
}

По этому коду в основном реализуется многоуровневый прокси-процесс. Богомол следит за движениями цикад, а иволга следит за движениями богомолов.

Точно так же, если вы хотите реализовать трехуровневый прокси, четырехуровневый прокси не представляет сложности, просто добавьте прокси-объект поверх каждого слоя.

Суть динамического прокси по-прежнему можно понимать как отделение «второстепенного бизнеса» от «основного бизнеса», что позволяет разработчикам больше сосредоточиться на основном бизнесе, повышая эффективность разработки и затраты на обслуживание.

Суммировать

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

После понимания динамического прокси для нас самоочевидно понять принцип АОП.

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

Я Ао Бин,Чем больше вы знаете, тем больше вы не знаете, спасибо за ваши таланты:как,собиратьиКомментарий, увидимся в следующий раз!


Статья постоянно обновляется, вы можете искать в WeChat "Третий принц Ао Бин"Прочтите это в первый раз, ответьте [материал] Подготовленные мной материалы интервью и шаблоны резюме крупных заводов первой линии, эта статьяGitHub github.com/JavaFamilyОн был включен, и есть полные тестовые сайты для интервью с крупными заводами.Добро пожаловать в Star.