I. Обзор
1. Что такое прокси
Все мы знаем, что агент микробизнеса, короче говоря, должен продавать товары от имени производителя, а производитель «поручает» агенту продавать товары за него. Что касается агентов микробизнеса, то, во-первых, когда мы покупаем у них вещи, мы обычно не знаем, кто за ними стоит производитель, то есть «грузоотправитель» для нас невидим; во-вторых, агенты микробизнеса в основном основаны на круге друзей.Люди являются целевыми клиентами, что эквивалентно «фильтрации» групп клиентов для производителей. Далее мы абстрагируем агента микробизнеса и производителя.Первый может быть абстрагирован как класс агента, а второй может быть абстрагирован как класс делегата (прокси-класс). Использование агента обычно дает два преимущества, которые могут соответствовать двум упомянутым нами характеристикам агента микробизнеса:
Преимущество 1: реализация класса делегата может быть скрыта;
Преимущество 2: можно реализовать разделение между клиентом и классом делегата, а также некоторую дополнительную обработку без изменения кода класса делегата.
2. Статический прокси
Если прокси-класс уже существует до запуска программы, то этот прокси-метод называется статическим прокси, и прокси-класс в этом случае обычно определяется в Java-коде. Как правило, класс прокси и класс делегата в статическом прокси реализуют один и тот же интерфейс или являются производными от одного и того же родительского класса. Затем мы используем класс Vendor для представления производителя и класс BusinessAgent для представления агента микробизнеса, чтобы представить простую реализацию статического агента. И класс делегата, и класс агента реализуют интерфейс Sell. Интерфейс продажи выглядит следующим образом:
/**
* 委托类和代理类都实现了Sell接口
*/
public interface Sell {
void sell();
void ad();
}
Класс Vendor определяется следующим образом:
/**
* 生产厂家
*/
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System,out.println("ad method");
}
}
Определение класса агента BusinessAgent выглядит следующим образом:
/**
* 代理类
*/
public class BusinessAgent implements Sell {
private Sell vendor;
public BusinessAgent(Sell vendor){
this.vendor = vendor;
}
public void sell() {
vendor.sell();
}
public void ad() {
vendor.ad();
}
}
Из определения класса BusinessAgent мы можем узнать, что статические агенты могут быть реализованы посредством агрегации, а класс агента может содержать ссылку на класс делегата.
Рассмотрим это требование: добавьте функцию фильтрации в класс Vendor и продавайте товары только студентам колледжей. С помощью статического агента мы можем достичь этого без изменения кода класса Vendor, просто добавим суждение к методу продажи в классе BusinessAgent, как показано ниже:
/**
* 代理类
*/
public class BusinessAgent(){ implements Sell {
private Sell vendor;
public BusinessAgent(Sell vendor){
this.vendor = vendor;
}
public void sell() {
if (isCollegeStudent()) {
vendor.sell();
}
}
public void ad() {
vendor.ad();
}
}
Это соответствует второму преимуществу использования прокси, о котором мы упоминали выше: может быть достигнуто разделение между клиентом и классом делегата, и может быть выполнена некоторая дополнительная обработка без изменения кода класса делегата. Ограничение статического прокси заключается в том, что прокси-класс должен быть написан перед запуском Давайте сосредоточимся на динамическом прокси-методе создания прокси-класса во время выполнения.
2. Динамический прокси
1. Что такое динамический прокси
Метод прокси, создаваемый классом прокси во время работы программы, называется динамическим прокси. То есть в данном случае прокси-класс не определяется в Java-коде, а динамически генерируется во время выполнения на основе наших «инструкций» в Java-коде. Преимущество динамического прокси по сравнению со статическим прокси заключается в том, что он может удобно обрабатывать функции классов прокси унифицированным образом без изменения функций каждого класса прокси. Это более абстрактно Давайте объединим пример, чтобы показать, как это преимущество динамического прокси отражается.
Теперь предположим, что мы хотим реализовать такое требование: выводить «до» перед выполнением метода в классе делегата и выводить «после» после выполнения. Мы по-прежнему используем класс Vendor в приведенном выше примере в качестве класса делегата и класс BusinessAgent в качестве прокси-класса для ознакомления. Во-первых, давайте использовать статический прокси для выполнения этого требования.Соответствующий код выглядит следующим образом:
public class BusinessAgent implements Sell {
private Vendor mVendor;
public BusinessAgent(Vendor vendor) {
this.mVendor = vendor;
}
public void sell() {
System.out.println("before");
mVendor.sell();
System.out.println("after");
}
public void ad() {
System.out.println("before");
mVendor.ad();
System.out.println("after");
}
}
Из приведенного выше кода мы можем понять, что реализация наших требований через статический прокси требует от нас добавления соответствующей логики к каждому методу.Здесь всего два метода, поэтому нагрузка не слишком велика.Если интерфейс Sell содержит сотни методов A? В настоящее время использование статического прокси-сервера приведет к написанию большого количества избыточного кода. Используя динамические прокси, мы можем сделать «унифицированную инструкцию», чтобы единообразно обрабатывать методы всех классов прокси, не изменяя каждый метод один за другим. Давайте представим, как использовать динамический прокси для достижения наших целей.
2. Используйте динамические прокси
2.1 Интерфейс InvocationHandler
При использовании динамического прокси нам необходимо определить промежуточный класс между прокси-классом и классом делегата.Этот промежуточный класс требуется для реализации интерфейса InvocationHandler, который определяется следующим образом:
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
Из имени InvocationHandler мы можем узнать, что промежуточный класс, реализующий этот интерфейс, используется как «обработчик вызова». Когда мы вызываем метод объекта прокси-класса, «вызов» будет переадресован методу вызова. Объект прокси-класса передается в качестве параметра прокси. Метод параметра определяет, какой метод класса прокси мы конкретно вызываем, а args — значение этого параметра метода. Таким образом, наши вызовы всех методов в прокси-классе станут вызовами для вызова, чтобы мы могли добавить унифицированную логику обработки в метод вызова (вы также можете выполнять различную обработку для разных методов прокси-класса в соответствии с параметром метода). Поэтому нам нужно только вывести «до» в реализации метода вызова класса-посредника, затем вызвать метод вызова класса делегата, а затем вывести «после». Давайте реализуем это шаг за шагом.
2.2 Определение класса делегата
В режиме динамического прокси для реализации интерфейса требуется класс делегата, здесь мы реализуем интерфейс Sell. Определение класса делегата Vendor class выглядит следующим образом:
public class Vendor implements Sell {
public void sell() {
System.out.println("In sell method");
}
public void ad() {
System,out.println("ad method");
}
}
2.3 Промежуточный класс
Как мы упоминали выше, промежуточный класс должен реализовать интерфейс InvocationHandler в качестве обработчика вызова для «перехвата» вызовов методов прокси-класса. Определение класса посредника выглядит следующим образом:
public class DynamicProxy implements InvocationHandler {
//obj为委托类对象;
private Object obj;
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(obj, args);
System.out.println("after");
return result;
}
}
Из приведенного выше кода мы видим, что промежуточный класс содержит ссылку на объект класса делегата и вызывает соответствующий метод объекта класса делегата в методе вызова.Вы чувствуете себя знакомым, когда видите это?
При сохранении ссылки на объект класса делегата посредством агрегирования внешние вызовы для вызова в конечном итоге преобразуются в вызовы объекта класса делегата. Разве это не реализация статического прокси, который мы представили выше?
На самом деле промежуточный класс и класс делегирования представляют собой статические агентские отношения, в которых промежуточный класс является прокси-классом, а класс делегирования — классом делегирования;
Прокси-класс и промежуточный класс также образуют статические прокси-отношения, в которых промежуточный класс является классом делегата, а прокси-класс — прокси-классом.
Это,Динамические прокси-отношения состоят из двух наборов статических прокси-отношений, что является принципом динамического прокси.. Давайте посмотрим, как «проинструктировать» динамически генерировать прокси-классы.
2.4 Динамическое создание прокси-классов
Соответствующий код для динамического создания прокси-класса выглядит следующим образом:
public class Main {
public static void main(String[] args) {
//创建中介类实例
DynamicProxy inter = new DynamicProxy(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}, inter));
//通过代理类对象调用代理类方法,实际上会转到invoke方法调用
sell.sell();
sell.ad();
}
}
В приведенном выше коде мы вызываем метод newProxyInstance класса Proxy, чтобы получить экземпляр класса прокси. Этот прокси-класс реализует указанный нами интерфейс и отправляет вызовы методов указанному обработчику вызовов. Объявление этого метода выглядит следующим образом:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
Значения трех параметров метода следующие:
загрузчик: ClassLoder, который определяет класс прокси; interfaces: список интерфейсов, реализованных прокси-классом h: обработчик вызова, то есть экземпляр класса, который мы определили выше, который реализует интерфейс InvocationHandler.
Давайте запустим его и посмотрим, работает ли наш динамический прокси. Вывод после того, как я запустил его здесь,
Это показывает, что наш динамический прокси действительно работает.
Мы кратко упомянули принцип динамического прокси выше, и вот краткое резюме: сначала экземпляр класса прокси получается через метод newProxyInstance, а затем мы можем вызвать метод класса прокси через экземпляр класса прокси, и вызвать прокси метод класса.Фактически будет вызываться метод invoke класса-посредника (вызывающий процессор).В методе invoke мы вызываем соответствующий метод класса делегата, и можем добавить свою логику обработки.
3. Агентский режим
Это должен быть самый простой из шаблонов проектирования, диаграмма классов
Самая большая особенность прокси-режима заключается в том, что прокси-класс и фактический бизнес-класс реализуют один и тот же интерфейс (или наследуют один и тот же родительский класс).
Динамический прокси-сервер Java фактически реализуется внутри через механизм отражения Java, то есть известный объект, а затем его метод динамически вызывается во время выполнения, так что некоторая соответствующая обработка выполняется до и после вызова.
Например, у нас есть такое требование в нашем приложении, что нам нужно сделать операцию журнала до и после вызова метода определенного класса.
общий интерфейс
public interface AppService {
public boolean createApp(String name);
}
Класс реализации по умолчанию этого интерфейса
public class AppServiceImpl implements AppService {
public boolean createApp(String name) {
System.out.println("App["+name+"] has been created.");
return true;
}
}
Процессор журнала (по сути, действует как промежуточный класс)
/**
* 注意需实现Handler接口
*/
public class LoggerInterceptor implements InvocationHandler {
private Object target;//目标对象的引用,这里设计成Object类型,更具通用性
public LoggerInterceptor(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()+",with arguments{"+arg[0]+"}");
Object result = method.invoke(target, arg);//调用目标对象的方法
System.out.println("Before return:"+result);
return result;
}
}
внешний вызов
public class Main {
public static void main(String[] args) {
AppService target = new AppServiceImpl();//生成目标对象
//接下来创建代理对象
AppService proxy = (AppService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new LoggerInterceptor(target));
proxy.createApp("Kevin Test");
}
}