6

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.

diziaq
  • 6,881
  • 16
  • 54
  • 96
xmen-5
  • 1,806
  • 1
  • 23
  • 44
  • Hello, how about some feedback? I invested a lot of time in debugging Spring for you and documenting my findings. – kriegaex Jul 11 '19 at 07:50
  • Sorry, for not responding to you. firstable than you for your time. secondly I though that I will take time to read your responses as your anwser has other discussions to read. – xmen-5 Jul 11 '19 at 09:04

1 Answers1

10

Regular Spring @Components

For 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 classes

As 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.

halfer
  • 19,824
  • 17
  • 99
  • 186
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • In your first discussion, when you explained how Jdk proxy works and why we can't make inner calls, in the documentation part that you gave, they mention that it is possible to alter this behaviour by dependenig on the AopContext to get a reference to the proxy object. Indeed, for the @Configuration class, they alter this behaviour in some other way. Is that correct. – xmen-5 Jul 11 '19 at 09:51
  • 2
    The Spring manual itself calls coupling your application class to Spring AOP "horrendous" because it is. The whole idea of AOP is that the application code has no knowledge of any aspects enhancing its behaviour. I see no connection to `@Configuration` because the Spring framework does some very special things there, thus my pointers to the source code. This is internal behaviour and not to be used by normal users and thus the details are not publicly documented. The hints I quoted from the Javadoc + the source code contain the truth. – kriegaex Jul 11 '19 at 10:19
  • The same section of the Spring manual which explains the self-invocation issue with Spring AOP also tells you that you can just use full AspectJ in order to avoid that problem. AspectJ does not use proxies, thus does not have their problems or limitations. – kriegaex Jul 11 '19 at 10:21