1. Обзор
Когда мы используем динамический прокси , JDK динамически генерирует класс $Proxy .
Обычно полное имя этого класса $Proxy
чем-то похоже на com.sun.proxy.$Proxy0
. Как сказано в документации по Java , «$Proxy» — это зарезервированный префикс имени для прокси-классов.
В этом уроке мы собираемся изучить этот класс $Proxy .
2. Класс $ Proxy
Прежде чем начать, давайте проведем различие между классом java.lang.reflect.Proxy
и классом $Proxy
. java.lang.reflect.Proxy
— это встроенный класс JDK . И, напротив, класс $Proxy
динамически генерируется во время выполнения . С точки зрения иерархии классов класс $Proxy
наследует класс java.lang.reflect.Proxy
.
2.1. Пример динамического прокси
Чтобы иметь основу для обсуждения, давайте определим два интерфейса: BasicOperation
и AdvancedOperation
. Интерфейс BasicOperation
содержит методы сложения
и вычитания
:
public interface BasicOperation {
int add(int a, int b);
int subtract(int a, int b);
}
И интерфейс AdvancedOperation
имеет методы умножения
и деления :
public interface AdvancedOperation {
int multiply(int a, int b);
int divide(int a, int b);
}
Чтобы получить только что сгенерированный прокси-класс — класс $Proxy
— мы можем вызвать метод Proxy::getProxyClass
:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?>[] interfaces = {BasicOperation.class, AdvancedOperation.class};
Class<?> proxyClass = Proxy.getProxyClass(classLoader, interfaces);
Однако указанный выше proxyClass
существует только в работающей JVM, и мы не можем напрямую просматривать члены его класса .
2.2. Дамп класса $Proxy
Для тщательного изучения этого класса $Proxy
нам лучше сбросить его на диск. При использовании Java 8 мы можем указать параметр « sun.misc.ProxyGenerator.saveGeneratedFiles
» в командной строке:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
Или мы можем установить эту опцию, вызвав метод System::setProperty
:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
В Java 9 и более поздних версиях вместо этого следует использовать параметр « jdk.proxy.ProxyGenerator.saveGeneratedFiles
». Почему такая разница? Из-за системы модулей Java пакет класса ProxyGenerator
изменился. В Java 8 ProxyGenerator
находится в пакете « sun.misc
»; однако, начиная с Java 9, ProxyGenerator
переместился в пакет « java.lang.reflect
» .
Если мы все еще не знаем, какой вариант подходит, мы можем найти поле saveGeneratedFiles класса
ProxyGenerator
, чтобы определить правильный вариант.
Будьте осторожны: класс ProxyGenerator
считывает это свойство только один раз `` . И это означает, что метод System::setProperty
не будет иметь никакого эффекта после того, как JVM явно или неявно создала какие-либо классы $Proxy .
Чтобы быть точным, вызов метода Proxy::getProxyClass
или Proxy::newProxyInstance
будет явно генерировать класс $Proxy .
С другой стороны, когда мы читаем аннотации, особенно в среде модульного тестирования, JVM будет неявно или автоматически генерировать класс $Proxy
для представления экземпляров аннотаций.
Точное расположение выгруженного файла класса напрямую связано с его полным именем класса . Например, если имя вновь сгенерированного класса — « com.sun.proxy.$Proxy0
», то файл дампа класса будет « com/sun/proxy/$Proxy0.class
» в текущем каталоге:
2.3. Члены класса $ Proxy
Теперь пришло время проверить члены класса этого сгенерированного класса $Proxy
.
Давайте сначала проверим иерархию классов. Класс $Proxy0
имеет java.lang.reflect.Proxy
в качестве суперкласса, что неявно объясняет, почему динамический прокси поддерживает только интерфейсы . Кроме того, класс $Proxy0
реализует наши ранее определенные интерфейсы BasicOperation
и AdvancedOperation
:
public final class $Proxy0 extends Proxy implements BasicOperation, AdvancedOperation
Для удобства чтения мы изменили имена полей класса $Proxy0
на более осмысленные. Поля hashCodeMethod
, equalsMethod
и toStringMethod
восходят к классу Object ;
поля addMethod
и subtractMethod
связаны с интерфейсом BasicOperation
; поляmultiMethodиdivideMethodсопоставляются
с интерфейсом
AdvanceOperation
: _ _ ``
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
private static Method addMethod;
private static Method subtractMethod;
private static Method multiplyMethod;
private static Method divideMethod;
Наконец, методы, определенные в классе $Proxy0
, следуют той же логике: все их реализации делегируют функции методу InvocationHandler::invoke
. И класс $Proxy0
получит экземпляр InvocationHandler
из своего конструктора:
public $Proxy0(InvocationHandler handler) {
super(handler);
}
public final int hashCode() {
try {
return (Integer) super.h.invoke(this, hashCodeMethod, (Object[]) null);
}
catch (RuntimeException | Error ex1) {
throw ex1;
}
catch (Throwable ex2) {
throw new UndeclaredThrowableException(ex2);
}
}
3. Как работает прокси
После того, как мы изучили сам класс $Proxy
, пришло время сделать еще один шаг: как сгенерировать класс $Proxy
и как загрузить класс $Proxy
? Ключевая логика заключается в классах java.lang.reflect.Proxy
и ProxyGenerator
.
По мере выпуска новых версий Java детали реализации классов Proxy
и ProxyGenerator
продолжают развиваться. Грубо говоря, ProxyGenerator отвечает
за генерацию массива байтов класса $Proxy , а класс
Proxy
отвечает за загрузку этого массива байтов в JVM .
Теперь давайте воспользуемся Java 8, Java 11 и Java 17 для нашего обсуждения, потому что они являются выпусками LTS (долгосрочная поддержка).
3.1. Ява 8
В Java 8 мы можем описать процесс генерации класса $Proxy в пять шагов:
Метод Proxy::getProxyClass
или Proxy::newProxyInstance
является нашей отправной точкой — любой из них вызовет метод Proxy::getProxyClass0
. Кроме того, метод Proxy::getProxyClass0
является закрытым
методом и в дальнейшем будет вызывать метод ProxyClassFactory::apply
.
ProxyClassFactory — это
статический вложенный класс, определенный в классе Proxy
. И его метод apply
определяет имя пакета, имя класса и флаги доступа будущего класса. Затем метод применения
вызовет метод ProxyGenerator::generateProxyClass
.
В Java 8 класс ProxyGenerator
является общедоступным
классом, определенным в пакете « sun.misc
». Начиная с Java 9, он перешел на пакет « java.lang.reflect ». И метод
generateProxyClass
создаст экземпляр ProxyGenerator
, вызовет его метод generateClassFile
, отвечающий за генерацию байт-кода, при необходимости выгрузит файл класса и вернет результирующий массив байтов. .
После успешного создания байт-кода метод Proxy::defineClass0
отвечает за загрузку этого массива байтов в работающую JVM. Наконец, мы получаем динамически сгенерированный класс $Proxy .
3.2. Ява 11
По сравнению с выпуском Java 8 в Java 11 внесены три основных изменения :
- Класс
Proxy
добавляет новый методgetProxyConstructor
и статический вложенный классProxyBuilder.
- Для модульной системы Java
ProxyGenerator
перенесен в пакет «java.lang.reflect
» и стал частным классом пакета. - Чтобы загрузить сгенерированный массив байтов в JVM, в игру вступает
Unsafe::defineClass.
3.3. Ява 17
По сравнению с выпуском Java 11 в Java 17 есть два основных изменения :
- С точки зрения реализации класс
ProxyGenerator
использует встроенный ASM JDK для генерации байт-кода. - Метод
JavaLangAccess::defineClass
отвечает за загрузку сгенерированного байт-кода в JVM.
4. Аннотации с использованием прокси
В Java тип аннотации — это особый тип интерфейса. Но нам может быть интересно, как создать экземпляр аннотации. На самом деле нам это не нужно. Когда мы используем Java Reflection API для чтения аннотации, JVM динамически генерирует класс $Proxy
в качестве реализации типа аннотации :
FunctionalInterface instance = Consumer.class.getDeclaredAnnotation(FunctionalInterface.class);
Class<?> clazz = instance.getClass();
boolean isProxyClass = Proxy.isProxyClass(clazz);
assertTrue(isProxyClass);
В приведенном выше фрагменте кода мы используем класс Consumer для получения его экземпляра
FunctionalInterface
, затем получаем класс экземпляра и, наконец, используем метод Proxy::isProxyClass
, чтобы проверить, является ли класс классом $Proxy
.
5. Вывод
В этом руководстве мы сначала представили пример динамического прокси, затем создали дамп сгенерированного класса $Proxy и проверили его члены.
Чтобы сделать еще один шаг, мы объяснили, как классы Proxy
и ProxyGenerator
работают вместе для создания и загрузки класса $Proxy
в разных версиях Java. Наконец, мы упомянули, что тип аннотации также реализуется с помощью класса $Proxy .
Как обычно, исходный код этого руководства можно найти на GitHub .