Используются ли разные типы динамических прокси?
Почти точно
Давайте выясним, в чем разница между классами @Configuration
иПрокси-серверы AOP отвечают на следующие вопросы:
- Почему метод с собственным вызовом
@Transactional
не имеет транзакционной семантики, даже если Spring способен перехватывать методы с собственным вызовом? - Как
@Configuration
и АОП связаны между собой?
Почему метод с собственным вызовом @Transactional
не имеет транзакционной семантики?
Краткий ответ:
Этокак AOP сделал.
Длинный ответ:
- Декларативное управление транзакциями опирается на AOP (для большинстваприложений Spring в Spring AOP )
Декларативное управление транзакциями в Spring Framework стало возможным благодаря Spring-аспектно-ориентированному программированию (AOP)
Он основан на прокси (
§5.8.1. Общие сведения о прокси AOP )
Spring AOP основан на прокси.
Из того же абзаца SimplePojo.java
:
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
И фрагмент кода, который его проксирует:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
Здесь необходимо понять, что клиентский код внутриМетод main(..)
класса Main
имеет ссылку на прокси 1069 *.
Это означает, что вызовы методов для этой ссылки на объект являются вызовами для прокси .
В результате прокси может делегировать все перехватчики (рекомендации), которые имеют отношение к этому конкретному вызову метода.
Однако, как только вызов наконец достиг целевого объекта (SimplePojo
, в данном случае ссылка), любые вызовы методов, которые он может выполнить для себя, такие как this.bar()
или this.foo()
, будут вызываться по ссылке this
, а не по доверенности .
Это имеет важные последствия.Это означает, что самовывоз не приведет к тому, что совет, связанный с вызовом метода, получит шанс на выполнение.
( Ключевые части выделены. )
Вы можете подумать, что AOP работает следующим образом:
Представьте, что у нас есть класс Foo
, который мы хотим прокси:
Foo.java
:
public class Foo {
public int getInt() {
return 42;
}
}
Ничего особенного.Просто getInt
метод, возвращающий 42
Перехватчик:
Interceptor.java
:
public interface Interceptor {
Object invoke(InterceptingFoo interceptingFoo);
}
LogInterceptor.java
(для демонстрации):
public class LogInterceptor implements Interceptor {
@Override
public Object invoke(InterceptingFoo interceptingFoo) {
System.out.println("log. before");
try {
return interceptingFoo.getInt();
} finally {
System.out.println("log. after");
}
}
}
InvokeTargetInterceptor.java
:
public class InvokeTargetInterceptor implements Interceptor {
@Override
public Object invoke(InterceptingFoo interceptingFoo) {
try {
System.out.println("Invoking target");
Object targetRetVal = interceptingFoo.method.invoke(interceptingFoo.target);
System.out.println("Target returned " + targetRetVal);
return targetRetVal;
} catch (Throwable t) {
throw new RuntimeException(t);
} finally {
System.out.println("Invoked target");
}
}
}
Наконец InterceptingFoo.java
:
public class InterceptingFoo extends Foo {
public Foo target;
public List<Interceptor> interceptors = new ArrayList<>();
public int index = 0;
public Method method;
@Override
public int getInt() {
try {
Interceptor interceptor = interceptors.get(index++);
return (Integer) interceptor.invoke(this);
} finally {
index--;
}
}
}
Соединение всего вместе:
public static void main(String[] args) throws Throwable {
Foo target = new Foo();
InterceptingFoo interceptingFoo = new InterceptingFoo();
interceptingFoo.method = Foo.class.getDeclaredMethod("getInt");
interceptingFoo.target = target;
interceptingFoo.interceptors.add(new LogInterceptor());
interceptingFoo.interceptors.add(new InvokeTargetInterceptor());
interceptingFoo.getInt();
interceptingFoo.getInt();
}
Будет напечатано:
log. before
Invoking target
Target returned 42
Invoked target
log. after
log. before
Invoking target
Target returned 42
Invoked target
log. after
Теперь давайте посмотрим на ReflectiveMethodInvocation
.
Вот часть его proceed
метода:
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
++this.currentInterceptorIndex
теперь выглядит знакомо
Вы можете попробовать добавить несколько аспектов в ваше приложение и увидеть, как стек увеличивается при методе proceed
, когда вызывается рекомендованный метод
Наконец все заканчивается на MethodProxy .
Из его invoke
метода javadoc:
Invoke thОригинальный метод для другого объекта того же типа.
И, как я упоминал ранее, документация:
после того, как вызов наконец достиг объекта target
, любойвызовы методов, которые он может совершать сам по себе, будут вызываться по ссылке this
, а не по доверенности
Надеюсь, теперь более или менее понятно, почему.
Как @Configuration
и АОП связаны?
Ответ: они не связаны .
Так что Весна здесь свободна делать все, что захочет.Здесь он не привязан к семантике прокси AOP .
Он расширяет такие классы, используя ConfigurationClassEnhancer
.
Взгляните на:
Возвращаясь к вопросу
If Springможет успешно перехватывать вызовы функций внутри класса в классе @Configuration, почему он не поддерживает его в обычном компоненте?
I надеюсь с технической точки зрения понятно, почему.
Теперь мои мысли с нетехнической стороны:
Я думаю, что это не сделано, потому что Spring AOP здесь достаточно долго ...
Начиная с Spring Framework 5, была введена Spring WebFlux Framework.
В настоящее время Spring Team усердно работает над улучшением реактивного модель программирования
См. некоторые заметные последние сообщения в блоге :
Больше и больше возможностей для Внедрен менее подходящий подход к созданию приложений Spring.(см., например, этот коммит )
Так что я думаю, что, несмотря на то, что возможно сделать то, что вы описали, это далеко от приоритета Spring Team # 1 на данный момент