Is it using different types of dynamic proxies?
Almost exactly
Let's figure out what's the difference between @Configuration
classes and AOP proxies answering the following questions:
- Why self-invoked
@Transactional
method has no transactional semantics even though Spring is capable of intercepting self-invoked methods?
- How
@Configuration
and AOP are related?
Why self-invoked @Transactional
method has no transactional semantics?
Short answer:
This is how AOP made.
Long answer:
- Declarative transaction management relies on AOP (for the majority of Spring applications on Spring AOP)
The Spring Framework’s declarative transaction management is made possible with Spring aspect-oriented programming (AOP)
- It is proxy-based (§5.8.1. Understanding AOP Proxies)
Spring AOP is proxy-based.
From the same paragraph 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...
}
}
And a snippet proxying it:
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();
}
}
The key thing to understand here is that the client code inside the main(..)
method of the Main
class has a reference to the proxy.
This means that method calls on that object reference are calls on the proxy.
As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call.
However, once the call has finally reached the target object (the SimplePojo
, reference in this case), any method calls that it may make on itself, such as this.bar()
or this.foo()
, are going to be invoked against the this
reference, and not the proxy.
This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
(Key parts are emphasized.)
You may think that aop works as follows:
Imagine we have a Foo
class which we want to proxy:
Foo.java
:
public class Foo {
public int getInt() {
return 42;
}
}
There is nothing special. Just getInt
method returning 42
An interceptor:
Interceptor.java
:
public interface Interceptor {
Object invoke(InterceptingFoo interceptingFoo);
}
LogInterceptor.java
(for demonstration):
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");
}
}
}
Finally 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--;
}
}
}
Wiring everything together:
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();
}
Will print:
log. before
Invoking target
Target returned 42
Invoked target
log. after
log. before
Invoking target
Target returned 42
Invoked target
log. after
Now let's take a look at ReflectiveMethodInvocation
.
Here is a part of its proceed
method:
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
++this.currentInterceptorIndex
should look familiar now
You may try introducing several aspects into your application and see the stack growing at the proceed
method when advised method is invoked
Finally everything ends up at MethodProxy.
From its invoke
method javadoc:
Invoke the original method, on a different object of the same type.
And as I mentioned previously documentation:
once the call has finally reached the target
object any method calls that it may make on itself are going to be invoked against the this
reference, and not the proxy
I hope now, more or less, it's clear why.
How @Configuration
and AOP are related?
The answer is they are not related.
So Spring here is free to do whatever it wants. Here it is not tied to the proxy AOP semantics.
It enhances such classes using ConfigurationClassEnhancer
.
Take a look at:
Returning to the question
If Spring can successfully intercept intra class function calls in a @Configuration class, why does it not support it in a regular bean?
I hope from technical point of view it is clear why.
Now my thoughts from non-technical side:
I think it is not done because Spring AOP is here long enough...
Since Spring Framework 5 the Spring WebFlux framework has been introduced.
Currently Spring Team is working hard towards enhancing reactive programming model
See some notable recent blog posts:
More and more features towards less-proxying approach of building Spring applications are introduced. (see this commit for example)
So I think that even though it might be possible to do what you've described it is far from Spring Team's #1 priority for now