当前位置: 首页 > 知识库问答 >
问题:

@configuration与@component类中的自调用行为

夏侯华彩
2023-03-14

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

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

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

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

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

共有1个答案

葛玉堂
2023-03-14

关于普通Spring(AOP)代理或动态代理(JDK,CGLIB)的一般工作原理的解释,请参阅我的另一个答案和示例代码。首先阅读这篇文章,您就会理解为什么不能通过Spring AOP拦截这些类型的代理的自调用。

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

我的一个配置类如下所示:

java prettyprint-override">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()中有多次调用。为什么?

如果在MyTestBean()中的一个MyInterfaceWithDefaultMethod()调用上放置一个断点,并让调试器停止,则可以了解更多信息。然后您可以通过评估代码来检查情况:

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

代理实现任何接口吗?

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

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

好吧,有意思。让我们读一些Javadoc。实际上类ConfigurationClassEnhancer是包范围的,因此我们必须读取源代码内部的Javadoc:

通过生成一个CGLIB子类来增强配置类,该子类与Spring容器交互,以尊重@bean方法的bean作用域语义。每个这样的@bean方法都将在生成的子类中被重写,只有在容器实际请求构造新实例时才委托给实际的@bean方法实现。否则,对这种@bean方法的调用将作为返回容器的引用,通过名称获得相应的bean。

内部接口enhancedconfiguration实际上是公共的,但Javadoc仍然只在源代码中:

要由所有@Configuration CGLIB子类实现的标记接口。通过检查候选类是否已经可分配给它,例如已经被增强,来促进增强的幂等行为。还扩展了BeanFactoryAware,因为所有增强的@Configuration类都需要访问创建它们的BeanFactory。

请注意,该接口仅用于框架内部使用,但必须保持公共,以便允许访问从其他包(即用户代码)生成的子类。

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

增强@bean方法以检查提供的BeanFactory是否存在此bean对象。

在那里你可以看到其他的神奇的发生,但是描述真的会超出这个已经很长的答案的范围。

 类似资料:
  • 本文向大家介绍Spring @Configuration和@Component的区别,包括了Spring @Configuration和@Component的区别的使用技巧和注意事项,需要的朋友参考一下 Spring @Configuration 和 @Component 区别 一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同

  • 问题内容: 我遇到了Spring 3提供的两个注释(@Component和@Configuration),我对它们之间有些困惑。 这是我读到的有关@Component的内容 将此“ context:component”放入bean配置文件中,这意味着在Spring中启用自动扫描功能。基本包指示组件的存储位置,Spring将扫描此文件夹并找出Bean(用@Component注释)并将其注册在Spri

  • 我遇到了Spring 3提供的两个注释(@Component和@Configuration),我对它们有点困惑。 这是我读到的关于的内容 将这个“context:component”放在bean配置文件中,这意味着在Spring中启用自动扫描特性。基础包指明了你的组件存储在哪里,Spring将扫描这个文件夹,找到bean(用@Component注释)并在Spring容器中注册它。 所以我想知道<c

  • 问题内容: 我们正在重用一个使用spring java- config(使用@Configuration)定义其bean的项目,并且在一个此类中有一个init方法。 这里的预期行为是什么?何时调用此方法?关于豆子,那就是。即,此方法的行为是否完全像配置类是Bean一样(实际上是一个吗?) 我们观察到的是,根据操作系统的不同,可以在初始化进入配置类的Bean之前调用它,从而最终导致不完全依赖项的工作

  • 我在spring 4.0.3配置中支持以下Java,它们有两个DataSource和JdbcTemplate bean: ...@Bean public JdbcTemplate internalJDBCTemplate(){return new JdbcTemplate(internalDataSource());} 我有其他配置bean类,它们自动连接第一个配置并调用internalDataS

  • 我正在尝试在Configuration类中自动生成一个类,如下所示: [编辑]在我的日志中没有例外,因为如果该对象为空,将不会使用该对象。下面是我在日志文件中看到的日志条目: [编辑]这里是主类--