为了账号安全,请及时绑定邮箱和手机立即绑定

Spring 中的作用域代理是什么?

Spring 中的作用域代理是什么?

呼啦一阵风 2023-09-13 16:45:05
正如我们所知,Spring 使用代理来添加功能(@Transactional例如@Scheduled)。有两种选择 - 使用 JDK 动态代理(该类必须实现非空接口),或使用 CGLIB 代码生成器生成子类。我一直认为 proxyMode 允许我在 JDK 动态代理和 CGLIB 之间进行选择。但我能够创建一个例子来表明我的假设是错误的:情况1:单例:@Servicepublic class MyBeanA {    @Autowired    private MyBeanB myBeanB;    public void foo() {        System.out.println(myBeanB.getCounter());    }    public MyBeanB getMyBeanB() {        return myBeanB;    }}原型:@Service@Scope(value = "prototype")public class MyBeanB {    private static final AtomicLong COUNTER = new AtomicLong(0);    private Long index;    public MyBeanB() {        index = COUNTER.getAndIncrement();        System.out.println("constructor invocation:" + index);    }    @Transactional // just to force Spring to create a proxy    public long getCounter() {        return index;    }}主要的:MyBeanA beanA = context.getBean(MyBeanA.class);beanA.foo();beanA.foo();MyBeanB myBeanB = beanA.getMyBeanB();System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());输出:constructor invocation:000counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e在这里我们可以看到两件事:MyBeanB仅被实例化一次。为了添加 的@Transactional功能MyBeanB,Spring 使用了 CGLIB。案例2:让我纠正一下MyBeanB定义:@Service@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)public class MyBeanB {在这种情况下,输出是:constructor invocation:00constructor invocation:11constructor invocation:2counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2在这里我们可以看到两件事:MyBeanB被实例化3次。为了添加 的@Transactional功能MyBeanB,Spring 使用了 CGLIB。你能解释一下发生了什么事吗?代理模式到底如何工作?
查看完整描述

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版本,该版本知道如何区分代理定义和实际MyBeanBbean 定义。这将返回一个新MyBeanB实例(因为它是原型),Spring 将通过反射将方法调用委托给它(经典Method.invoke)。


您的第三个示例与第二个示例基本相同。


查看完整回答
反对 回复 2023-09-13
  • 1 回答
  • 0 关注
  • 111 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信