My question is about AOP Spring behaviour in case of internal method calls.
@Service
class Service {
@Transactional
public void method1() {
method2();
}
@Transactional
public void method2() {}
}
If we call method1() from outside, method1() will be executed in a transaction mode, but as it calls internally method2(), the code inside method2() will not be executed in a transaction mode.
In parallel, for a Configuration class, normally we should have the same behaviour:
@Configuration
class MyConfiguration{
@Bean
public Object1 bean1() {
return new Object1();
}
@Bean
public Object1 bean2() {
Object1 b1 = bean1();
return new Object2(b1);
}
}
Normally if i understood well, the call call to bean1() method from bean2() should not be intercepted by the proxy object and hence, if we call bean1() many times, we should get different object every time.
Firstly, could you explains technically why the inner calls are not intercepted by the proxy object, and secondly to check if my understanding of the second example is correct.
@Component
sFor an explanation of how normal Spring (AOP) proxies or dynamic proxies (JDK, CGLIB) in general work, see my other answer with illustrative sample code. Read that first and you will understand why self-invocation cannot be intercepted for these types of proxies via Spring AOP.
@Configuration
classesAs for @Configuration
classes, they work differently. In order to avoid Spring beans which have already been created from being created again just because their @Bean
factory methods are being called again ex- or internally, Spring creates special CGLIB proxies for them.
One of my config classes looks like this:
package spring.aop;
import org.springframework.context.annotation.*;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ApplicationConfig {
@Bean(name = "myInterfaceWDM")
public MyInterfaceWithDefaultMethod myInterfaceWithDefaultMethod() {
MyClassImplementingInterfaceWithDefaultMethod myBean = new MyClassImplementingInterfaceWithDefaultMethod();
System.out.println("Creating bean: " + myBean);
return myBean;
}
@Bean(name = "myTestBean")
public Object myTestBean() {
System.out.println(this);
myInterfaceWithDefaultMethod();
myInterfaceWithDefaultMethod();
return myInterfaceWithDefaultMethod();
}
}
The corresponding application looks like this:
package spring.aop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args);
MyInterfaceWithDefaultMethod myInterfaceWithDefaultMethod =
(MyInterfaceWithDefaultMethod) appContext.getBean("myInterfaceWDM");
System.out.println(appContext.getBean("myTestBean"));
}
}
This prints (edited to remove stuff we don't want to see):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.2.RELEASE)
2019-07-07 08:37:55.750 INFO 22656 --- [ main] spring.aop.DemoApplication : Starting DemoApplication on (...)
(...)
Creating bean: spring.aop.MyClassImplementingInterfaceWithDefaultMethod@7173ae5b
spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a@72456279
When running the application, method myInterfaceWithDefaultMethod()
is not called multiple times even though there are multiple calls from within myTestBean()
. Why?
You learn more if you put a breakpoint onto one of the myInterfaceWithDefaultMethod()
calls within myTestBean()
and let the debugger stop there. Then you can inspect the situation by evaluating code:
System.out.println(this);
spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a@72456279
So the config class is indeed a CGLIB proxy. But what methods does it have?
for (Method method: this.getClass().getDeclaredMethods()) {
System.out.println(method);
}
public final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.myTestBean()
public final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.myInterfaceWithDefaultMethod()
public final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.setBeanFactory(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$myInterfaceWithDefaultMethod$1()
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$SET_THREAD_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$SET_STATIC_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static org.springframework.cglib.proxy.MethodProxy spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$findMethodProxy(org.springframework.cglib.core.Signature)
final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$setBeanFactory$6(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$STATICHOOK4()
private static final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$BIND_CALLBACKS(java.lang.Object)
final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$myTestBean$0()
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$STATICHOOK3()
This looks kinda messy, let's just print the method names:
for (Method method: this.getClass().getDeclaredMethods()) {
System.out.println(method.name);
}
myTestBean
myInterfaceWithDefaultMethod
setBeanFactory
CGLIB$myInterfaceWithDefaultMethod$1
CGLIB$SET_THREAD_CALLBACKS
CGLIB$SET_STATIC_CALLBACKS
CGLIB$findMethodProxy
CGLIB$setBeanFactory$6
CGLIB$STATICHOOK4
CGLIB$BIND_CALLBACKS
CGLIB$myTestBean$0
CGLIB$STATICHOOK3
Does that proxy implement any interfaces?
for (Class<?> implementedInterface : this.getClass().getInterfaces()) {
System.out.println(implementedInterface);
}
interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration
Okay, interesting. Let us read some Javadoc. Actually class ConfigurationClassEnhancer
is package-scoped, so we have to read the Javadoc right inside the source code:
Enhances Configuration classes by generating a CGLIB subclass which interacts with the Spring container to respect bean scoping semantics for @Bean methods. Each such @Bean method will be overridden in the generated subclass, only delegating to the actual @Bean method implementation if the container actually requests the construction of a new instance. Otherwise, a call to such an @Bean method serves as a reference back to the container, obtaining the corresponding bean by name.
The inner interface EnhancedConfiguration
is actually public, but still the Javadoc is again only in the source code:
Marker interface to be implemented by all @Configuration CGLIB subclasses. Facilitates idempotent behavior for enhance through checking to see if candidate classes are already assignable to it, e.g. have already been enhanced. Also extends BeanFactoryAware, as all enhanced @Configuration classes require access to the BeanFactory that created them.
Note that this interface is intended for framework-internal use only, however must remain public in order to allow access to subclasses generated from other packages (i.e. user code).
Now what do we see if we step into the myInterfaceWithDefaultMethod()
call? The generated proxy method calls method ConfigurationClassEnhancer.BeanMethodInterceptor.intercept(..)
and that method's Javadoc says:
Enhance a @Bean method to check the supplied BeanFactory for the existence of this bean object.
There you can see the rest of the magic happening, but the description would really be out of scope of this already lengthy answer.