亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

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 關注
  • 148 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號