Динамический прокси-сервер Java имеет широкий спектр практических сценариев использования, таких как большинство сценариев Spring AOP, получение аннотаций Java, ведение журнала, аутентификация пользователя и т. д. В этой статье вы узнаете о режиме прокси, статическом прокси-сервере и собственном динамическом прокси-сервере на основе JDK.
прокси-режим
Независимо от того, изучаете ли вы статический прокси или динамический прокси, мы должны сначала понять режим прокси.
Сначала взгляните на определение энциклопедии Baidu:
Определение режима прокси: Предоставьте прокси для других объектов, чтобы контролировать доступ к этому объекту. В некоторых случаях объект не подходит или не может напрямую ссылаться на другой объект, а прокси-объект может действовать как посредник между клиентом и целевым объектом.
Это может быть трудно понять непосредственно, глядя на определение, поэтому давайте проиллюстрируем его конкретными примерами из жизни.
Мы все были в супермаркете, чтобы купить товары.Супермаркет покупает товары у производителя и продает их нам.Мы обычно не знаем, откуда берутся товары и сколько процессов проходит в супермаркете.
В этом процессе это равносильно тому, что производитель «поручает» супермаркету продавать товар, для нас производитель (реальный объект) невидим. А супермаркет (прокси-объект) взаимодействует с нами как «агент» производителя.
В то же время супермаркет может также выполнять скидки и другую обработку в соответствии с конкретной ситуацией продаж, чтобы расширить функции прокси-объекта.
В режиме прокси мы можем делать две вещи:
1. Скрыть конкретную реализацию класса делегата.
2. Реализовать развязку клиента и класса делегата, а также добавить некоторые дополнительные функции (логи, разрешения) и т.п. без изменения кода класса делегата.
Определение роли режима агента
В приведенном выше процессе мы можем определить три типа объектов в процессе программирования:
- Субъект (роль абстрактного субъекта): определяет общедоступный внешний метод прокси-класса и реального субъекта, а также является методом прокси-класса для передачи реального субъекта. Например: реклама, продажа и т.д.
- RealSubject: класс, который фактически реализует бизнес-логику. Например, поставщик (Vendor), реализующий такие методы, как реклама и продажа.
- Прокси (роль прокси-темы): используется для проксирования и инкапсуляции реальной темы. Например, также реализуется тайм-аут (магазин) рекламы, продажи и другие методы.
Диаграммы классов, соответствующие трем вышеперечисленным ролям, следующие:
Статический прокси-сервер
Статический прокси означает, что прокси-класс уже существует до запуска программы.В этом случае прокси-класс обычно определяется в коде Java.
Давайте возьмем конкретный пример, чтобы продемонстрировать статический прокси.
Сначала определите набор интерфейсов Sell, используемых для предоставления таких функций, как реклама и продажи. Затем предоставьте класс Vendor (производитель, прокси-объект) и Shop (супермаркет, прокси-класс), которые соответственно реализуют интерфейс Sell.
Интерфейс Sell определяется следующим образом:
/**
* 委托类和代理类都实现了Sell接口
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public interface Sell {
/**
* 出售
*/
void sell();
/**
* 广告
*/
void ad();
}
Класс Vendor определяется следующим образом:
/**
* 供应商
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public class Vendor implements Sell{
@Override
public void sell() {
System.out.println("Shop sell goods");
}
@Override
public void ad() {
System.out.println("Shop advert goods");
}
}
Класс Shop определяется следующим образом:
/**
* 超市,代理类
* @author sec
* @version 1.0
* @date 2020/3/21 9:30 AM
**/
public class Shop implements Sell{
private Sell sell;
public Shop(Sell sell){
this.sell = sell;
}
@Override
public void sell() {
System.out.println("代理类Shop,处理sell");
sell.sell();
}
@Override
public void ad() {
System.out.println("代理类Shop,处理ad");
sell.ad();
}
}
Прокси-класс Shop содержит ссылку на прокси-класс Vendor посредством агрегации и вызывает соответствующий метод Vendor в соответствующем методе. В классе Shop мы можем добавить некоторые дополнительные обработки, такие как фильтрация пользователей покупки, запись журналов и другие операции.
Давайте посмотрим, как прокси-класс используется в клиенте.
/**
* 静态代理类测试方法
* @author sec
* @version 1.0
* @date 2020/3/21 9:33 AM
**/
public class StaticProxy {
public static void main(String[] args) {
// 供应商---被代理类
Vendor vendor = new Vendor();
// 创建供应商的代理类Shop
Sell sell = new Shop(vendor);
// 客户端使用时面向的是代理类Shop。
sell.ad();
sell.sell();
}
}
В приведенном выше коде клиент видит, что интерфейс Sell предоставляет функции, а функции предоставляются Shop. Мы можем изменить или добавить некоторый контент в магазине, не затрагивая прокси-класс Vendor.
Недостатки статических прокси
Реализация статического прокси проста и не затрагивает исходный код, но когда сцена сложная, статический прокси будет иметь следующие недостатки:
1. Когда необходимо представить несколько классов, прокси-объект должен реализовывать тот же интерфейс, что и целевой объект. В качестве альтернативы можно поддерживать только один прокси-класс для реализации нескольких интерфейсов, но это сделает прокси-класс слишком большим. Или создайте несколько прокси-классов, но это создаст слишком много прокси-классов.
2. Когда в интерфейсе необходимо добавлять, удалять и изменять методы, целевой объект и прокси-класс необходимо изменять одновременно, что непросто поддерживать.
Итак, динамические прокси пригодятся.
Динамический прокси
Динамический прокси относится к методу прокси, в котором класс прокси создается во время работы программы. В этом случае прокси-класс не определен в коде Java, а динамически генерируется во время выполнения на основе «инструкций» в коде Java.
Преимущество динамических прокси по сравнению со статическими прокси состоит в том, что функции классов прокси можно легко обрабатывать единообразно, не изменяя функции каждого класса прокси.
На основе встроенной реализации динамического прокси JDK.
Обычно существует два способа реализации динамических прокси: собственные динамические прокси JDK и динамические прокси CGLIB. Здесь мы берем собственный динамический прокси-сервер JDK в качестве примера для объяснения.
Динамический прокси-сервер JDK в основном включает два класса: java.lang.reflect.Proxy и java.lang.reflect.InvocationHandler.
Интерфейс InvocationHandler определяет следующие методы:
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
Как следует из названия, промежуточный класс, реализующий этот интерфейс, действует как «обработчик вызовов». При вызове метода объекта прокси-класса «вызов» будет переадресован методу вызова. Объект прокси-класса передается в качестве параметра прокси. Метод параметра определяет, какой именно метод прокси-класса вызывается, а аргументы является параметром метода. Таким образом, вызовы всех методов в прокси-классе станут вызовами для вызова, и к методу вызова можно добавить унифицированную логику обработки (разные методы прокси-классов также могут обрабатываться по-разному в соответствии с параметром метода).
Класс Proxy используется для получения обработчика вызова, связанного с указанным прокси-объектом.
Ниже приведен пример добавления журнала для демонстрации динамического прокси.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
Модуль записи на стороне клиента использует динамический прокси-код следующим образом:
import java.lang.reflect.Proxy;
/**
* 动态代理测试
*
* @author sec
* @version 1.0
* @date 2020/3/21 10:40 AM
**/
public class DynamicProxyMain {
public static void main(String[] args) {
// 创建中介类实例
LogHandler logHandler = new LogHandler(new Vendor());
// 设置该变量可以保存动态代理类,默认名称$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 获取代理类实例Sell
Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));
// 通过代理类对象调用代理类方法,实际上会转到invoke方法调用
sell.sell();
sell.ad();
}
}
После выполнения журнал печати выглядит следующим образом:
调用方法sell之【前】的日志处理
Shop sell goods
调用方法sell之【后】的日志处理
调用方法ad之【前】的日志处理
Shop advert goods
调用方法ad之【后】的日志处理
После вышеуказанной проверки мы обнаружили, что логи до и после выполнения метода были успешно добавлены в наш прокси-класс.
В приведенном выше примере, чтобы взглянуть на код сгенерированного динамического прокси-класса, мы добавили следующие настройки свойства (это свойство необходимо удалить в рабочей среде).
// 设置该变量可以保存动态代理类,默认名称$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Затем, после того как мы можем выполнить основной метод, также создается файл класса с именем $Proxy0.class. После декомпиляции можно увидеть следующий код:
package com.sun.proxy;
import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Sell {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy0(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);
}
}
public final void ad() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sell() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
Вы можете видеть, что $Proxy0 (класс прокси) наследует класс Proxy и реализует все проксируемые интерфейсы, а также такие методы, как equals, hashCode и toString.
Поскольку класс динамического прокси наследует класс Proxy, каждый класс прокси связан с обработчиком вызова метода InvocationHandler.
Класс и все методы изменяются с помощью public final, поэтому можно использовать только прокси-класс, который нельзя наследовать.
Каждый метод описывается объектом Method, который создается в статическом блоке статического кода и называется в формате «m+число».
При вызове метода он вызывается через super.h.invoke(this,m1,(Object[])null);. Super.h.invoke на самом деле является объектом LogHandler, который передается в Proxy.newProxyInstance при создании прокси, наследует класс InvocationHandler и отвечает за реальную логику обработки вызовов.
резюме
Это все, что мы можем сказать о прокси и динамических прокси. Знание шаблона прокси может сделать нашу систему более масштабируемой. Применение динамических агентов шире, используются различные фреймворки и бизнес-сценарии. С двумя основами можно лучше изучить другие фреймворки.
Что касается содержания динамических прокси CGLIB, то мы поговорим об этом в следующей статье.
--- Программа Новые Горизонты: Захватывающие и растущие нельзя пропустить