Поиск в WeChat: Code Farmer StayUp
Адрес домашней страницы:gozhuyinglong.github.io
Совместное использование исходного кода:GitHub.com/go Основной бизнес…
Динамический прокси JDK означает, что экземпляр класса прокси динамически генерируется JVM в соответствии с механизмом отражения во время работы программы. То есть прокси-класс не определяется пользователем, а генерируется JVM.
Поскольку его принцип реализуется через механизм отражения Java, перед обучением необходимо иметь определенное представление о механизме отражения. Портал:Механизм отражения Java: следуйте коду, чтобы изучить отражение
Ниже приводится содержание этой статьи:
1. Основной класс динамического прокси JDK
Динамический прокси JDK имеет два основных класса, которые находятся в пакете отражения Java (java.lang.reflect
), соответственноInvocationHandler
интерфейс иProxy
Добрый.
1.1 Интерфейс InvocationHandler
Необходимо реализовать обработчик вызова экземпляра прокси.
InvocationHandler
Интерфейс, и у каждого экземпляра агента есть связанный с ним вызывающий процессор. Этот вызов метода будет закодирован и назначен вызывающему процессору, когда метод вызывается на экземпляре прокси.invoke
метод.
То есть каждый экземпляр прокси, который мы создаем, должен иметь связанныйInvocationHandler
, а при вызове метода экземпляра прокси он будет переадресован наInvocationHandler
изinvoke
метод.
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Долженinvoke
Цель метода — обработать вызов метода на экземпляре прокси и вернуть результат.
Он имеет три параметра, а именно:
- proxy: это экземпляр прокси, который вызывает метод.
-
method: соответствует методу интерфейса, вызываемому в экземпляре прокси.
Method
пример. -
args:Один
Object
Массив значений параметров, передаваемых при вызове метода экземпляра прокси. Если метод интерфейса не имеет параметров, значение равно null.
Его возвращаемое значение: возвращаемое значение вызова метода на экземпляре прокси.
1.2 Класс прокси
Proxy
Класс предоставляет статические методы для создания динамических прокси-классов и их экземпляров, которые также являются надклассом динамических прокси-классов.
Прокси-класс имеет следующие свойства:
- Имя прокси-класса начинается с «$Proxy», за которым следует числовой порядковый номер.
- Прокси-класс наследует
Proxy
Добрый. - Класс прокси реализует интерфейс, указанный во время создания (динамические прокси JDK ориентированы на интерфейс).
- Каждый прокси-класс имеет общедоступный конструктор, который принимает один параметр — интерфейс.
InvocationHandler
Реализация обработчика вызова, используемого для установки экземпляра прокси.
Proxy
Для получения прокси-объекта предусмотрено два статических метода.
1.2.1 getProxyClass
используется для получения прокси-классаClass
объект, а затем создайте экземпляр прокси, вызвав конструктор.
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
Метод имеет два параметра:
- loader: для загрузчика классов.
-
intefaces: для интерфейса
Class
массив объектов.
Возвращаемое значение — динамический прокси-класс.Class
объект.
1.2.2 newProxyInstance
Используется для создания экземпляра прокси.
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
Метод имеет три параметра:
- loader: для загрузчика классов.
-
interfaces: для интерфейса
Class
массив объектов. - h: указанный обработчик вызова.
Возвращаемое значение является экземпляром прокси-класса для указанного интерфейса.
1.3 Резюме
Proxy
Класс в основном используется для получения динамических прокси-объектов,InvocationHandler
Интерфейсы в основном используются для ограничения и улучшения вызовов методов.
2. Пример кода для получения экземпляра прокси
Два статических метода для получения экземпляра прокси были представлены в предыдущей главе, а теперь их реализация демонстрируется на примере кода.
2.1 Создайте целевой интерфейс и класс его реализации
Динамический прокси JDK основан на интерфейсе, мы создаем интерфейс и класс его реализации.
Фу интерфейс:
public interface Foo {
String ping(String name);
}
Класс реализации RealFoo интерфейса Foo:
public class RealFoo implements Foo {
@Override
public String ping(String name) {
System.out.println("ping");
return "pong";
}
}
2.2 Создание обработчика вызовов
СоздаватьInvocationHandler
Класс реализации интерфейса MyInvocationHandler. Параметр конструктора этого класса является целевым объектом для проксирования.
invoke
Три параметра метода были введены выше путем вызоваmethod
изinvoke
метод для завершения вызова метода.
Неважно, если вы какое-то время этого не понимаете, глава анализа исходного кода проанализирует это позже.
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy - " + proxy.getClass());
System.out.println("method - " + method);
System.out.println("args - " + Arrays.toString(args));
return method.invoke(target, args);
}
}
2.3 Метод 1: Получите экземпляр прокси с помощью метода getProxyClass
Конкретные этапы реализации следующие:
- Получить объект класса прокси-класса на основе загрузчика классов и массива интерфейса
- Создать экземпляр через конструктор объекта Class (экземпляр прокси-класса)
- Принуждает экземпляр прокси к целевому интерфейсу Foo (поскольку класс прокси реализует целевой интерфейс, его можно принудить).
- Наконец, используйте прокси для вызова метода.
@Test
public void test1() throws Exception {
Foo foo = new RealFoo();
// 根据类加载器和接口数组获取代理类的Class对象
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
// 通过Class对象的构造器创建一个实例(代理类的实例)
Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler(foo));
// 调用 ping 方法,并输出返回值
String value = fooProxy.ping("杨过");
System.out.println(value);
}
Выходной результат:
proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [杨过]
ping
pong
Из вывода видно, что:
- Имя прокси-класса
$Proxy
начало. - Экземпляр метода — это метод, вызываемый прокси-классом.
- Параметр — это параметр, который передается, когда прокси-класс вызывает метод.
2.4 Метод 2: Получите экземпляр прокси с помощью метода newProxyInstance
Этот метод является самым простым и рекомендуемым, и прокси-объект можно получить напрямую с помощью этого метода.
Примечание. На самом деле фоновая реализация этого метода аналогична описанному выше процессу использования метода getProxyClass.
@Test
public void test2() {
Foo foo = new RealFoo();
// 通过类加载器、接口数组和调用处理器,创建代理类的实例
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
new MyInvocationHandler(foo));
String value = fooProxy.ping("小龙女");
System.out.println(value);
}
2.5 Упрощенная реализация с помощью лямбда-выражений
фактическиInvocationHander
Интерфейсу не нужно создавать класс реализации, и он может использовать выражения Lambad для упрощенной реализации следующим образом:
@Test
public void test3() {
Foo foo = new RealFoo();
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
(proxy, method, args) -> method.invoke(foo, args));
String value = fooProxy.ping("雕兄");
System.out.println(value);
}
3. Анализ исходного кода
3.1 Как выглядит прокси-класс $Proxy
Как именно выглядит прокси-класс, который JVM автоматически генерирует для нас? Давайте сначала сгенерируем его, а потом посмотрим на структуру внутри.
3.1.1 Создайте файл .class для $Proxy
JVM не создает файл .class по умолчанию, вам нужно добавить параметр запуска:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
Нажмите [Edit Configurations...] в IDEA, чтобы открыть окно конфигурации Run/Debug Configurations.
Добавьте вышеуказанные параметры запуска в [Параметры VM] и нажмите [OK].
Запустите код еще раз, вы найдете файл .class в каталоге [com.sun.proxy] в проекте, вот "$Proxy4.class"
3.1.2 Почему файл байт-кода $Proxy может быть создан путем добавления этого параметра запуска
существуетProxy
класс имеетProxyClassFactory
Статический внутренний класс, основная функция этого класса — создание статического прокси.
Там есть кусок кодаProxyGenerator.generateProxyClass
Файл .class, используемый для создания прокси-классов.
где переменнаяsaveGeneratedFiles
На значение этого параметра запуска ссылаются. Настройте этот параметр запуска какtrue
Будет создан файл .class.
3.1.3 Как выглядит этот прокси-класс $Proxy?
Таинственная завеса вот-вот откроется, и здесь можно найти ответы на многие неразгаданные тайны!
открыть это$Proxy
файл, который я сгенерировал здесь$Proxy4
, следующее содержание:
// 该类为final类,其继承了Proxy类,并实现了被代理接口Foo
public final class $Proxy4 extends Proxy implements Foo {
// 这4个Method实例,代表了本类实现的4个方法
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 静态代码块根据反射获取这4个方法的Method实例
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("io.github.gozhuyinglong.proxy.Foo").getMethod("ping");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
// 一个公开的构造函数,参数为指定的 InvocationHandler
public $Proxy4(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// Foo接口的实现方法,最终调用了 InvocationHandler 中的 invoke 方法
public final String ping(String var1) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
Из этого документа видно, что:
- Прокси-класс наследует
Proxy
класс, основная цель которого - пройтиInvocationHandler
. - Прокси-класс реализует прокси-интерфейс Foo, поэтому прокси-класс можно напрямую привести к интерфейсу.
- имеет публичный конструктор с указанными параметрами
InvocationHandler
и передать параметр родительскому классуProxy
середина. - Каждый реализованный метод будет вызываться
InvocationHandler
серединаinvoke
метод и передать три параметра самого прокси-класса, экземпляр метода и входные параметры. Вот почему, когда вызывается метод в прокси-классе, он всегда отправляется вInvocationHandler
серединаinvoke
причина метода.
3.2 Как создается прокси-класс
мы начинаем сProxy
Класс предоставляет нам два статических метода для запускаgetProxyClass
а такжеnewProxyInstance
. Как описано выше, эти два метода используются для создания прокси-классов и их экземпляров.Давайте посмотрим на исходный код.
3.2.1 Методы getProxyClass и newProxyInstance
Как видно из приведенного выше исходного кода, эти два метода в итоге будут вызыватьсяgetProxyClass0
метод создания прокси-классаClass
объект. ТолькоnewProxyInstance
метод создает экземпляр прокси для нас, иgetProxyClass
Метод требует, чтобы мы сами создали экземпляр прокси.
3.2.2 Метод getProxyClass0
Давайте посмотрим на эту объединенную запись:getProxyClass0
Как видно из исходного кода и комментариев:
- Максимальное количество прокси-интерфейсов не может превышать 65535.
- Класс прокси сначала будет получен из кеша, потом не пройдет
ProxyClassFactory
Создайте прокси-класс. (Прокси-классы кэшируются на определенный период времени.)
3.2.3 Класс WeakCache
Вот краткое введениеWeakCache<K, P, V>
class, который в основном кэшируется для прокси-классов. При получении прокси-класса он сначала будет получен из кеша, если нет, то будет вызванProxyClassFactory
Класс создается и кэшируется после его создания.
3.2.4 Класс ProxyClassFactory
ProxyClassFactory
даProxy
Статический внутренний класс класса, который используется для создания прокси-классов. Следующий рисунок является частью исходного кода:
- Здесь определяется имя прокси-класса и его префикс
$Proxy
, с добавлением числа. - перечислить
ProxyGenerator.generateProxyClass
для создания указанного прокси-класса. -
defineClass0
метод представляет собойnative
метод, отвечающий за реализацию загрузки байт-кода, и возвращает соответствующийClass
объект.
3.3 Схема
Для удобства записи процесс генерации прокси-класса организован в виде диаграммы.
Совместное использование исходного кода
Пожалуйста, посетите мой Github для получения полного кода.Если он вам полезен, поставьте ⭐, спасибо~~🌹🌹🌹
GitHub.com/go Основной бизнес…
Рекомендуемое чтение
Об авторе
проект | содержание |
---|---|
Нет публики | Код Farmer StayUp (ID: AcmenStayUp) |
Домой | gozhuyinglong.github.io |
CSDN | blog.CSDN.net/go Основной бизнес... |
раскопки | Талант /user/123990… |
Github | GitHub.com/go Основной бизнес… |
Gitee | git ee.com/go - основной бизнес... |