Spring 使用 @Configuration 或 @Component 注释的 类 的代理创建
Spring Proxy Creation of Classes annotated with @Configuration or @Component
Spring 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。如果 class 被 @Configuration 注释,则使用 CGLIB。
但是,Spring AOP 的一个限制是,一旦调用最终到达目标对象,它可能对自身进行的任何方法调用都将针对 this 引用进行调用,而不是针对代理。在使用 @Transactional
和其他地方时,记住这条信息很重要。
那么在下面的代码中,有了这些知识,Spring 就会注入 SimpleBean
的实际实例或代理?
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean()); //<---
}
}
如果 class 是带有 @Component
的注释,行为是什么?
让我给你换个角度。
假设有另一个 bean AnotherBeanConsumer
也需要一个 simpleBean
。 Simple Bean 有一个 Singleton 作用域:
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
@Bean
public AnotherBeanConsumer anotherBeanConsumer() {
return new AnotherBeanConsumer(simpleBean());
}
}
现在的问题是,从不同的方法 simpleBeanConsumer
和 anotherBeanConsumer
return 对 simpleBean()
的两次调用如何可能是同一个简单 bean 的实例(因为它显然是单身人士)?
IMO(免责声明,我不隶属于 spring 或其他组织),这是创建包装配置的代理的主要原因。
现在Spring AOP确实有调用方法的限制,就像你说的那样,但是谁说spring under-the-hood使用spring AOP?在低得多的级别上完成的字节码检测没有这样的限制。毕竟创建代理意味着:"create a proxy object that will have the same interface but will alter the behavior",对吧?
例如,如果您使用使用继承的 CGLIB,您可以从配置中创建一个代理,如下所示(示意图):
class CGLIB_GENERATED_PROXY extends Config {
private Map<String, Object> singletonBeans;
public SimpleBean simpleBean() {
String name = getNameFromMethodNameMaybePrecached();
if(singletonBeans.get(name) != null) {
return singletonBeans.get(name);
}
else {
SimpleBean bean = super.simpleBean();
singletonBeans.put(name, bean);
return bean;
}
}
....
}
当然这只是一个示意图,在现实生活中有一个应用上下文基本上提供了这样的地图访问,但你明白了。
如果这还不够,那么 spring 必须使用一些更复杂的框架来加载配置(如 ASM)...
这是一个例子:
如果您使用 @ConditionalOnClass(A.class)
并且 class 在运行时并不存在,那么 spring 如何加载使用此配置的配置的字节码并且不会在类似 [=18= 的情况下失败]?
我的观点是它远远超出了 spring AOP,并且有其怪癖 :)
话虽如此,我上面描述的任何内容都不需要真正的组件始终 包裹在任何类型的代理中。所以在最简单的情况下,当 SimpleBean
本身没有一些需要代理生成的注释(像 @Cached
、@Transactional
等等)时,Spring 不会如果不包装该类型的对象,您将得到一个普通的 SimpleBean
对象。
Spring 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。如果 class 被 @Configuration 注释,则使用 CGLIB。
但是,Spring AOP 的一个限制是,一旦调用最终到达目标对象,它可能对自身进行的任何方法调用都将针对 this 引用进行调用,而不是针对代理。在使用 @Transactional
和其他地方时,记住这条信息很重要。
那么在下面的代码中,有了这些知识,Spring 就会注入 SimpleBean
的实际实例或代理?
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean()); //<---
}
}
如果 class 是带有 @Component
的注释,行为是什么?
让我给你换个角度。
假设有另一个 bean AnotherBeanConsumer
也需要一个 simpleBean
。 Simple Bean 有一个 Singleton 作用域:
@Configuration
public class Config {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
@Bean
public AnotherBeanConsumer anotherBeanConsumer() {
return new AnotherBeanConsumer(simpleBean());
}
}
现在的问题是,从不同的方法 simpleBeanConsumer
和 anotherBeanConsumer
return 对 simpleBean()
的两次调用如何可能是同一个简单 bean 的实例(因为它显然是单身人士)?
IMO(免责声明,我不隶属于 spring 或其他组织),这是创建包装配置的代理的主要原因。
现在Spring AOP确实有调用方法的限制,就像你说的那样,但是谁说spring under-the-hood使用spring AOP?在低得多的级别上完成的字节码检测没有这样的限制。毕竟创建代理意味着:"create a proxy object that will have the same interface but will alter the behavior",对吧?
例如,如果您使用使用继承的 CGLIB,您可以从配置中创建一个代理,如下所示(示意图):
class CGLIB_GENERATED_PROXY extends Config {
private Map<String, Object> singletonBeans;
public SimpleBean simpleBean() {
String name = getNameFromMethodNameMaybePrecached();
if(singletonBeans.get(name) != null) {
return singletonBeans.get(name);
}
else {
SimpleBean bean = super.simpleBean();
singletonBeans.put(name, bean);
return bean;
}
}
....
}
当然这只是一个示意图,在现实生活中有一个应用上下文基本上提供了这样的地图访问,但你明白了。
如果这还不够,那么 spring 必须使用一些更复杂的框架来加载配置(如 ASM)...
这是一个例子:
如果您使用 @ConditionalOnClass(A.class)
并且 class 在运行时并不存在,那么 spring 如何加载使用此配置的配置的字节码并且不会在类似 [=18= 的情况下失败]?
我的观点是它远远超出了 spring AOP,并且有其怪癖 :)
话虽如此,我上面描述的任何内容都不需要真正的组件始终 包裹在任何类型的代理中。所以在最简单的情况下,当 SimpleBean
本身没有一些需要代理生成的注释(像 @Cached
、@Transactional
等等)时,Spring 不会如果不包装该类型的对象,您将得到一个普通的 SimpleBean
对象。