#java #spring #aop #aspectj #aspect
Вопрос:
Пожалуйста, объясните, почему самостоятельный вызов прокси выполняется на целевом сервере, но не на прокси-сервере? Если это сделано нарочно, то почему? Если прокси создаются путем создания подклассов, то перед каждым вызовом метода может выполняться некоторый код, даже при самостоятельном вызове. Я пытался, и у меня есть прокси для самостоятельного вызова
public class DummyPrinter {
public void print1() {
System.out.println("print1");
}
public void print2() {
System.out.println("print2");
}
public void printBoth() {
print1();
print2();
}
}
public class PrinterProxy extends DummyPrinter {
@Override
public void print1() {
System.out.println("Before print1");
super.print1();
}
@Override
public void print2() {
System.out.println("Before print2");
super.print2();
}
@Override
public void printBoth() {
System.out.println("Before print both");
super.printBoth();
}
}
public class Main {
public static void main(String[] args) {
DummyPrinter p = new PrinterProxy();
p.printBoth();
}
}
Выход:
Before print both
Before print1
print1
Before print2
print2
Здесь каждый метод вызывается по прокси-серверу. Почему в документации упоминается, что AspectJ следует использовать в случае самостоятельного вызова?
Ответ №1:
Пожалуйста, прочтите эту главу в руководстве Spring, тогда вы поймете. Там используется даже термин «самовзвращение». Если вы все еще не понимаете, не стесняйтесь задавать дополнительные вопросы, если они соответствуют контексту.
Обновление: Хорошо, теперь, после того как мы установили, что вы действительно прочитали эту главу, и после повторного прочтения вашего вопроса и анализа вашего кода, я вижу, что вопрос на самом деле довольно глубокий (я даже поддержал его) и стоит ответить более подробно.
Ваше (ложное) предположение о том, как это работает
Ваше недопонимание связано с тем, как работают динамические прокси, потому что они работают не так, как в вашем примере кода. Позвольте мне добавить идентификатор объекта (хэш-код) в вывод журнала для иллюстрации к вашему собственному коду:
package de.scrum_master.app;
public class DummyPrinter {
public void print1() {
System.out.println(this " print1");
}
public void print2() {
System.out.println(this " print2");
}
public void printBoth() {
print1();
print2();
}
}
package de.scrum_master.app;
public class PseudoPrinterProxy extends DummyPrinter {
@Override
public void print1() {
System.out.println(this " Before print1");
super.print1();
}
@Override
public void print2() {
System.out.println(this " Before print2");
super.print2();
}
@Override
public void printBoth() {
System.out.println(this " Before print both");
super.printBoth();
}
public static void main(String[] args) {
new PseudoPrinterProxy().printBoth();
}
}
Журнал консоли:
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print both
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print2
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print2
Видишь? Всегда существует один и тот же идентификатор объекта, что неудивительно. Самовзвращение для вашего «прокси» (который на самом деле не является прокси, а статически скомпилированным подклассом) работает из-за полиморфизма. Об этом заботится компилятор Java.
Как это на самом деле работает
Теперь, пожалуйста, помните, что мы говорим здесь о динамических прокси-серверах, т. е. подклассах и объектах, созданных во время выполнения:
- Прокси-серверы JDK работают для классов, реализующих интерфейсы, что означает, что классы, реализующие эти интерфейсы, создаются во время выполнения. В этом случае все равно нет суперкласса, что также объясняет, почему он работает только для общедоступных методов: интерфейсы имеют только общедоступные методы.
- Прокси CGLIB также работают для классов, не реализующих никаких интерфейсов, и, следовательно, также работают для защищенных методов и методов с областью действия пакета (не частных, хотя, поскольку вы не можете их переопределить, поэтому термин «частный»).
- Решающим моментом, однако, является то, что в обоих вышеперечисленных случаях исходный объект уже (и все еще) существует при создании прокси-серверов, поэтому такой вещи, как полиморфизм, не существует. Ситуация такова, что у нас есть динамически созданный прокси-объект, делегирующий исходный объект, т. е. у нас есть два объекта: прокси и делегат.
Я хочу проиллюстрировать это так:
package de.scrum_master.app;
public class DelegatingPrinterProxy extends DummyPrinter {
DummyPrinter delegate;
public DelegatingPrinterProxy(DummyPrinter delegate) {
this.delegate = delegate;
}
@Override
public void print1() {
System.out.println(this " Before print1");
delegate.print1();
}
@Override
public void print2() {
System.out.println(this " Before print2");
delegate.print2();
}
@Override
public void printBoth() {
System.out.println(this " Before print both");
delegate.printBoth();
}
public static void main(String[] args) {
new DelegatingPrinterProxy(new DummyPrinter()).printBoth();
}
}
Видите разницу? Следовательно, журнал консоли изменяется на:
de.scrum_master.app.DelegatingPrinterProxy@59f95c5d Before print both
de.scrum_master.app.DummyPrinter@5c8da962 print1
de.scrum_master.app.DummyPrinter@5c8da962 print2
Это поведение, которое вы видите с Spring AOP или другими частями Spring, использующими динамические прокси-серверы или даже приложения, не являющиеся Spring, использующие прокси JDK или CGLIB в целом.
Это особенность или ограничение? Я, как пользователь AspectJ (а не Spring AOP), думаю, что это ограничение. Возможно, кто-то еще может подумать, что это функция, потому что из-за того, как использование прокси реализовано весной, вы в принципе можете (отменить)динамически регистрировать советы по аспектам или перехватчики во время выполнения, т. Е. У вас есть один прокси на исходный объект (делегат), но для каждого прокси есть динамический список перехватчиков, вызываемых до и/или после вызова исходного метода делегата. Это может быть хорошей вещью в очень динамичных условиях. Я понятия не имею, как часто вы могли бы использовать это. Но в AspectJ у вас также есть обозначение if()
pointcut, с помощью которого вы можете определить во время выполнения, следует ли применять определенные советы (язык AOP для перехватчиков) или нет.
Решения
Что вы можете сделать для решения этой проблемы, так это:
- Переключитесь на собственный AspectJ, используя плетение во время загрузки, как описано в руководстве Spring. В качестве альтернативы вы также можете использовать плетение во время компиляции, например, с помощью плагина AspectJ Maven.
- Если вы хотите придерживаться Spring AOP, вам нужно сделать ваш бобовый прокси-сервер осведомленным, т. Е. косвенно также осведомленным о AOP, что далеко не идеально с точки зрения дизайна. Я не рекомендую этого делать, но это достаточно просто реализовать: просто самостоятельно введите ссылку на компонент, например
@Autowire MyComponent INSTANCE
, а затем всегда вызывайте методы, использующие этот экземпляр компонента:INSTANCE.internalMethod()
. Таким образом, все вызовы будут проходить через прокси-серверы, и будут срабатывать аспекты Spring AOP.
Комментарии:
1. Привет. Конечно, я его читал. Я не понимаю, почему введено это ограничение на самовзвращение? Есть ли от этого какая-то польза? Если я доверяю методы, интуитивно я ожидаю, что этот код будет выполнен. Eaven если это функция, почему там сказано, что AspectJ следует использовать для самостоятельного вызова, если можно получить тот же результат путем подкласса?
2. Пример с делегатом выглядит как декоратор, но цель другая. И прокси, и декоратор являются структурными шаблонами. И оба они оборачивают другой объект.
3. Как у прокси, так и у декоратора есть делегат. Исходный шаблон прокси-сервера-это именно то, что мы видим с динамическими прокси JDK, т. Е. Как делегат, так и прокси реализуют один и тот же интерфейс. Шаблон декоратора также сделал бы это, но имел бы базового декоратора и любое количество конкретных декораторов, расширяющих базового декоратора, т. Е. Структура отличается от прокси-сервера. Мой пример кода-это скорее то, что делает прокси CGLIB, т. Е. Расширяет конкретный класс. Как прокси, так и декоратор могут добавлять функциональность. Это не делает декораторов весенних прокси, но это довольно академично и не сильно помогает.
4. В качестве позднего ответа на @krund: Мой ответ уже объясняет это: динамические прокси расширяют исходный объект во время выполнения (!), а не во время компиляции, как подкласс, который вы пишете в своей среде разработки, а затем компилируете его вместе с базовым классом. Вы всегда будете создавать экземпляр подкласса только во время выполнения, но самый простой способ-создать исходный объект, а затем дополнительный прокси-сервер. Следовательно, самовзвращение (вызовы методов через
this
) не может работать так, как вы ожидаете, это невозможно с помощью прокси-шаблона. Но, как я уже сказал, AspectJ не использует прокси-серверы, поэтому вы можете просто использовать его, если вам нужен самовзвращение.