簡體   English   中英

Spring Prototype范圍和CDI依賴范圍之間有什么區別?

[英]What is the difference between Spring Prototype scope and the CDI Dependent Scope?

Spring原型范圍是否與CDI相關范圍相同。

谷歌搜索引導我到博客帖子聲稱他們是相同的和其他聲稱他們相似但不完全相同而沒有解釋差異。

那么彈簧原型范圍和cdi依賴范圍之間有什么區別?

根據CDI 文檔javadoc

當bean聲明具有@Dependent范圍時:

  • 在多個注入點之間不共享注入的bean實例。
  • ...

同樣, Spring文檔說明

bean的非單例原型范圍部署導致每次發出對該特定bean的請求時都會創建一個新的bean實例。

它們在行為上是一樣的。

我能找到的唯一區別是bean的生命周期。 在春天

因此,盡管無論范圍如何都在所有對象上調用初始化生命周期回調方法,但在原型的情況下,不會調用已配置的銷毀生命周期回調。 客戶端代碼必須清理原型范圍的對象並釋放原型bean所持有的昂貴資源。

然而在CDI中 ,容器管理bean的整個生命周期,直接在它作為方法調用參數注入時或在銷毀注入的bean時間接管理。 這些條件都在鏈接的文檔中描述。

正如Luiggi在評論中提到的那樣,重要的是要注意bean聲明的默認范圍。 在Spring docs狀態中

單例范圍是默認范圍[...]

在CDI中,默認范圍是dependent

我認為正確的答案是: 這取決於使用的proxyMode

Spring的prototype范圍在ScopedProxyMode.NO (通常等於ScopedProxyMode.DEFAULT ,除非在組件掃描指令級別配置了不同的默認值)和ScopedProxyMode.TARGET_CLASS (或ScopedProxyMode.INTERFACES )中完全不同。

在Spring中,當使用ScopedProxyMode.NO聲明prototype范圍的bean時,它的行為實際上與CDI的默認范圍@Dependent幾乎相同。 唯一的區別是生命周期管理。 在Spring中,“未調用已配置的銷毀生命周期回調”,而CDI管理@Dependent bean的完整生命周期。

但是,當使用ScopedProxyMode.TARGET_CLASS (或ScopedProxyMode.INTERFACES )聲明prototype范圍的bean時,那么在Spring中它的行為與CDI的@Dependent范圍完全不同。 prototype bean不會以任何方式綁定到注入它們的實例的生命周期。 如果將這樣的原型bean注入到singleton bean中,則會注入代理, 每次調用任何代理方法時都會創建新的bean實例!

這意味着什么?

看看這段代碼:

public static class MyBean {
    public MyBean() {
        System.out.println(MyBean.class.getSimpleName() + " " + this + " created!");
    }
    public void doSomething() {
        System.out.println(MyBean.class.getSimpleName() + " " + this + " doSomething() invoked!");
    }
}

@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyBean myBean() {
    return new MyBean();
}

@RestController
public static class MyController {
    @Autowired
    private MyBean myBean;

    @GetMapping("/hello")
    public String hello() {
        myBean.doSomething();
        myBean.doSomething();
        myBean.doSomething();
        return "Hello from " + MyController.class.getSimpleName();
    }
}

一次調用MyController.hello()方法會創建多少個不同的myBean實例? 你覺得嗎? 沒有! 將為一個控制器的方法調用創建三個myBean實例。 每次當myBean.doSomething(); 叫做。 MyController.hello()一次調用將打印如下內容:

MyBean demo.DemoApplication$MyBean@30853c6d created!
MyBean demo.DemoApplication$MyBean@30853c6d doSomething() invoked!
MyBean demo.DemoApplication$MyBean@a0b81cf created!
MyBean demo.DemoApplication$MyBean@a0b81cf doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4caa7eb1 created!
MyBean demo.DemoApplication$MyBean@4caa7eb1 doSomething() invoked!

所有6行只是一次調用MyController.hello()

它與CDI的@Dependent范圍完全不同,其中注入的MyBean實例將僅為其注入的每個實例創建一次。 即使是Spring的prototype范圍與ScopedProxyMode.NO也不同。

例如,如果你創建另一個控制器MyController2 ,代碼等於MyController (記住,Spring控制器總是有singleton范圍)並將MyBean聲明更改為:

@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)
public MyBean myBean() {
    return new MyBean();
}

那么Spring只會創建兩個MyBean實例 - 每個控制器一個。 兩者都將與控制器一起熱切地創建。 您將在應用程序啟動時看到類似的內容:

MyBean demo.DemoApplication$MyBean@4e1800d0 created!
MyBean demo.DemoApplication$MyBean@5e5cedf8 created!

每次調用MyController.hello()方法時都是這樣的(每次都是相同的MyBean實例):

MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!

每次調用MyController2.hello()方法時都是這樣的(每次都是相同的MyBean實例):

MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!

所以在這種情況下它並不是真正的“原型”bean。 它更像是沒有范圍的bean - 它的范圍完全取決於封閉bean(控制器)的范圍。 就像CDI的@Dependent范圍一樣。

並記住:默認情況下,Spring代理模式ScopedProxyMode.DEFAULT創建的prototype bean,與ScopedProxyMode.NO相同。

小心

通常這不是您對范圍命名prototype期望。 還有其他方法可以將原型bean的新副本注入更廣泛范圍的bean(即singleton )。 其中之一:使用ObjectProvider

使用ObjectProvider您可以在ScopedProxyMode.NO擁有原型bean,但是將ObjectProvider<MyBean> myBeanProvider而不僅僅是MyBean myBean注入到控制器中。

這樣,您可以使用controller方法,每次調用myBeanProvider.getObject()時都會獲取MyBean的全新副本:

@RestController
public static class MyController {
    @Autowired
    private ObjectProvider<MyBean> myBeanProvider;

    @GetMapping("/hello")
    public String hello() {
        MyBean myBean = myBeanProvider.getObject();
        myBean.doSomething();
        myBean.doSomething();
        myBean.doSomething();
        return "Hello from " + MyController.class.getSimpleName();
    }
}

此代碼將打印:

MyBean demo.DemoApplication$MyBean@52689e05 created!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!

在調用MyController.hello()方法時。

注意: 在同一個MyBean實例上調用了doSomething()三次!

在每次調用MyController.hello()方法時,都會創建另一個 MyBean實例 - 每次調用myBeanProvider.getObject()

所以,基本規則:

  • 如果你需要每次在這個bean上調用某個方法時懶惰創建的Spring bean,請使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
  • 如果你需要一個與它的封閉bean一起創建的Srping bean(無論它有什么范圍),請使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)
  • 如果你需要,當決定創建它是懶洋洋地創建一個Spring bean,使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)並注入ObjectProvider而不是bean本身的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM