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

时间:2019-07-06 20:34:01

标签: 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(),则每次都应获得不同的对象。 / p>

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

1 个答案:

答案 0 :(得分:1)

常规弹簧@Component s

有关一般情况下正常的Spring(AOP)代理或动态代理(JDK,CGLIB)如何工作的说明,请参见my other answer及其示例代码。首先阅读该书,您将了解为什么无法通过Spring AOP拦截这些类型的代理的自调用。

@Configuration

对于@Configuration类,它们的工作方式不同。为了避免仅由于在外部或内部再次调用其@Bean工厂方法而再次创建已创建的Spring 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

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

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

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

好的,很有趣。让我们阅读一些Javadoc。实际上,类ConfigurationClassEnhancer是包范围的,因此我们必须在source code内阅读Javadoc:

  

通过生成一个CGLIB子类来增强配置类,该子类与Spring容器进行交互,以遵守@Bean方法的bean作用域语义。 在生成的子类中将覆盖每个此类@Bean方法,仅在容器实际请求构造新实例时才委托实际的@Bean方法实现。否则,将调用此类@Bean方法用作对容器的引用,并按名称获取相应的bean。

内部接口EnhancedConfiguration实际上是公共的,但Javadoc仍然只在source code中:

  

Marker接口将由所有@Configuration CGLIB子类实现。通过检查是否已经向其分配候选类来促进幂等行为以增强功能。已经得到增强。   还扩展了BeanFactoryAware,因为所有增强的@Configuration类都需要访问创建它们的BeanFactory。

     

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

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

  

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

您可以看到其余的魔术发生了,但是描述确实超出了本已冗长答案的范围。

我希望这会有所帮助。