@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()内部的代码将不会在事务模式下执行.
并行地,对于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>
首先,您能否从技术上解释为什么内部调用未被代理对象拦截,其次,请检查我对第二个示例的理解是否正确.
常规Spring @Component
s
有关一般情况下正常的Spring(AOP)代理或动态代理(JDK,CGLIB)如何工作的说明,请参见我的其他答案和示例代码.首先阅读该书,您将了解为什么无法通过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
运行应用程序时,即使myTestBean()
内部有多个调用,也不会多次调用方法myInterfaceWithDefaultMethod()
.为什么?
如果在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
是包作用域的,因此我们必须在源代码:
Marker接口将由所有@Configuration CGLIB子类实现.通过检查是否已经向其分配候选类来促进幂等行为以增强功能.已经得到增强. 还扩展了BeanFactoryAware,因为所有增强的@Configuration类都需要访问创建它们的BeanFactory.
请注意,此接口仅供框架内部使用,但必须保持公共状态,以允许访问从其他程序包生成的子类(即用户代码).
现在,如果我们进入 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: 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. 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. As for One of my config classes looks like this: The corresponding application looks like this: This prints (edited to remove stuff we don't want to see): When running the application, method You learn more if you put a breakpoint onto one of the So the config class is indeed a CGLIB proxy. But what methods does it have? This looks kinda messy, let's just print the method names: Does that proxy implement any interfaces? Okay, interesting. Let us read some Javadoc. Actually class 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 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 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. I hope this helps. 这篇关于@Configuration与@Component类中的自调用行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!myInterfaceWithDefaultMethod()
通话,我们会看到什么?生成的代理方法调用方法ConfigurationClassEnhancer.BeanMethodInterceptor.intercept(..)
,并且该方法的@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);
}
}
Regular Spring
@Component
s@Configuration
classes@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.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()
is not called multiple times even though there are multiple calls from within myTestBean()
. Why?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
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
ConfigurationClassEnhancer
is package-scoped, so we have to read the Javadoc right inside the source code:
EnhancedConfiguration
is actually public, but still the Javadoc is again only in the source code:
myInterfaceWithDefaultMethod()
call? The generated proxy method calls method ConfigurationClassEnhancer.BeanMethodInterceptor.intercept(..)
and that method's Javadoc says: