Статические прокси-серверы Java, динамические прокси-серверы и прокси-серверы cglib

Java
Статические прокси-серверы Java, динамические прокси-серверы и прокси-серверы cglib

Введение в режим прокси

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

В мире программирования мы можем использовать классы-делегаты и прокси-классы для описания отношений между двумя

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

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

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

Классификация шаблонов прокси

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

    // DataService接口提供了save方法
    public interface DataService {
      public void save();
    }

    // DataServiceImpl类实现了DataService接口,并重写了save方法
    public class DataServiceImpl implements DataService{
      @Override
      public void save() {
        System.out.println("DataServiceImpl method save is called");
      }
    }

Статический прокси (обертка)

    // 代理类
    public class DataServiceProxy implements DataService{
        // 委托类
      DataService dataService = new DataServiceImpl();

      @Override
      public void save() {
        System.out.println("DataServiceProxy method save is called");
        dataService.save();
      }
    }

Что касается приведенного выше анализа кода,DataServiceProxyдля прокси-класса,DataServiceImplдля класса делегата

Анализ преимуществ и недостатков статического прокси:

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

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

использоватьJDK APЯ динамически строю в памятиЭкземпляр объекта прокси-класса

Динамический прокси-класс (далее просто прокси-класс) — это класс, который реализует список интерфейсов, заданных во время выполнения при создании класса, и имеет поведение, описанное ниже:

  • Прокси-интерфейс — это интерфейс, реализуемый прокси-классом.
  • Экземпляр прокси — это экземпляр класса прокси.
  • Каждый экземпляр прокси имеет связанный с ним объект обработчика вызова, который может реализовывать интерфейс InvocationHandler. Вызов метода экземпляра прокси через один из интерфейсов прокси будет отправлен методу Invoke обработчика вызовов экземпляра, передавая экземпляр прокси, объект java.lang.reflect.Method, идентифицирующий вызывающий метод, и массив типа Объект, содержащий параметры . Обработчик вызова соответствующим образом обрабатывает закодированный вызов метода, и возвращаемый им результат возвращается как результат вызова метода в экземпляре прокси.

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

  • java.lang.reflect.Proxy: это основной класс механизма динамического прокси Java, который предоставляет набор статических методов для динамического создания классов прокси и их объектов для набора интерфейсов.

Статические методы прокси

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 

    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 

    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
        InvocationHandler h)
  • java.lang.reflect.InvocationHandler: это интерфейс обработчика вызовов, который настраиваетinvokeметод, который используется для централизованной обработки вызовов методов для объектов динамического прокси-класса и обычно реализует прокси-доступ к классу делегата в этом методе.

Основной метод InvocationHandler

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是,第二个参数是
     * proxy: 代理类实例
     * method: 被调用的方法对象
     * args: 调用参数
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
     */
    Object invoke(Object proxy, Method method, Object[] args)

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

  • java.lang.ClassLoader: это загрузчик классов, отвечающий за загрузку байт-кода в виртуальную машину Java (JVM) и определение для него объектов класса (Class), то класс можно использовать. пройти черезProxy.newProxyInstanceСгенерированный динамический прокси-класс также нуждается в загрузчике классов для его загрузки, прежде чем его можно будет использовать.Единственная разница между ним и обычным классом заключается в том, что его байт-код динамически генерируется JVM во время выполнения, а не существует в конкретном файле .class.

Динамический агентный механизм и его характеристики

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

  1. путем реализацииInvocationHandlerинтерфейс и реализацияinvokeметод создания собственного обработчика вызовов
  2. будучиProxyобозначение классаClassLoaderобъект и一组 interfaceсоздать динамический прокси-класс
  3. Конструктор динамического прокси-класса получается через механизм отражения, и единственным его типом параметра является тип интерфейса вызывающего процессора.
  4. Создайте экземпляр динамического прокси-класса через конструктор и вызовите объект процессора в качестве параметра, который будет передан во время построения.
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 

    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

Фактический процесс использования проще, потому что статический метод newProxyInstance of Proxy инкапсулировал для нас процесс от шага 2 до шага 4, поэтому упрощенный процесс выглядит следующим образом.

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 

    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
         new Class[] { Interface.class }, 
         handler );

