Говоря о фронте: сегодня давайте поговорим об агентах в Java, давайте сначала поговорим о предыстории истории:
Сяо Мин хочет купить духи определенной марки во Франции для своей девушки, но в Китае нет источника для продажи.Лично поехать во Францию требует больших усилий, а Сяохун сейчас играет во Франции.Она и Сяомин хорошие друзья и могут помочь Сяомин купить его.Этот бренд духов, поэтому Сяо Мин нашла Сяохун и пообещала дать ей дополнительные 5% ее тяжелой работы.Сяохун согласилась, и Сяомин успешно купила французские духи в Китае. После этого Сяохун запустил сумасшедшую модель закупок и заработал много комиссий за обработку.
В рассказеСяо Мин — клиент, он попросил Xiaohong помочь купить духи,Xiaohong стал прокси-объектом,иПоставщик ароматизаторов - реальный объект, может продавать духи, Сяомин купил французские духи через агента Сяохуна, что является примером покупки. Я нарисовал схему, чтобы помочь понять всю структуру рассказа.
Эта история представляет собой наиболее типичную агентскую модель: агент по закупкам покупает товар у поставщика и возвращает его звонящему, то есть Сяо Мину, которому нужен агент.
Агенты можно разделить на静态代理
и动态代理
Две категории:
статический прокси
- Преимущества: простая структура кода, простота реализации.
- Недостатки: не может адаптироваться ко всем сценариям использования прокси, при появлении новых требований необходимо изменить класс прокси.Не соответствует принципу открытого-закрытого программного обеспечения
Сяохун теперь является только агентом парфюмерии.Если Сяомину нужно найти Сяохуна, чтобы купить французское красное вино, то Сяохун должен быть агентом по французскому красному вину, но статический агент может расширить функцию агентства.Внутренняя логика Xiaohong должна быть изменена, что сделает внутренний код Xiaohong все более и более раздутым., которые будут подробно проанализированы позже.
Динамический прокси
- Преимущества: он может динамически адаптироваться к конкретным сценариям использования прокси и обладает хорошей масштабируемостью.В соответствии с принципом открытого-закрытого программного обеспечения
- Недостатки: динамический прокси-сервер должен использовать механизм отражения и динамически генерировать байт-код, что приводит к несколько худшей производительности, чем у статического прокси-сервера.Но эти недостатки практически ничтожны по сравнению с достоинствами.
Если Сяо Мину нужно найти Сяохуна как агента по продаже красного вина, мыНет необходимости изменять внутреннюю логику прокси-класса Xiaohong., просто сосредоточьтесь на расширенных функциональных точках:Агент красное вино, создайте экземпляр нового класса и сделайте Xiaohong с помощью некоторых преобразованийМожет представлять как духи, так и красное вино.
В этой статье мы попытаемся помочь вам понять все важные моменты в Java Agent с помощью следующих пунктов:
- Изучите шаблон агента (код для реализации истории, объясните особенности структуры классов шаблона агента)
- Сравните сходства и различия между статическим прокси и динамическим прокси.
- Две распространенные реализации динамических прокси на Java (JDK Proxy и Cglib)
- Применение динамического прокси (Spring AOP)
прокси-режим
(1) Определимпродавать духиИнтерфейс, определите способ продажи парфюма и укажите цену парфюма.
public interface SellPerfume {
void sellPerfume(double price);
}
(2) Определить поставщика духов Chanel и реализовать интерфейс.
public class ChanelFactory implements SellPerfume {
@Override
public void sellPerfume(double price) {
System.out.println("成功购买香奈儿品牌的香水,价格是:" + price + "元");
}
}
(3) Определениенемного красныйПрокси-класс, который ей нужно купить, чтобы продавать духи Chanel, поэтому она является прокси-объектом поставщика духов Chanel, также реализует интерфейс и хранит ссылку на целевой объект (поставщик Chanel) внутри для управления доступом других объектов к целевой объект.
public class XiaoHongSellProxy implements SellPerfume {
private SellPerfume sellPerfumeFactory;
public XiaoHongSellProxy(SellPerfume sellPerfumeFactory) {
this.sellPerfumeFactory = sellPerfumeFactory;
}
@Override
public void sellPerfume(double price) {
doSomethingBeforeSell(); // 前置增强
sellPerfumeFactory.sellPerfume(price);
doSomethingAfterSell(); // 后置增强
}
private void doSomethingBeforeSell() {
System.out.println("小红代理购买香水前的额外操作...");
}
private void doSomethingAfterSell() {
System.out.println("小红代理购买香水后的额外操作...");
}
}
(4) Сяо Мин требователен, ему нужно купить духи, он может купить их только через Сяохуна, поэтому он идет к Сяохуну, чтобы купить их.1999.99
духи.
public class XiaoMing {
public static void main(String[] args) {
ChanelFactory factory = new ChanelFactory();
XiaoHongSellProxy proxy = new XiaoHongSellProxy(factory);
proxy.sellPerfume(1999.99);
}
}
Давайте посмотрим на текущие результаты: Сяохун может выполнять дополнительные операции, прежде чем продавать духи Сяомину.скидки, бесплатная доставка..., если агент по закупкам точки черного сердца будетПлюс плата за обработку, невозвратная продажа..., не очень интересно.
Давайте посмотрим на структуру отношений диаграммы классов, состоящую из вышеуказанных 4 классов, и мы обнаружим, чтонемного красныйипоставщик Шанельвсе сбудетсяпродавать духиЭтот интерфейс, и Xiaohong добавил внутреннюю ссылку на поставщика, которая используется для вызова функции продажи ароматов поставщика.
Для реализации режима прокси необходимо выполнить следующие шаги:
- Определить общедоступный интерфейс для реальных и прокси-объектов(Продам парфюмерный интерфейс)
- Прокси-объект внутренне содержит ссылку на реальный целевой объект.(Сяохун цитирует провайдера)
- Посетитель может получить доступ к реальному целевому объекту только через прокси-объект,Целевой объект не доступен напрямую(Сяо Мин может покупать духи только через Xiaohong, а не напрямую у поставщика Chanel)
Место, где прокси-режим склонен к неправильному мышлению: прокси-объект — это не объект, который действительно предоставляет услуги, это просто объект, который обращается к целевому объекту для посетителя.посредник, Предоставляя реальные услуги или целевой объект, а роль прокси-объекта состоит в том, чтобы предоставить услуги до и после того, как целевой объект может выполнять дополнительную логику.
Судя по истории, Xiaohong на самом деле не продает духи, а поставщик Chanel, и Xiaohong просто выполнил некоторые дополнительные операции до и после того, как Chanel разрешил продавать духи.
Поговорив о кодовой реализации этого режима прокси, давайте систематически изучим, как он определяется и на какие спецификации необходимо обратить внимание для его реализации.
Определение режима прокси:Предоставляет прокси-объект для целевого объекта, прокси-объект содержит целевой объект и управляет доступом к целевому объекту.
Назначение режима прокси:
- Благодаря изоляции прокси-объекта вы можете получить доступ к целевому объекту до и послеДобавьте дополнительную бизнес-логику для достижения функциональных улучшений.
- Чтобы получить доступ к целевому объекту через прокси-объект, вы можетеПредотвратить неправильный доступ системы к целевому объекту напрямую, с непредсказуемыми последствиями
Статические и динамические прокси
У вас такие же сомнения, как и у меня: зачем прокси делить на статические и динамические? Они два разных?
Очевидно, такие сомнения будут у всех, давайте сначала посмотрим на их сходство:
- Все могут реализовать режим прокси (это ли не ерунда...)
- Будь то статический прокси или динамический прокси, и прокси-объект, и целевой объект должны реализоватьпубличный интерфейс
Дело, конечно же, в их отличиях.Динамические прокси совершенствуются на основе статических прокси, что значительно повышает производительность программы.ремонтопригодностьиМасштабируемость. Позвольте мне сначала перечислить различия между ними, а затем подробно объяснить, почему у статических прокси нет этих двух характеристик:
- Время динамического прокси для создания прокси-объектаДинамически генерируется во время выполнения, у него нет исходных файлов Java,Напрямую создавать файлы байт-кода для создания экземпляров прокси-объектов; и прокси-объект статического прокси, ввремя компиляции программыФайл Java написан, и можно напрямую создать новый прокси-объект.
- Динамические прокси более надежны, чем статические, и более удобны для поддержки и расширения программ.
В настоящее время агент Сяохун может покупать духи в качестве агента, но однажды пришел другой друг Сяохуна, Сяо Хэ.Он хочет купить самое чистое французское красное вино, в Китае нет такого канала покупки. Сяохун оказался во Франции, поэтому Сяохэ хотел найти Сяохуна, чтобы помочь ему купить красное вино.
Но проблема в том, что в программе Сяохун может покупать духи только как агент.Если вы хотите купить вино в качестве агента,Как нам это сделать?
-
Создать интерфейс для продажи вина
-
И поставщик красного вина, и прокси-объект Xiaohong должны реализовать этот интерфейс.
-
Сяо Хэ посещает Сяохуна и просит Сяохуна продать ему красное вино.
Хорошо, это конец, код не будет повторяться, давайте обсудим, перед лицом этого нового сценария, есть ли какой-либо дефект в приведенном выше методе реализации?
Мы должны упомянуть, что в программной инженериипринцип открыто-закрыто
Принцип открытости-закрытости: в процессе написания программы все объекты программного обеспечения должны быть открыты для расширения и закрыты для модификации.
Статический прокси нарушает принцип «открыто-закрыто», потому что при появлении новых требований необходимо модифицировать класс прокси, добавлять и реализовывать новые интерфейсы и методы, из-за чего класс прокси становится больше и сложнее в обслуживании.
Хотя говорят, что в настоящее время класс агента реализует только два интерфейса, если Сяохун не только продает красное вино в качестве агента в будущем, но также должен продавать билеты в кино и покупать японские суши в качестве агента...** Интерфейсы реализованных будет становиться все больше и больше Чем больше, тем сложнее становится внутренняя структура,Весь класс выглядит раздутым, становится неподдерживаемым, и последующее расширение также будет проблемой.Пока меняется какой-либо интерфейс, будет задействован прокси-класс, а стоимость обслуживания высока.
Поэтому, чтобы улучшить расширяемость и ремонтопригодность класса и удовлетворить принцип открытого-закрытого, Java предоставляет механизм динамического прокси.
Общие реализации динамического прокси
Самое главное в динамических прокси, конечно же,динамичныйВ двух словах, самое главное в процессе изучения динамических агентов — понять, что такое динамика.
Давайте проясним:Проблема, решаемая динамическим прокси, заключается в том, что при столкновении с новыми требованиями нет необходимости изменять код прокси-объекта, а нужно только добавить интерфейсы и реальные объекты и вызвать клиента для завершения нового прокси.
Цель этого состоит в том, чтобы удовлетворить принципу открытости-закрытости разработки программного обеспечения и улучшить ремонтопригодность и расширяемость классов.
JDK Proxy
JDK Proxy — это механизм динамического прокси, предоставляемый JDK, который включает два основных класса, а именноProxy
иInvocationHandler
, давайте сначала поймем, как их использовать.
Взяв в качестве примера историю агента Сяохуна, продающего духи, поставщик парфюмерии Chanel по-прежнему является реальным объектом, и это было реализовано.SellPerfume
Интерфейс, здесь переписываться не будет, суть в томмаленький красный агент, прокси-объект здесь больше не один Сяохун, аагентство фабрика, у которого будет много прокси-объектов. Я нарисовал картинку, вы ее хорошо поймете после прочтения:
Сяо Мин приходит на фабрику агентства и должен купить духи Chanel, продаваемые во Франции, затем фабрика находит фактический прокси-объект (динамическое создание экземпляра) и назначает его Сяо Мину, например, Сяохун или Сяохуа, и позволяет агенту Объект удовлетворяет потребности Сяо Мина.Прокси-фабрика содержит бесконечное количество прокси-объектов, которые могут быть выделены, и то, что каждый объект может проксировать, может динамически изменяться по мере изменения программы без изменения прокси-фабрики.
Если однажды Сяо Мину понадобится кого-то развлечь, он сможетПокупка красного винаПрокси-объект, прокси-фабрика все еще может удовлетворить его потребности, независимо от того, какой прокси ему понадобится в будущем, он может быть удовлетворен, вы не думаете, что это потрясающе? Давайте научимся им пользоваться.
Давайте посмотрим, как выглядит структура диаграммы классов UML динамического прокси.
Видно, что он мало чем отличается от статического прокси, единственное изменение — объект прокси, который я отметил:Произведено агентством factory.
Это предложение означает:Прокси-объект динамически генерируется прокси-фабрикой во время работы программы, и в самом прокси-объекте нет исходного файла Java..
Тогда в центре нашего внимания2
Кусок:
- Как реализовать фабрику прокси
- Как динамически генерировать прокси-объекты через прокси-фабрику
Во-первых, фабрика прокси должна реализоватьInvocationHanlder
интерфейс и реализовать егоinvoke()
метод.
public class SellProxyFactory implements InvocationHandler {
/** 代理的真实对象 */
private Object realObject;
public SellProxyFactory(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doSomethingBefore();
Object obj = method.invoke(realObject, args);
doSomethingAfter();
return obj;
}
private void doSomethingAfter() {
System.out.println("执行代理后的额外操作...");
}
private void doSomethingBefore() {
System.out.println("执行代理前的额外操作...");
}
}
Метод invoke() имеет3
параметры:
-
Object proxy
: прокси-объект -
Method method
: метод, который фактически выполняется -
Object[] agrs
: значение списка параметров, передаваемое при вызове метода второго параметра.
Метод invoke() является прокси-методом, что означает, что когда клиент запрашивает прокси, этот метод выполняется. На этом класс proxy factory закончился, давайте посмотрим на второй пункт:Как динамически генерировать прокси-объекты через прокси-фабрику.
Генерировать прокси-объекты, которые необходимо использоватьProxy
класс, который может помочь нам сгенерировать любой прокси-объект, который предоставляет статический методnewProxyInstance
.
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
При создании экземпляра прокси-объекта вам необходимо передать3
параметры:
- Загрузчик ClassLoader: загрузчик классов, который загружает динамические прокси-классы.
- Интерфейсы класса>[]: интерфейс, реализованный прокси-классом, несколько интерфейсов могут быть переданы в
- InvocationHandler h: указывает класс прокси.обработчик вызовов, то есть при вызове метода в интерфейсе будет найдена фабрика прокси
h
,воплощать в жизньinvoke()
метод
Нам нужно использовать вышеуказанный метод, когда клиент запрашивает прокси.
public class XiaoMing {
public static void main(String[] args) {
ChanelFactory chanelFactory = new ChanelFactory();
SellProxyFactory sellProxyFactory = new SellProxyFactory(chanelFactory);
SellPerfume sellPerfume = (SellPerfume) Proxy.newProxyInstance(chanelFactory.getClass().getClassLoader(),
chanelFactory.getClass().getInterfaces(),
sellProxyFactory);
sellPerfume.sellPerfume(1999.99);
}
}
Результат выполнения такой же, как у статического прокси, но идеи у них разные, один статический, а другой динамический. Так как же отразить преимущества динамических прокси? Не волнуйся, просто посмотри вниз, и ты узнаешь.
Обратите внимание на картинку ниже, по сравнению с
静态代理
до и после улучшения, менеенемного красныйДва слова, на самом деле прокси-объекты, выделенные фабрикой прокси, являются случайными и не будут нацелены на конкретный прокси-объект, поэтому прокси-объекты, генерируемые каждый раз, разные, поэтому я не уверен, что это Xiaohong, но это может быть однозначно определено да,Этот агент может помочь Сяомину купить духи, как Сяохун!
Согласно развитию предыдущей сюжетной линии, Сяохун представлял красное вино, аСяо Мин снова хочет купить красное вино известной французской марки, поэтому я пошел на фабрику агентства и попросил назначить другого человека для покупки красного вина для Сяомина. Фабрика агентства сказала: "Конечно, нет проблем! Мы профессионалы! Подождите!"
Нам нужно реализовать два класса: класс поставщика вина и интерфейс продажи вина.
/** 售卖红酒接口 */
public interface SellWine {
void sellWine(double price);
}
/** 红酒供应商 */
public class RedWineFactory implements SellWine {
@Override
public void sellWine(double price) {
System.out.println("成功售卖一瓶红酒,价格:" + price + "元");
}
}
Затем, когда наш Сяомин запрашивает фабрику прокси, он можетСоздайте прокси, который может продавать вино.
public class XiaoMing {
public static void main(String[] args) {
// 实例化一个红酒销售商
RedWineFactory redWineFactory = new RedWineFactory();
// 实例化代理工厂,传入红酒销售商引用控制对其的访问
SellProxyFactory sellProxyFactory = new SellProxyFactory(redWineFactory);
// 实例化代理对象,该对象可以代理售卖红酒
SellWine sellWineProxy = (SellWine) Proxy.newProxyInstance(redWineFactory.getClass().getClassLoader(),
redWineFactory.getClass().getInterfaces(),
sellProxyFactory);
// 代理售卖红酒
sellWineProxy.sellWine(1999.99);
}
}
С нетерпением ожидая результатов исполнения, вы будете очень удивлены, обнаружив, что вы действительно можете продавать красное вино в качестве агента, но мыБез модификации прокси-фабрики.
Напомним, что когда мы добавили функцию винного прокси, нам нужно2
Шаги:
- Создать нового поставщика вин
SellWineFactory
Интерфейс с продажей винаSellWine
- Создайте экземпляр прокси-объекта на стороне клиента, затем купите вино у прокси-объекта.
подумай еще разПринцип открыт-закрыт: открыт для расширения, закрыт для модификации. Динамический прокси удовлетворяет этому важному принципу: при расширении функциональных требований нам нужно обращать внимание только на расширенную часть и не нужно модифицировать исходный код в системе.
Если вы заинтересованы в друзьях, которые хотят продолжить расследование, обратите внимание наProxy.newProxyInstance()
Таким образом, это способ запуска всего динамического прокси-сервера JDK.
Говоря об этом, динамический прокси, предоставляемый JDK, подошел к концу.Подытожим динамический прокси JDK:
(1) Как использовать динамический прокси JDK
- Фабрика прокси должна реализовать
InvocationHandler
Интерфейс, при вызове прокси-метода он перейдет к исполнениюinvoke()
метод - Необходимо использовать сгенерированный прокси-объект
Proxy
в объектеnewProxyInstance()
метод, возвращаемый объект может быть принудительно передан в один из входящих интерфейсов, а затем может быть вызван метод интерфейса для реализации прокси-сервера.
(2) Особенности динамического прокси JDK
- Целевой объект принудительно реализует интерфейс, иначе нельзя будет использовать динамический прокси JDK.
(Ниже представлен расширенный контент, если вы не хотите его читать, вы можете его пропустить)
Proxy.newProxyInstance() — это ключ к созданию динамического прокси-объекта. Давайте посмотрим, что в нем происходит. Я извлек важный код и опустил часть бесполезного для анализа кода.
private static final Class<?>[] constructorParams ={ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 获取代理类的 Class 对象
Class<?> cl = getProxyClass0(loader, intfs);
// 获取代理对象的显示构造器,参数类型是 InvocationHandler
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 反射,通过构造器实例化动态代理对象
return cons.newInstance(new Object[]{h});
}
мы видим первый6
Строка получает динамический прокси-объект, так как же он генерируется? Затем посмотрите вниз.
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 去代理类对象缓存中获取代理类的 Class 对象
return proxyClassCache.get(loader, interfaces);
}
Выяснилось, что в нем использовался кешproxyClassCache, который похож по структуре наmap
структура, согласно загрузчику классовloader
и интерфейс, реализованный реальным объектомinterfaces
Узнайте, есть ли соответствующий объект класса, давайте посмотрим внизget()
метод.
public V get(K key, P parameter) {
// 先从缓存中查询是否能根据 key 和 parameter 查询到 Class 对象
// ...
// 生成一个代理类
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}
В методе get(), если объект класса не получен из кеша, необходимо использоватьsubKeyFactoryдля создания экземпляра динамического прокси-объекта, а вProxyкласс содержитProxyClassFactoryВнутренний класс, который создает динамический прокси-класс, поэтому давайте посмотрим на ProxyClassFactory вapply()
метод.
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 非常重要,这就是我们看到的动态代理的对象名前缀!
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 一些状态校验
// 计数器,该计数器记录了当前已经实例化多少个代理对象
long num = nextUniqueNumber.getAndIncrement();
// 动态代理对象名拼接!包名 + "$Proxy" + 数字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成字节码文件,返回一个字节数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 利用字节码文件创建该字节码的 Class 类对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
Обратите внимание, что в методе apply() естьДва очень важных метода:
-
ProxyGenerator.generateProxyClass(): это метод создания файла байт-кода, он возвращает массив байтов, файл байт-кода по сути представляет собой массив байтов, поэтому
proxyClassFile
Массив представляет собой файл байт-кода -
defineClass0(): объект Class, создающий файл байт-кода, который
native
Собственный метод, вызывающий базовый метод операционной системы для создания объекта класса.
иproxyName
это имя прокси-объекта, мы видим, что он используетproxyClassNamePrefix + счетчикслился в новое имя. Поэтому, когда DEBUG, оставайтесь на переменной прокси-объекта, вы обнаружите, что имя переменной$Proxy0
.
На этом анализ исходного кода закончен, вы чувствуете себя опустошенным? Хахахаха, у меня действительно было такое чувство в то время, но теперь вы также чувствуете, что динамический прокси JDK не особенно сложен (пока у вас есть настойчивость)
CGLIB
CGLIB (библиотека генерации кода) — это не динамический прокси-сервер, поставляемый с JDK. Он должен импортировать сторонние зависимости. Это библиотека классов генерации байт-кода, которая может динамически генерировать пары прокси-классов во время выполнения.Классы Java и интерфейсы Javaрасширение.
CGLIB может не только проксировать интерфейсы Java, но иВозможность прокси для общих классов Java, а прокси-сервер JDKтолько для реализации интерфейсаКлассы Java выполняют прокси, поэтому CGLIB — хорошее расширение для прокси Java.Если класс, который необходимо проксировать, не реализует интерфейс, вы можете выбрать Cglib в качестве инструмента для реализации динамических прокси.
Слишком много бреда в одном предложении:CGLIB может проксировать классы Java, которые не реализуют интерфейсы.
Давайте узнаем, как использовать его нижеСяо Мин находит агентскую фабрику для покупки французских духовЭтот сюжетный фон является примером.
(1) Импорт зависимостей
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
Существует еще один пакет CGLIB, разница между ними заключается в
-nodep
Зависимости уже включены внутриASM
Код, связанный с фреймворком байт-кода, без дополнительных зависимостейASM
(2) В прокси-сервере CGLIB есть два основных класса:MethodInterceptor
интерфейс иEnhancer
Класс, первый — это корневой интерфейс, реализующий фабрику прокси, а второй — класс, создающий динамические прокси-объекты.Здесь я снова опубликую структурную диаграмму истории, чтобы помочь вам понять.
Сначала мы определяем фабрику проксиSellProxyFactory
.
public class SellProxyFactory implements MethodInterceptor {
// 关联真实对象,控制对真实对象的访问
private Object realObject;
/** 从代理工厂中获取一个代理对象实例,等价于创建小红代理 */
public Object getProxyInstance(Object realObject) {
this.realObject = realObject;
Enhancer enhancer = new Enhancer();
// 设置需要增强类的类加载器
enhancer.setClassLoader(realObject.getClass().getClassLoader());
// 设置被代理类,真实对象
enhancer.setSuperclass(realObject.getClass());
// 设置方法拦截器,代理工厂
enhancer.setCallback(this);
// 创建代理类
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
doSomethingBefore(); // 前置增强
Object object = methodProxy.invokeSuper(o, objects);
doSomethingAfter(); // 后置增强
return object;
}
private void doSomethingBefore() {
System.out.println("执行方法前额外的操作...");
}
private void doSomethingAfter() {
System.out.println("执行方法后额外的操作...");
}
}
Метод intercept() включает 4 параметра:
- Объект o: прокси-объект
- Метод метода: перехваченный метод
- Объекты Object[]: все значения входных параметров перехваченного метода
- MethodProxy methodProxy: прокси метода, используемый для вызова исходного метода.
за
methodProxy
Метод, вызываемый параметрами, имеет внутри две опции:invoke()
иinvokeSuper()
, разница между ними не объясняется в этой статье.Заинтересованные читатели могут обратиться к этой статье:Разница в анализе исходного кода Cglib между invoke и invokeSuper
существуетgetInstance()
метод, используяEnhancer
Класс создает экземпляр прокси-объекта (который можно рассматривать как Xiaohong) и возвращает его вызывающему объекту Сяомину для завершения операции прокси.
public class XiaoMing {
public static void main(String[] args) {
SellProxyFactory sellProxyFactory = new SellProxyFactory();
// 获取一个代理实例
SellPerfumeFactory proxyInstance =
(SellPerfumeFactory) sellProxyFactory.getProxyInstance(new SellPerfumeFactory());
// 创建代理类
proxyInstance.sellPerfume(1999.99);
}
}
Мы по-прежнему сосредоточены на расширяемости и ремонтопригодности, и Cglib по-прежнему соответствует требованиям.принцип открыто-закрыто, Если Сяо Мину нужно, чтобы Сяохун купил красное вино в качестве агента, что он должен делать? Из-за недостатка места я больше не буду выкладывать здесь полный код, вы можете попробовать реализовать его вручную, а можете иметь в голове общую идею реализации.
Подытожим динамический прокси CGLIB:
(1) Как использовать CGLIB:
- Потребности агентской фабрикиРеализовать интерфейс MethodInterceptor, и переопределить метод,Внутренне связанные реальные объекты, который контролирует доступ третьей стороны к реальному объекту; фабрика прокси открыта изнутри
getInstance(Object realObject)
метод,Используется для получения экземпляра прокси-объекта с фабрики прокси.. -
Enhancer
Этот класс используется для создания экземпляра прокси-объекта из фабрики прокси для предоставления прокси-сервисов вызывающей стороне.
Сравнение JDK Proxy и CGLIB
(2) Внимательно сравните, JDK Proxy и CGLIB имеют сходство:
JDK Proxy | CGLIB | |
---|---|---|
Фабрика прокси реализует интерфейс | InvocationHandler | MethodInterceptor |
Создайте прокси-объект для обслуживания клиента | Proxy | Enhancer |
Оба используют два основных класса, которые также отличаются:
-
Самое очевидное отличие: CGLIB может проксироватьбольшинство классов(упомянут второй пункт); в то время как JDK ProxyМогут использоваться только прокси-классы, реализующие интерфейс
-
CGLIB использует динамически создаваемые подклассы прокси-класса для реализации перехвата метода, а подкласс переопределяет перехваченный метод, поэтому CGLIB не может проксировать перехваченный метод.
final
классы и методы, украшенные ключевыми словами
Внимательный читатель обнаружит, что сказанноеПросто попробуй это
(Вы не сказали мне исходный код, настоящий гидрологический молот), суть динамического прокси в томПрограмма динамически генерирует объекты прокси-класса во время выполнения, перехватывает вызывающие методы и расширяет дополнительные функции до и после вызова методов., а принцип генерации динамических прокси-объектов таков:механизм отражения, в прошлой статье я подробно рассказывал о том, как использовать рефлексию для инстанцирования объектов и вызова методов... Она используется в прокси, так что рефлексия и прокси тоже естественная пара.Когда дело доходит до одного из них, то это неизбежное повлечет за собой другое.
Практическое применение динамических прокси
Традиционное программирование ООП соответствует отношениям кодирования сверху вниз, но не соответствует отношениям кодирования слева направо.Если вы не понимаете этого, вы можете обратиться к следующей анимации.ООП удовлетворяет нас для выполнения одного метода и один метод сверху вниз.но неВставлять код слева направо, и появление АОП хорошо это компенсирует.Позволяет нам извлекать повторяющуюся логику кода в один оверлей., оверлейный слой может быть встроен в логику исходного кода неосознанно при выполнении кода.
Spring AOP
Как показано на рисунке ниже, и метод 1, и метод 2 должны выполняться до и после метода.бревно, на самом деле будет больше методов, которым нужно записывать логи, традиционный ООП позволяет нам только вручную записывать логи до и после каждого метода, большое количествоLog.info
Он существует внутри метода, что снижает читабельность кода, и метод не может сосредоточиться на собственной логике.
АОП может обернуть эти повторяющиеся коды в дополнительный уровень для отслеживания выполнения метода.При вызове метода общий уровень ведения журнала перехватывает метод и записывает журнал до и после вызова метода, чтобы метод мог быть сфокусирован Сосредоточьтесь на собственной бизнес-логике, не обращая внимания на другую ненужную информацию.
Spring AOP имеет много возможностей: обеспечивает кэширование, обеспечивает перенос логов, обработку транзакций... Здесь я начну сделаВ качестве примера, чтобы показать вам, как Spring использует динамические прокси под капотом.
Транзакции Spring включают основную аннотацию@Transactional
, я считаю, что многие использовали его в проекте.После добавления этой аннотации, если возникнет исключение при выполнении метода, все транзакции в методе будут откатываться, иначе все подчинения вступят в силу.Это самый макроскопический производительность Как она достигается? Давайте сегодня проведем простой анализ.
Каждая операция, связанная с базой данных, должна обеспечивать либо успешное выполнение всех операций внутри транзакции, либо отказ всех операций.try...catch
блок кода завершен
SqlSession session = null;
try{
session = getSqlSessionFactory().openSession(false);
session.update("...", new Object());
// 事务提交
session.commit();
}catch(Exception e){
// 事务回滚
session.rollback();
throw e;
}finally{
// 关闭事务
session.close();
}
Если несколько методов должны написать этот кусок логики, это очень избыточно, поэтому Spring инкапсулирует для нас аннотацию @Transactional.После ее использования метод будет отслеживаться при вызове метода.Если метод содержит эту аннотацию, он будет автоматически помогает нам поставить базу данных Код связанных операций заворачивается, и в итоге формируется принцип кода, аналогичный вышеизложенному.Конечно, это не точно.Это просто для того, чтобы дать вам общий обзор, чтобы понять, в чем суть Spring АОП делает.Эта статья объясняет здесь, количество знаний также должно быть Есть много, хорошо переварить вышеперечисленные точки знаний и заложить прочную основу для последующего специального изучения АОП Spring.
Привет, я cxuan, я написал четыре PDF-файла самостоятельно, а именно: Сводка основ Java, Сводка ядра HTTP, Основы компьютера, Сводка ядра операционной системы, я организовал их в PDF-файлы, вы можете подписаться на официальную учетную запись Java Builder, чтобы ответить на PDF для получения информации о качестве.