繁体   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