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

标签 spring spring-aop spring-transactions cglib dynamic-proxy

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

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

    @Transactional
    public void method2() {}
}

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

同时,对于 Configuration 类,通常我们应该有相同的行为:

@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 @Component s
有关普通 Spring (AOP) 代理或动态代理(JDK、CGLIB)在一般情况下如何工作的说明,请参阅 my other answer 和示例代码。首先阅读它,你就会明白为什么不能通过 Spring AOP 拦截这些类型的代理的自调用。@Configuration
至于 @Configuration 类,它们的工作方式不同。为了避免已经创建的 Spring bean 仅仅因为它们的 @Bean 工厂方法被再次外部或内部调用而再次创建,Spring 为它们创建了特殊的 CGLIB 代理。
我的配置类之一如下所示:

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$$8b4ed8a@72456279
运行应用程序时,即使 myInterfaceWithDefaultMethod() 内有多次调用,也不会多次调用方法 myTestBean() 。为什么?
如果您在 myInterfaceWithDefaultMethod() 中的 myTestBean() 调用之一上放置断点并让调试器在那里停止,您将了解更多信息。然后你可以通过评估代码来检查情况:
System.out.println(this);

spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a@72456279
所以 config 类确实是一个 CGLIB 代理。但是它有哪些方法呢?
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()
这看起来有点乱,让我们打印方法名称:
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
该代理是否实现了任何接口(interface)?
for (Class<?> implementedInterface : this.getClass().getInterfaces()) {
  System.out.println(implementedInterface);
}

interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration
不错,有意思。让我们阅读一些 Javadoc。实际上 ConfigurationClassEnhancer 类是包范围的,所以我们必须阅读 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.


内部接口(interface) EnhancedConfiguration 实际上是公开的,但 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.


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

关于spring - @Configuration 与 @Component 类中的自调用行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56917484/

相关文章:

java - 如何仅针对特定 url 额外添加 Spring Security 验证码过滤器

java - Spring 响应实体

spring - 如何在冒充其他用户后获得原始用户?

spring-mvc - 如何在 Spring MVC 中的 @controllerAdvice 或 @RestControllerAdvice 中找到 Controller 名称?

使用 @transactional 注释的 Spring Boot 事务支持不适用于 mongoDB,有人有解决方案吗?

spring - 许多缓存在 spring 中的缺点

java - 如何通过将切入点作为用户的输入来将 spring aop 应用于遗留代码

java - 在 AOP 中为 @Async 方法处理异常时丢失 HttpServletRequest 对象

java - 没有回滚的 Spring Transactional

postgresql - 使用带有存储库的 Spring 4 data jpa 增加或减少计数器值的预期方法是什么?