Статьи

Создание прокси-объекта с использованием djcproxy

В последние недели я показал, как создать прокси-объект, используя API отражения Java и cglib. В этой статье я покажу вам, как это можно сделать с помощью djcproxy.

О, не снова, другая реализация прокси!

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

Как это использовать

Вы можете получить исходный код из github или просто добавить зависимость в свой проект maven pom.

1
2
3
4
5
<dependency>
    <groupId>com.javax0</groupId>
    <artifactId>djcproxy</artifactId>
    <version>2.0.3</version>
</dependency>

После этого вы можете использовать следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
class A {
  public int method() {
  return 1;
  }
}
class Interceptor implements MethodInterceptor {
 
  @Override
  public Object intercept(Object obj, Method method, Object[] args,
    MethodProxy mproxy) throws Exception {
      if (method.getName().equals("toString")) {
        return "interceptedToString";
      }
      return 0;
  }
}
 
 ...
 
    A a = new A();
    ProxyFactory<A> factory = new ProxyFactory<>();
    A s = factory.create(a, new Interceptor());

Этот код можно найти в тестах проекта в GitHub. Это отредактированная сокращенная версия, подверженная ошибкам редактирования.

Класс «A» является исходным классом, и когда мы хотим создать новый прокси-объект, мы создаем прокси для уже существующего объекта. Это отличается от рефлексии или cgilib. В случае cgilib вы создаете прокси-объект, и он «содержит» исходный объект. На самом деле это не ограничение в терминах ОО, поскольку прокси-класс расширяет исходный класс. Однако из-за этого расширение прокси-объекта также является экземпляром исходного класса. Cgilib на самом деле не волнует, какой экземпляр класса (объект) вы хотите перехватить. При желании вы можете вставить ссылку на любой экземпляр объекта вашему перехватчику. Djcproxy использует другой подход и делает это для вас и вашего перехватчика, вы получите этот объект в качестве аргумента. Вот почему вы должны создать экземпляр объекта в строке 20.

Interceptor реализует интерфейс MethodInterceptor также предоставленный в библиотеке. У него есть только один метод: intercept , который вызывается при вызове метода прокси-объекта. Аргументы

  • obj — исходный объект
  • method — метод, который был вызван в объекте прокси
  • args — аргументы, которые были переданы вызову метода для объекта прокси. Обратите внимание, что примитивные аргументы будут упакованы.
  • mproxy — прокси метода, который можно использовать для вызова метода исходного объекта или просто любого другого объекта того же типа

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

В то время как cglib предоставляет вам статический метод фабрики для создания новых объектов, djcproxy требует, чтобы вы создали фабрику прокси. Он находится в строке под номером 21. Если вы хотите использовать его так же, как вы использовали cglib, вы можете объявить статическое поле ProxyFactory в классе, из которого вы хотите использовать фабрику. С другой стороны, можно иметь разные фабрики в разных частях кода. Хотя его преимущество редко, но я считаю, что это более чистый подход, чем использование статического метода.

Как работает прокси?

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

1
2
String generatedSource = factory.getGeneratedSource();
    System.out.println(generatedSource);

распечатать сгенерированный прокси-класс, который после некоторого форматирования выглядит так:

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
47
48
package com.javax0.djcproxy;
 
class PROXY$CLASS$A extends com.javax0.djcproxy.ProxyFactoryTest.A implements com.javax0.djcproxy.ProxySetter {
    com.javax0.djcproxy.ProxyFactoryTest.A PROXY$OBJECT = null;
    com.javax0.djcproxy.MethodInterceptor PROXY$INTERCEPTOR = null;
 
    public void setPROXY$OBJECT(java.lang.Object PROXY$OBJECT) {
        this.PROXY$OBJECT = (com.javax0.djcproxy.ProxyFactoryTest.A) PROXY$OBJECT;
 
    }
 
    public void setPROXY$INTERCEPTOR(com.javax0.djcproxy.MethodInterceptor PROXY$INTERCEPTOR) {
        this.PROXY$INTERCEPTOR = PROXY$INTERCEPTOR;
 
    }
 
    PROXY$CLASS$A() {
        super();
 
    }
 
    private com.javax0.djcproxy.MethodProxy method_MethodProxyInstance = null;
 
    @Override
    public int method() {
 
        try {
            if (null == method_MethodProxyInstance) {
                method_MethodProxyInstance = new com.javax0.djcproxy.MethodProxy() {
                    public java.lang.Object invoke(java.lang.Object obj, java.lang.Object[] args) throws Throwable {
                        return ((com.javax0.djcproxy.ProxyFactoryTest.A) obj).method();
 
                    }
                };
            }
            return (int) PROXY$INTERCEPTOR.intercept(
                    PROXY$OBJECT, PROXY$OBJECT.getClass().getMethod("method", new Class[]{}),
                    new Object[]{}, method_MethodProxyInstance);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
 
    }
 
 
... other overridden methods deleted ...
 
}

Обратите внимание, что класс A является статическим вложенным классом ProxyFactoryTest для этого сгенерированного кода.

Интересный код — переопределение метода method() . (Извините за название. У меня нет фантазии, чтобы иметь лучшее имя для метода, который ничего не делает.) Давайте пропустим часть, где метод проверяет, существует ли уже экземпляр MethodProxy и если он отсутствует, он его создает. Метод method() фактически вызывает определенный нами объект-перехватчик, передавая прокси-объект, рефлексивный объект метода, аргументы, а также прокси метода.

Что такое метод прокси

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

Заметки

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