Статьи

Сила прокси в Java

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

Они повсюду, но о них знает лишь горстка людей. Hibernate для объектов с отложенной загрузкой, Spring для AOP , LambdaJ для DSL , только некоторые из них: все они используют свою скрытую магию. Кто они такие? Они … динамические прокси Java.

Все знают о шаблоне проектирования GOF Proxy :

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

Аналогично, в Java динамический прокси — это экземпляр, который действует как проход к реальному объекту. Этот мощный шаблон позволяет вам изменить реальное поведение с точки зрения вызывающей стороны, поскольку прокси может перехватывать вызовы методов.

Чистые Java прокси

Чистые Java прокси имеют некоторые интересные свойства:

  • Они основаны на реализациях интерфейсов во время выполнения.
  • Они публичные, окончательные и не абстрактные
  • Они расширяют java.lang.reflect.Proxy

В Java сам прокси не так важен, как его поведение. Последнее сделано в реализации  java.lang.reflect.InvocationHandler . У него есть только один метод для реализации:

public Object invoke(Object proxy, Method method, Object[] args)
  • proxy: экземпляр прокси, на котором был вызван метод
  • method: экземпляр метода, соответствующий методу интерфейса, вызванному на экземпляре прокси. Класс объявления объекта Method будет интерфейсом, в котором был объявлен метод, который может быть суперинтерфейсом интерфейса прокси, через который прокси-класс наследует метод.
  • args: массив объектов, содержащий значения аргументов, переданных в вызове метода для экземпляра прокси, или ноль, если метод интерфейса не принимает аргументов. Аргументы примитивных типов переносятся в экземпляры соответствующего примитивного класса-оболочки, такого как java.lang.Integer или java.lang.Boolean.

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

public class NoOpAddInvocationHandler implements InvocationHandler {

  private final List proxied;

  public NoOpAddInvocationHandler(List proxied) {

    this.proxied = proxied;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    if (method.getName().startsWith("add")) {

      return false;
    }

    return method.invoke(proxied, args);
  }
}

Метод invoke будет перехватывать вызовы метода и ничего не делать, если метод начинается с «add». В противном случае он передаст вызов реальному прокси-объекту. Это очень грубый пример, но этого достаточно, чтобы мы поняли магию, стоящую за этим.

Обратите внимание, что если вы хотите, чтобы ваш вызов метода проходил, вам нужно вызвать метод для реального объекта. Для этого вам понадобится ссылка на последнее, чего метод invoke не предоставляет. Вот почему в большинстве случаев полезно передать его конструктору и сохранить как атрибут.

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

Чтобы создать сам прокси:

 List proxy = (List) Proxy.newProxyInstance(
  NoOpAddInvocationHandlerTest.class.getClassLoader(),
  new Class[] { List.class },
  new NoOpAddInvocationHandler(list));

Метод newProxyInstance принимает 3 аргумента:

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

Теперь, если вы попытаетесь добавить элементы в прокси, вызвав любой метод add, это не будет иметь никакого эффекта.

CGLib прокси

Java прокси — это реализации интерфейсов во время выполнения. Объекты не обязательно реализуют интерфейсы, а коллекции объектов не обязательно используют одни и те же интерфейсы. Столкнувшись с такими потребностями, прокси-серверы Java не могут предоставить ответ.

Здесь начинается царство CGLib . CGlib — это сторонняя платформа, основанная на манипулировании байт-кодом, предоставляемым ASM, которая может помочь с предыдущими ограничениями. Прежде всего, совет: документация CGLib не соответствует ее возможностям: здесь нет ни учебника, ни документации. Горстка JavaDocs — это все, на что вы можете рассчитывать. При этом CGLib отказывается от многих ограничений, накладываемых чистыми Java-прокси:

  • вы не обязаны реализовывать интерфейсы
  • Вы можете продлить класс

Например, поскольку объекты Hibernate являются POJO, прокси Java не могут использоваться при отложенной загрузке; Может CGLib прокси.

Между чистыми Java-прокси и прокси-серверами CGLib есть совпадения: где вы используете Proxy, вы используете класс net.sf.cglib.proxy.Enhancer, где вы используете InvocationHandler, вы используете net.sf.cglib.proxy.Callback. Два основных отличия в том, что Enhancer имеет открытый конструктор, и Callback нельзя использовать как таковой, но только через один из его подинтерфейсов:

  • Диспетчер: диспетчеризация обратного вызова Enhancer
  • FixedValue: обратный вызов Enhancer, который просто возвращает значение, возвращаемое из прокси-метода
  • LazyLoader: Lazy-loading Enhancer callback
  • MethodInterceptor: универсальный обратный вызов Enhancer, который обеспечивает «советы по всему»
  • NoOp: методы, использующие этот обратный вызов Enhancer, будут делегированы непосредственно реализации по умолчанию (супер) в базовом классе.

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

<public class HashCodeAlwaysZeroMethodInterceptor implements MethodInterceptor {

  public Object intercept(Object object, Method method, Object[] args,
    MethodProxy methodProxy) throws Throwable {

    if ("hashCode".equals(method.getName())) {

      return 0;
    }

    return methodProxy.invokeSuper(object, args);
  }
}

Выглядит очень похоже на обработчик вызовов Java, не так ли? Теперь, чтобы создать сам прокси:

Object proxy = Enhancer.create(
  Object.class,
  new HashCodeAlwaysZeroMethodInterceptor());

Кроме того, создание прокси не удивительно. Настоящие различия:

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

Вывод

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

Вы можете найти источники для этой статьи в формате Eclipse / Maven здесь .

 

От http://blog.frankel.ch/the-power-of-proxies-in-java