Конкретная реализация кода:

класс обработчика вызовов

    public class DataServiceInvovationHandler implements InvocationHandler {
      DataService dataService;

      public DataServiceInvovationHandler(DataService dataService) {
        this.dataService = dataService;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getDeclaringClass() + " " +  method.getName() + "() is called");
        method.invoke(dataService, args);
        return null;
      }
    }

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

    public class DataServiceDynamicProxy {
      DataService dataService = new DataServiceImpl();
      public void save() {
        DataService dataServiceDynamicProxy =
                (DataService) Proxy.newProxyInstance(dataService.getClass().getClassLoader(), new Class[]{DataService.class}, new DataServiceInvovationHandler(dataService));
        dataServiceDynamicProxy.save();
      }
    }

Основное различие между статическим прокси и динамическим прокси заключается в следующем:

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

cglib-прокси

cglib(Библиотека генерации кода) — это сторонняя библиотека классов генерации кода, которая динамически генерирует объект подкласса в памяти во время выполнения для расширения функции целевого объекта.

возможности cglib

  • Динамические прокси JDK имеют ограничение: объекты, использующие динамические прокси, должны реализовывать один или несколько интерфейсов. Если вы хотите проксировать класс, который не реализует интерфейс, вы можете использовать CGLIB для его реализации.
  • CGLIB — это мощный высокопроизводительный пакет генерации кода, который расширяет классы Java и реализует интерфейсы Java во время выполнения. Он широко используется многими средами АОП, такими как Spring AOP и dynaop, для предоставления им методов.interception(перехват).
  • Нижний уровень пакета CGLIB предназначен для преобразования байт-кодов и создания новых классов с помощью ASM, небольшой и быстрой среды обработки байт-кода. Прямое использование ASM не рекомендуется, так как оно требует от вас знакомства с внутренней структурой JVM, включая формат и набор инструкций файлов классов.

cglib с динамическими прокси maxразницато есть

  • Объекты, использующие динамические прокси, должны реализовывать один или несколько интерфейсов.
  • использоватьcglibПрокси-объекту не нужно реализовывать интерфейс, поэтому прокси-класс не имеет вторжения

использоватьcglibнужно представитьcglibизjarМешок,cglibизmavenКоординаты следующие:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>

Код

класс делегата

    public class DataService {
      public void save() {
        System.out.println("class DataService method save is called");
      }
    }

прокси-класс

    public class CglibProxy implements MethodInterceptor {
      private Object target; // 委托类

      public CglibProxy(Object target) {
        this.target = target;
      }

      //为委托类生成代理对象
      public Object getProxyInstance() {
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置委托类为父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
      }


      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("DataService cglib  proxy start...");
        // 通过反射执行委托类的方法
        method.invoke(target, objects);
        System.out.println("DataService cglib proxy end...");
        return null;
      }
    }

Результаты теста

Суммировать

  1. Реализация статического прокси относительно проста. До тех пор, пока прокси-объект обертывает целевой объект, расширенная функция может быть реализована, но статический прокси может обслуживать только один целевой объект. Если целевых объектов слишком много, будет много прокси-классов. быть сгенерирован.
  2. Динамический прокси-сервер JDK требует, чтобы целевой объект реализовывал бизнес-интерфейс, а прокси-классу нужно только реализоватьInvocationHandlerинтерфейс.
  3. Статические прокси генерируются во время компиляцииclassФайлы байт-кода можно использовать напрямую, и они очень эффективны.
  4. Динамические прокси должны реализоватьInvocationHandlerИнтерфейс через метод прокси-сервера отражения потребляет производительность системы, но может уменьшить количество классов прокси и использовать их более гибко.
  5. cglibПрокси не нужно реализовывать интерфейс, и прокси реализуется путем генерации байт-кода класса, что немного быстрее, чем отражение, и проблем с производительностью нет, ноcglibУнаследует целевой объект и должен переопределить метод, поэтому целевой объект не может бытьfinalсвоего рода