1 回答

TA贡献1796条经验 获得超7个赞
为行为生成的代理@Transactional
与作用域代理具有不同的用途。
代理@Transactional
是一种包装特定 bean 以添加会话管理行为的代理。所有方法调用都将在委托给实际 bean 之前和之后执行事务管理。
如果你举例说明的话,它看起来像
main -> getCounter -> (cglib-proxy -> MyBeanB)
出于我们的目的,您基本上可以忽略它的行为(删除@Transactional
后您应该看到相同的行为,除非您没有 cglib 代理)。
代理@Scope
的行为有所不同。文档指出:
[...]您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)检索真实目标对象,并将方法调用委托给真实对象。
Spring 真正做的是为代表代理的工厂类型创建一个单例 bean 定义。然而,相应的代理对象会在每次调用时查询上下文以获取实际的 bean。
如果你举例说明的话,它看起来像
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
由于MyBeanB
是原型 bean,上下文将始终返回一个新实例。
出于本答案的目的,假设您MyBeanB
直接使用
MyBeanB beanB = context.getBean(MyBeanB.class);
这本质上就是 Spring 为满足@Autowired
注入目标所做的事情。
在你的第一个例子中,
@Service
@Scope(value = "prototype")
public class MyBeanB {
您声明原型 bean定义(通过注释)。@Scope
有一个proxyMode
元素
指定组件是否应配置为作用域代理,如果是,则代理是否应基于接口或基于子类。
默认为
ScopedProxyMode.DEFAULT
,这通常表示不应创建作用域代理,除非在组件扫描指令级别配置了不同的默认值。
因此 Spring 不会为生成的 bean 创建作用域代理。您可以使用以下命令检索该 bean
MyBeanB beanB = context.getBean(MyBeanB.class);
您现在拥有对 Spring 创建的新对象的引用MyBeanB
。这与任何其他 Java 对象一样,方法调用将直接转到引用的实例。
如果再次使用getBean(MyBeanB.class)
,Spring 将返回一个新实例,因为 bean 定义是针对原型 bean的。您没有这样做,因此所有方法调用都会转到同一个对象。
在你的第二个例子中,
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
您声明一个通过 cglib 实现的作用域代理。当从 Spring 请求这种类型的 bean 时
MyBeanB beanB = context.getBean(MyBeanB.class);
Spring知道这MyBeanB
是一个作用域代理,因此返回一个满足API的代理对象MyBeanB
(即实现其所有公共方法),该对象内部知道如何MyBeanB
为每个方法调用检索实际的bean类型。
尝试跑步
System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));
这将返回true
暗示 Spring 返回一个单例代理对象(而不是原型 bean)的事实。
在代理实现内部的方法调用上,Spring 将使用一个特殊getBean
版本,该版本知道如何区分代理定义和实际MyBeanB
bean 定义。这将返回一个新MyBeanB
实例(因为它是原型),Spring 将通过反射将方法调用委托给它(经典Method.invoke
)。
您的第三个示例与第二个示例基本相同。
添加回答
举报