@Configuration 与 @Component 中的自调用行为 类

Self-invocation behaviour in @Configuration vs. @Component classes

我的问题是关于 AOP Spring 在内部方法调用的情况下的行为。

@Service
class Service {
    @Transactional
    public void method1() {
        method1();
    }

    @Transactional
    public void method2() {}
}

如果我们从外部调用method1(),method1()会以事务方式执行,但由于是在内部调用method2(),method2()里面的代码不会以事务方式执行。

同时,对于配置 class,通常我们应该有相同的行为:

@Configuration
class MyConfiguration{
    @Bean
    public Object1 bean1() {
        return new Object1();
    }

    @Bean
    public Object1 bean2() { 
        Object1 b1 = bean1();
        return new Object2(b1);
    }
}

通常情况下,如果我理解得很好,bean2() 对 bean1() 方法的调用不应该被代理对象拦截,因此,如果我们多次调用 bean1(),我们应该每次都得到不同的对象.

首先,您能否从技术上解释为什么内部调用没有被代理对象拦截,其次检查我对第二个示例的理解是否正确。

常规 Spring @Components

有关正常 Spring (AOP) 代理或动态代理(JDK、CGLIB)在一般工作中的解释,请参阅 和说明性示例代码。先读一下,你就会明白为什么 self-invocation 不能通过 Spring AOP 拦截这些类型的代理。

@Configuration classes

至于 @Configuration classes,它们的工作方式不同。为了避免已经创建的 Spring beans 仅仅因为它们的 @Bean 工厂方法在外部或内部再次被调用而被再次创建,Spring 为它们创建了特殊的 CGLIB 代理。

我的一个配置 classes 看起来像这样:

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();
  }
}

相应的应用程序如下所示:

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"));
    }
}

这将打印(编辑以删除我们不想看到的内容):

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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$b4ed8a@72456279

当 运行 应用程序时,即使在 myTestBean() 中有多次调用,方法 myInterfaceWithDefaultMethod() 也不会被多次调用。为什么?

如果在 myTestBean() 中的 myInterfaceWithDefaultMethod() 调用之一上放置断点并让调试器停在那里,您会学到更多。然后你可以通过评估代码来检查情况:

System.out.println(this);

spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a@72456279

所以配置 class 确实是一个 CGLIB 代理。但是它有什么方法呢?

for (Method method: this.getClass().getDeclaredMethods()) {
  System.out.println(method);
}

public final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.myTestBean()
public final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.myInterfaceWithDefaultMethod()
public final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.setBeanFactory(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$myInterfaceWithDefaultMethod()
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$SET_THREAD_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$SET_STATIC_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static org.springframework.cglib.proxy.MethodProxy spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$findMethodProxy(org.springframework.cglib.core.Signature)
final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$setBeanFactory(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$STATICHOOK4()
private static final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$BIND_CALLBACKS(java.lang.Object)
final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$myTestBean[=14=]()
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$b4ed8a.CGLIB$STATICHOOK3()

这看起来有点乱,让我们打印方法名称:

for (Method method: this.getClass().getDeclaredMethods()) {
  System.out.println(method.name);
}

myTestBean
myInterfaceWithDefaultMethod
setBeanFactory
CGLIB$myInterfaceWithDefaultMethod
CGLIB$SET_THREAD_CALLBACKS
CGLIB$SET_STATIC_CALLBACKS
CGLIB$findMethodProxy
CGLIB$setBeanFactory
CGLIB$STATICHOOK4
CGLIB$BIND_CALLBACKS
CGLIB$myTestBean[=15=]
CGLIB$STATICHOOK3

该代理是否实现任何接口?

for (Class<?> implementedInterface : this.getClass().getInterfaces()) {
  System.out.println(implementedInterface);
}

interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration

好的,很有趣。让我们阅读一些 Javadoc。实际上 class ConfigurationClassEnhancer 是 package-scoped,所以我们必须阅读 source code:

中的 Javadoc

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.

内部接口 EnhancedConfiguration 实际上是 public,但 Javadoc 仍然只在 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).

现在,如果我们进入 myInterfaceWithDefaultMethod() 调用,我们会看到什么?生成的代理方法调用方法 ConfigurationClassEnhancer.BeanMethodInterceptor.intercept(..) 并且该方法的 Javadoc 表示:

Enhance a @Bean method to check the supplied BeanFactory for the existence of this bean object.

在那里你可以看到其余的魔法正在发生,但描述确实超出了这个已经很长的答案的范围。