1. Определение прокси -режима
Предоставьте объект с прокси -объектом, а объект Proxy управляет доступом к исходному объекту, то есть клиент напрямую не манипулирует исходным объектом, но косвенно манипулирует исходным объектом через прокси -объект.
Примером известного прокси -шаблона является отсчет ссылки: когда необходимо несколько копий сложного объекта, прокси -шаблон может быть объединен с режимом MetA, чтобы уменьшить количество памяти. Типичным подходом является создание сложного объекта и нескольких прокси, каждый прокси, относящийся к исходному объекту. Операции, которые действуют на агенте, будут направлены на исходный объект. Как только все агенты не существуют, сложные объекты удаляются.
Это просто понять прокси -модель, но на самом деле есть прокси -модель в жизни:
Мы можем купить билеты на поезд на железнодорожной станции, но мы также можем купить их в офисе продажи билетов на поезда. Офис продажи билетов на поезда здесь является агентом для покупок билетов на железнодорожной станции. То есть мы выпускаем запрос на покупку билетов в торговой точке. Отдел продаж отправит запрос на железнодорожную станцию, и железнодорожная станция отправит успешный ответ на покупку на торговую точку, и продажа сообщит вам еще раз.
Тем не менее, билеты можно приобрести только в торговой точке, но не возврат средств, в то время как билеты можно приобрести на железнодорожной станции, поэтому операции, поддерживаемая агентом, могут отличаться от операций заказанного объекта.
Позвольте мне привести еще один пример, с которым вы столкнетесь при написании программы:
Если существует существующий проект (у вас нет исходного кода, вы можете позвонить только в него), который может вызвать Int Compute (String exp1) для реализации расчета выражения суффикса. Если вы хотите использовать этот проект для реализации расчета выражения Infix, вы можете написать класс прокси и определить вычислитель (String Exp2). Этот параметр exp2 является выражением инфикса. Следовательно, вам необходимо преобразовать экспрессию Infix в выражение суффикса (предварительное просматривание), прежде чем вызовать Compute () существующего проекта, а затем вызовать Compute () существующего проекта. Конечно, вы также можете получить обратную стоимость и выполнять другие операции, такие как сохранение файла (постпроцесс). Этот процесс использует прокси -режим.
При использовании компьютера вы также встретите приложения режима прокси:
Удаленный прокси: мы не можем получить доступ к Facebook из -за GFW в Китае. Мы можем получить к нему доступ, просматривая стену (настройка прокси). Процесс доступа:
(1) Пользователь отправляет HTTP -запрос на прокси -сервер
(2) Прокси отправляет HTTP -запрос на веб -сервер
(3) Веб -сервер отправляет ответ HTTP на прокси
(4) Прокси отправляет ответ HTTP обратно пользователю
2. Статический прокси
Так называемый статический прокси означает, что на стадии компиляции генерируется класс прокси, чтобы завершить серию операций на прокси-объекте. Ниже приведена структура классовая диаграмма прокси -паттерна:
1. Участники прокси -модели
В режиме прокси -режима есть четыре роли:
Интерфейс темы: то есть поведенческий интерфейс, реализованный классом прокси.
Целевой объект: то есть объект, который является доверенным лицом.
Прокси -объект: прокси -клиент, используемый для инкапсуляции реального класса темы.
2. Идеи для реализации модели агента
Как объект прокси, так и целевой объект реализуют один и тот же поведенческий интерфейс.
Класс прокси и целевой класс реализуют логику интерфейса отдельно.
Создать создание целевого объекта в конструкторе класса прокси.
Вызов поведенческого интерфейса целевого объекта в классе прокси.
Если клиент хочет вызвать поведенческий интерфейс целевого объекта, он может работать только через класс прокси.
3. Примеры статического прокси -сервера
Ниже приведен ленивый пример загрузки, чтобы проиллюстрировать статический прокси. Когда мы запустим систему обслуживания, для загрузки определенного класса может потребоваться много времени. Чтобы получить лучшую производительность, при запуске системы мы часто не инициализируем этот сложный класс, но вместо этого инициализируем его класс прокси. Это разделяет методы потребления ресурсов с использованием прокси для разделения, что может ускорить скорость запуска системы и сократить время ожидания пользователя.
Определите интерфейс темы
Общественный интерфейс субъект {public void sayshello (); Public void SpeaksGoodbye ();} Определите целевой класс и реализуйте интерфейс темы
открытый класс RealSubject реализует субъект {public void sayshello () {System.out.println ("Hello World"); } public void showergybye () {System.out.println ("Goodbye World"); }} Определите класс прокси для прокси -сервера целевого объекта.
открытый класс staticproxy реализует субъект {private RealSubject RealSubject = null; public staticproxy () {} public void sayshello () {// он загружен в это время, ленивая загрузка if (RealSubject == null) {RealSubject = new RealSubject (); } RealSubject.SayHello (); } // метод saygoodbye такой же ...} Определите клиента
Public Class Client {public static void main (string [] args) {staticproxy sp = new Staticproxy (); sp.sayhello (); sp.saygoodbye (); }}Выше приведено простым испытательным примером статического прокси. Это может не чувствовать себя практичным. Однако это не так. Используя прокси, мы также можем преобразовать методы целевого объекта. Например, серия соединений создается в пуле подключений базы данных. Чтобы гарантировать, что соединения открываются нечасто, эти соединения почти никогда не закрыты. Тем не менее, у нас всегда есть привычка закрывать открытую связь. Таким образом, мы можем использовать прокси -режим для воспроизведения метода закрытия в интерфейсе соединения и изменить его для переработки в пул подключения к базе данных вместо того, чтобы фактически выполнять метод Connection#Close. Есть много других примеров, и вам нужно познакомиться с ними самостоятельно.
3. Динамический агент
Динамический прокси относится к динамическому генерированию классов прокси во время выполнения. То есть байт -код класса прокси будет генерироваться и загружаться во время выполнения в класс -загрузку текущего прокси. По сравнению со статическими классами обработки, динамические классы имеют много преимуществ.
Нет необходимости писать совершенно идентичный класс инкапсуляции для реальной темы. Если в интерфейсе темы есть много методов, также трудно написать прокси -метод для каждого интерфейса. Если интерфейс изменяется, реальная тема и прокси -классы должны быть изменены, что не способствует обслуживанию системы;
Использование некоторых динамических методов генерации прокси может даже сформулировать логику выполнения класса прокси во время выполнения, тем самым значительно улучшив гибкость системы.
Есть много способов генерировать динамический прокси: JDK поставляется с динамическим прокси, CGLIB, Javassist и т. Д. Эти методы имеют свои преимущества и недостатки. В этой статье в основном рассматривается использование динамического анализа прокси и исходного кода в JDK.
Вот пример, чтобы объяснить использование динамического прокси в JDK:
Общедоступный класс DynamicProxy реализует upocationHandler {private RealSubject = null; public Object invoke (Object Proxy, Метод метода, Object [] args) {if (RealSubject == null) {RealSubject = new RealSubject (); } method.invoke (RealSubject, Args); вернуть RealSubject; }}Пример кода клиента
Public Class Client {public static void main (Strings [] args) {субъект субъекта = (субъект) proxy.newinstance (classloader.getsystemloader (), realsubject.class.getInterfaces (), new DynamicProxy ()); Субъект.sayhello (); Субъект.saygoodbye (); }}Как видно из приведенного выше кода, нам нужно использовать динамический прокси в JDK. Используйте статический метод Proxy.newinStance (ClassLoader, Interfaces [], Invokehandler), чтобы создать динамический класс прокси. Метод NewInstance имеет три параметра, которые представляют собой загрузчик класса, список интерфейсов, которые вы хотите реализовать класс прокси, и экземпляр, который реализует интерфейс Invokehandler. Динамический прокси передал процесс выполнения каждого метода методу inloke для обработки.
Динамический прокси -сервер JDK требует, чтобы прокси был интерфейсом, но простой класс не может. Классы прокси, сгенерированные JDK Dynamic Proxy, будут наследовать класс прокси, а класс прокси реализует весь список интерфейсов, в котором вы проходили. Следовательно, тип может быть поднят в тип интерфейса. Ниже приведена структурная схема прокси.
Можно видеть, что прокси - это все статические методы, поэтому, если класс прокси не реализует какого -либо интерфейса, то это тип прокси и не имеет методов экземпляра.
Конечно, если вы присоединитесь, вы должны прокси -прокси, который не реализует определенный интерфейс, и методы этого класса такие же, как и те, которые определяются другими интерфейсами, и его можно легко реализовать с помощью отражения.
Общедоступный класс DynamicProxy реализует InvokeHandler {// класс, который вы хотите прокси -личкому targetClass TargetClass = null; // Инициализировать этот класс public DynamicProxy (TargetClass TargetClass) {this.TargetClass = TargetClass; } public Object invoke (объект прокси, метод метода, объект [] args) {// Использовать отражение для получения класса, который вы хотите для прокси -метода mymethod = targetClass.getClass (). getDeclaredMethod (method.getName (), method.getParameterTepes ()); mymethod.setAccessible (true); вернуть mymethod.invoke (TargetClass, Args); }}4. Анализ исходного кода динамического прокси JDK (JDK7)
Посмотрев на пример выше, мы просто знаем, как использовать динамический прокси. Тем не менее, все еще туманно относится к тому, как создается класс прокси, который назвал метод Invoke и т. Д. Следующий анализ
1. Как создаются прокси -объекты?
Сначала посмотрите на исходный код метода proxy.newinstance:
Public Static Object NewProxyInstance (ClassLoader Loader, Class <?> [] Интерфейсы, volcocationHandler h) Throws allogalArgumentException {} // Получить информацию об интерфейсе окончательный класс <?> [] intfs = interfaces.clone (); Final SecurityManager SM = System.GetSecurityManager (); if (sm! = null) {checkproxyaccess (Reflection.getCallerclass (), Loader, Intfs); } // генерировать класс прокси -класса <?> Cl = getProxyClass0 (Loader, Intfs); // ... хорошо, давайте посмотрим на первую половину первой}Из исходного кода видно, что генерация прокси -классов зависит от метода getProxyClass0. Далее, давайте посмотрим на исходный код GetProxyClass0:
Частный статический класс <?> getProxyClass0 (ClassLoader Loader, Class <?> ... интерфейсы) {// Количество списков интерфейса не может превышать 0xffff if (интерфейсы. } // ПРИМЕЧАНИЕ ЗДЕСЬ, следующее объяснение дано подробно для возврата proxyclasscache.get (loader, интерфейсы); } Объяснение ProxyClassCache.get: если класс прокси, который реализует список интерфейсов, уже существует, то выберите его непосредственно из кэша. Если его не существует, он генерируется через ProxyClassFactory.
Прежде чем посмотреть на исходный код proxyclasscache.get, давайте кратко поймем ProxyClassCache:
Private Static Final SleaseCache <ClassLoader, Class <?> [], Class <? >> ProxyClassCache = New SleedCache <> (new KeyFactory (), New ProxyClassFactory ());
ProxyClassCache - это кэш типа SleadCache. Его конструктор имеет два параметра. Одним из них является ProxyClassFactory, используемый для генерации класса прокси. Ниже приведен исходный код proxyclasscache.get:
Окончательный класс SleedCache <K, p, v> {... public v get (k key, pameter) {}}Здесь k представляет ключ, P представляет параметры, V представляет значение
public v get (k key, parater) {// java7 nullobject Метод суждения, если параметр пуст, будет брошено исключение с указанным сообщением. Если не пусто, вернитесь. Objects.Requirenonnull (параметр); // Очистить структуру данных SleedHashMap, которая содержит слабые ссылки, которые обычно используются для кэширования EnsungestaleEntries (); // Получить CacheKey из объекта очереди cachekey = cachekey.valueof (key, reafqueue); // заполнить поставщика ленивой загрузкой. Одновременный-это безопасная потока карта coundrentMap <объект, поставщик <v >> valuesmap = map.get (cachekey); if (valuesmap == null) {coundrentMap <объект, поставщик <v >> oldvaluesmap = map.putifabsent (cachekey, valuesmap = new concurrenthashmap <> ()); if (oldvaluesmap! = null) {valuesmap = oldvaluesmap; }} // Создать подкей и получить возможный поставщик <v>, хранящийся с помощью этого // подкиата из объекта Valuesmap subkey = objects.requirenonlonull (subkeyfactory.apply (key, parameter)); Поставщик <v> поставщик = valuemap.get (subkey); Заводская фабрика = null; while (true) {if (поставщик! = null) {// получить значение от поставщика. Это значение может быть заводской или реализацией кэша. // Следующие три предложения являются основным кодом, который возвращает класс, который реализует Invokehandler и содержит необходимую информацию. V value = поставщик.get (); if (value! = null) {return value; }} // else нет поставщика в кэше // или поставщика, который возвращал нулевый (может быть очищенным cachevalue // или фабрикой, которая не была успешной в установке Cachevalue) // Следующий процесс - это процесс заполнения поставщика if (factory == null) {// Создать фабрику} if (поставщик == inul Функция while Loop заключается в постоянном получении класса, который реализует Invokehandler. Этот класс может быть получен из кэша или сгенерирован из ProxyFactoryClass.
Фабрика - это внутренний класс, который реализует интерфейс поставщика <v>. Этот класс переопределяет метод GET, а метод экземпляра типа ProxyFactoryClass вызывается в методе GET. Этот метод является реальным способом создания класса прокси. Давайте посмотрим на исходный код метода ProxyFactoryClass#Применить:
public class <?> Apply (ClassLoader Loader, Class <?> [] Интерфейсы) {map <class <?>, boolean> interfaceset = new IdentityHashmap <> (интерфейсы.length); for (class <?> intf: interfaces) { /* Убедитесь, что загрузчик класса разрешает имя этого интерфейса к одному и тому же объекту класса.* / class <?> interfaceclass = null; try {// загрузить информацию о каждом интерфейсексексе = class.forname (intf.getName (), false, loader); } catch (classnotfoundexception e) {} // Если класс, загруженный вашим собственным классом, не равен классу, в котором вы проходите, добавьте исключение, если (Interfaceclass! = intf) {бросить новый allodalargumentException (intf + "не видно из загрузчика класса"); } // Если вход не является типом интерфейса if (! InterfaceClass.isInterface ()) {бросить новый allodalargumentException (enterfaceclass.getName () + "не является интерфейсом"); } // Убедитесь, повторяется ли интерфейс if (interfaceset.put (interfaceclass, boolean.true)! = Null) {throw new allogalargumentException ("повторный интерфейс:" + interfaceclass.getname ()); }} String proxypkg = null; // Пакет для определения класса прокси в /* Запишите пакет непубличного прокси-интерфейса, чтобы класс прокси был определен в том же пакете. * Убедитесь, что все непубличные прокси-интерфейсы находятся в одном пакете. */// Этот абзац зависит от того, существуют ли интерфейсы, которые не являются публичными в интерфейсе, который вы передали. Если это так, все эти интерфейсы должны быть определены в одном пакете. В противном случае, добавьте исключения для (класс <?> Intf: интерфейсы) {int flags = intf.getModifiers (); if (! modifier.ispublic (flags)) {string name = intf.getName (); int n = name.lastindexof ('.'); String pkg = ((n == -1)? "": Name.substring (0, n + 1)); if (proxypkg == null) {proxypkg = pkg; } else if (! pkg.equals (proxypkg)) {бросить new allogalargumentException («Непубличные интерфейсы из разных пакетов»); }}}} if (proxypkg == null) {// Если нет непубличных прокси-интерфейсов, используйте com.sun.proxy package proxypkg = Refertutil.proxy_package + "."; } / * * Выберите имя для создания класса прокси. */ long num = nextUniqueNumber.getAndIncrement (); // Сгенерировать имя класса класса случайных прокси, $ proxy + num String proxyname = proxypkg + proxyclassalmaprefix + num; /** Сгенерировать файл класса класса прокси -класса, вернуть байтовый поток*/ byte [] proxyclassfile = proxygenerator.generateproxyclass (proxyname, интерфейсы); try {return deciplasclass0 (Loader, ProxyName, ProxyClassFile, 0, proxyClassfile.length); } catch (classformaterror e) {// end выбрасывать new allogalargumentException (e.toString ()); }}}Применение вышеупомянутого ProxyFactoryClass# - это метод действительно генерирования классов прокси, который на самом деле является неточным. После прочтения исходного кода здесь мы обнаружим, что Proxygenerator#GenerateProxyClass - это метод действительно генерирования классов прокси. Сгенерируйте соответствующий файл класса в соответствии с композицией байткода класса Java (см. Другую мою статью Java Bytecode Learning Notes). Конкретный исходный код Proxygenerator#GenerateProxyClass выглядит следующим образом:
Private Byte [] generateClassfile () { / * * Шаг 1: Соберите объекты проксиметода для всех методов для * генерировать прокси -код для отправки. */ // Метод AddProxyMethod предназначен для добавления всех методов в список и соответствовать соответствующему классу // Вот три метода, соответствующие объекту, ToString и Equals AddProxyMethod (hashdemethod, object.class); addProxyMethod (equalsmethod, object.class); addProxyMethod (ToStringMethod, Object.Class); // Сравните интерфейс в списке интерфейса с методами в интерфейсе для (int i = 0; i <интерфейсы. for (int j = 0; j <methods.length; j ++) {addProxyMethod (методы [j], интерфейсы [i]); }} / * * Для каждого набора прокси -методов с той же подписью * убедитесь, что типы возврата методов совместимы. */ for (list <proxymethod> signmethods: proxymethods.values ()) {ceckreTurnTypes (sigmethods); } / * * Шаг 2: Соберите FieldInfo и методы методовфов для всех * полей и методов в классе, который мы генерируем. */// Добавить метод конструктора в метод, который является только одним конструктором, который является конструктором с интерфейсом vocationHandler./This является реальным методом добавления метода в файл класса, то есть класс прокси. Тем не менее, это еще не обработано. Сначала он добавлен и подождите петлю. Имя Описание конструктора в файле класса - <int> try {mehodss.add (generateConstructor ()); для (list <proxymethod> signmethods: proxymethods.values ()) {for (proxymethod pm: signmethods) {// добавить атрибут типа метода в каждый метод прокси. Номер 10 является идентификатором файла класса, что означает, что эти атрибуты являются Fields.Add (New FieldInfo (PM.MethodfieldName, "ljava/lang/Refert/method;", acc_private | acc_static)); // Добавить каждый прокси -метод в метод прокси -класса метода. }} // Добавить статический блок инициализации и инициализируйте каждый атрибут. Здесь статический кодовый блок также называется конструктором класса. На самом деле это метод с именем <clinit>, поэтому добавьте его в методы списка методов. } catch (ioException e) {бросить новый InternalError ("Неожиданное исключение ввода/вывода"); } // Количество методов и атрибутов не может превышать 65535, включая предыдущее количество интерфейсов. // Это потому, что в файле класса эти числа представлены в 4 -битной шестнадцатеричной, поэтому максимальное значение составляет 2 к мощности 16 -го -1 if (methods.size ()> 65535) {бросить новое allosalargumentException («предел метода превысил»); } if (fields.size ()> 65535) {бросить новое allosalargumentException («Предел поля превышен»); } // Следующий шаг - написать файл класса, включая магические номера, имена классов, постоянные пулы и другие серии байткодов. Я не буду вдаваться в подробности. Если вам это нужно, вы можете ссылаться на соответствующие знания виртуальной машины JVM. cp.getClass (Dottoslash (classname)); CP.GetClass (SuperClassName); for (int i = 0; i <интерфейсы. } cp.setreadonly (); BytearRayOutputStream BOUT = новый BytearRayOutputStream (); DataOutputStream dout = new DataOutputStream (BOUT); попробуйте {// U4 Magic; dout.writeint (0xcafebabe); // u2 minor_version; dout.writeshort (classfile_minor_version); // u2 major_version; dout.writeshort (classfile_major_version); CP.Write (Dout); // (Постоянный пул писать) // u2 access_flags; dout.writeshort (acc_public | acc_final | acc_super); // u2 this_class; dout.writeshort (cp.getClass (dottoslash (classname))); // u2 super_class; dout.writeshort (cp.getclass (superclassname)); // u2 interfaces_count; dout.writeshort (interfaces.length); // u2 интерфейсы [interfaces_count]; for (int i = 0; i <interfaces.length; i ++) {dout.writeshort (cp.getClass (dottoslash (интерфейсы [i] .getName ()))); } // u2 fields_count; dout.writeshort (fields.size ()); // Field_info Fields [fields_count]; для (FieldInfo F: Fields) {f.Write (Dout); } // u2 methods_count; dout.writeshort (methods.size ()); // method_info methods [method_count]; for (methodInfo m: methods) {m.Write (dout); } // u2 attributes_count; dout.writeshort (0); // (без атрибутов ClassFile для классов прокси)} catch (ioException e) {бросить новый InternalError («Неожиданное исключение ввода/вывода»); } return bout.tobytearray (); }После слоев вызовов, наконец -то генерируется класс прокси.
2. Кто позвонил в Anloke?
Мы имитируем JDK, чтобы само по себе генерировать класс прокси -класса, название класса TestProxyGen:
открытый класс TestGeneratorProxy {public static void main (string [] args) выбрасывает ioException {byte [] classfile = proxygenerator.generateproxyclass ("testproxygen", subject.class.getInterfaces ()); File file = new File ("/users/yadoao/desktop/testproxygen.class"); FileOutputStream fos = new FileOutputStream (file); fos.write (classfile); fos.flush (); fos.close (); }}Декомпиляция файла класса с помощью JD-Gui, и результат заключается в следующем:
import com.su.dynamicProxy.ISubject;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class TestProxyGen extends Proxy implements ISubject{ private static Method m3; частный статический метод M1; частный статический метод M0; частный статический метод M4; частный статический метод M2; Public TestProxyGen (vocationHandler paraminVocationHandler) бросает {super (paraminvocationHandler); } public final void sayshello () throws {try {this.h.invoke (this, m3, null); возвращаться; } catch (ошибка | runtimeexception localerror) {throw LocalError; } catch (throwable localthrowable) {бросить новый UndeclaredThrowableException (localThrowable); }} public final boolean equals (объект paramobject) throws {try {return ((boolean) this.h.invoke (this, m1, new object [] {paramobject})). booleanvalue (); } catch (ошибка | runtimeexception localerror) {throw LocalError; } catch (throwable localthrowable) {бросить новый UndeclaredThrowableException (localThrowable); }} public final int hashcode () throws {try {return ((integer) this.h.invoke (this, m0, null)). intvalue (); } catch (ошибка | runtimeexception localerror) {throw LocalError; } catch (throwable localthrowable) {бросить новый UndeclaredThrowableException (localThrowable); }} public final void shiedgybye () throws {try {this.h.invoke (this, m4, null); возвращаться; } catch (ошибка | runtimeexception localerror) {throw LocalError; } catch (throwable localthrowable) {бросить новый UndeclaredThrowableException (localThrowable); }} public final String toString () throws {try {return (string) this.h.invoke (this, m2, null); } catch (ошибка | runtimeexception localerror) {throw LocalError; } catch (throwable localthrowable) {бросить новый UndeclaredThrowableException (localThrowable); }} static {try {m3 = class.forname ("com.su.dynamicproxy.isubject"). getmethod ("sayhello", новый класс [0]); m1 = class.forname ("java.lang.object"). getmethod ("equals", new class [] {class.forname ("java.lang.object")}); m0 = class.forname ("java.lang.object"). getmethod ("hashcode", новый класс [0]); m4 = class.forname ("com.su.dynamicproxy.isubject"). getmethod ("saygoodbye", новый класс [0]); m2 = class.forname ("java.lang.object"). getMethod ("toString", новый класс [0]); возвращаться; } catch (nosuchmethodexception localnosuchmethodexception) {бросить новый nosuchmethoderror (localnosuchmethodexception.getmessage ()); } catch (classnotfoundexception localclassnotfoundexception) {бросить новый noclassdeffounderror (localclassnotfoundexception.getmessage ()); }}} Во -первых, я заметил, что конструктор сгенерированного класса прокси передается в классе, который реализует интерфейс Invokehandler в качестве параметра, и называется конструктором Proxy Proxy родительского класса, который инициализировал переменную членов Protected Invokehander H в прокси.
Я снова заметил несколько статических блоков инициализации. Блок статической инициализации здесь предназначен для инициализации списка интерфейса прокси и методов Hashcode, ToString и равных.
Наконец, существует процесс вызова этих методов, все из которых являются обратными обращениями метода Invoke.
Это заканчивается анализом этой прокси -паттерна.