Статьи

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

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

Таким образом, прокси-классы могут реализовать многие вещи удобным способом:

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

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

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

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

Это сложная тема, потому что она требует использования класса отражения, манипулирования байтовым кодом или компиляции кода Java, сгенерированного динамически. Или все это. Чтобы новый класс не был доступен как байт-код еще во время выполнения, потребуется генерация байт-кода и загрузчик классов, который загружает байт-код. Для создания байтового кода вы можете использовать cglib или bytebuddy или встроенный компилятор Java.

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

Самый простой способ сделать это — использовать класс java.lang.reflect.Proxy , который является частью JDK. Этот класс может создать прокси-класс или непосредственно его экземпляр. Использовать встроенный прокси Java очень просто. Все, что вам нужно сделать, это реализовать java.lang.InvocationHandler чтобы прокси-объект мог вызывать это. Интерфейс InvocationHandler чрезвычайно прост. Он содержит только один метод: invoke() . Когда invoke() , аргументы содержат исходный объект, который проксируется, метод, который был вызван (как объект Method отражения), и массив объектов исходных аргументов. Пример кода демонстрирует использование:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package proxy;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class JdkProxyDemo {
 
    interface If {
        void originalMethod(String s);
    }
 
    static class Original implements If {
        public void originalMethod(String s) {
            System.out.println(s);
        }
    }
 
    static class Handler implements InvocationHandler {
        private final If original;
 
        public Handler(If original) {
            this.original = original;
        }
 
        public Object invoke(Object proxy, Method method, Object[] args)
                throws IllegalAccessException, IllegalArgumentException,
                InvocationTargetException {
            System.out.println("BEFORE");
            method.invoke(original, args);
            System.out.println("AFTER");
            return null;
        }
    }
 
    public static void main(String[] args){
        Original original = new Original();
        Handler handler = new Handler(original);
        If f = (If) Proxy.newProxyInstance(If.class.getClassLoader(),
                new Class[] { If.class },
                handler);
        f.originalMethod("Hallo");
    }
 
}

Если обработчик хочет вызвать оригинальный метод для исходного объекта, он должен иметь к нему доступ. Это не обеспечивается реализацией прокси Java. Вы должны передать этот аргумент экземпляру обработчика самостоятельно в своем коде. (Обратите внимание, что существует объект, обычно называемый proxy передается в качестве аргумента обработчику вызова. Это объект прокси, который динамически генерирует отражение Java, а не объект, который мы хотим прокси.) Таким образом, вы абсолютно свободны использовать отдельный обработчик объекта для каждого исходного класса или использование некоторого общего объекта, который каким-то образом знает, какой исходный объект вызывать, если есть какой-либо метод для вызова вообще.

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

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

Ссылка: Java Dynamic Proxy от нашего партнера JCG Питера Верхаса из блога Java Deep .