Перейти к основному содержимому

Что такое класс JDK com.sun.proxy.$Proxy?

· 6 мин. чтения

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 » в текущем каталоге:

./32193923ab878e92b482026f47460289.png

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 в пять шагов:

./0977c5ef1cdd5463cc06f2187f27cbd0.png

Метод 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.

./61caf892b266994f8c1cf6e29ecede60.png

3.3. Ява 17

По сравнению с выпуском Java 11 в Java 17 есть два основных изменения :

  • С точки зрения реализации класс ProxyGenerator использует встроенный ASM JDK для генерации байт-кода.
  • Метод JavaLangAccess::defineClass отвечает за загрузку сгенерированного байт-кода в JVM.

./c7aa5335800c8ffa3721d0bd62c93f13.png

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 .