Spring Prototype范围和CDI依赖范围之间有什么区别?

时间:2013-11-18 02:34:51

标签: java spring java-ee dependency-injection cdi

Spring原型范围是否与CDI相关范围相同。

谷歌搜索引导我发布声称他们是相同的博客文章和其他声称他们相似但不完全相同而没有解释差异的人。

那么弹簧原型范围和cdi依赖范围之间有什么区别?

2 个答案:

答案 0 :(得分:5)

根据CDI documentationjavadoc

  

当声明bean具有@Dependent范围时:

     
      
  • 不会在多个之间共享bean的注入实例   注射点。
  •   
  • ...
  •   

同样,Spring documentation

  

非单例,bean部署的原型范围导致了   每次请求特定时,都会创建一个新的bean实例   豆是制作的。

他们在行为上是一样的。

我能找到的唯一区别是bean的生命周期。 In Spring

  

因此,尽管调用了初始化生命周期回调方法   所有对象,无论范围如何,在原型的情况下都配置为   不会调用销毁生命周期回调。客户端代码必须   清理原型范围的对象并释放昂贵的资源   原型豆正在举行。

In CDI however容器管理bean的整个生命周期,直接在它作为方法调用参数注入时或在销毁注入的bean时间接管理。这些条件都在链接的文档中描述。

正如Luiggi在评论中提到的,重要的是要注意bean声明的默认范围。 In the Spring docs state

  

单例范围是默认范围[...]

while in CDI, the default scope is dependent.

答案 1 :(得分:0)

我认为正确的答案是:这取决于使用的proxyMode

Spring的prototype范围在ScopedProxyMode.NO(通常等于ScopedProxyMode.DEFAULT,除非在组件扫描指令级别配置了不同的默认值)和{ {1}}(或ScopedProxyMode.TARGET_CLASS)。

在Spring中,当ScopedProxyMode.INTERFACESprototype声明了作用域bean时,其行为实际上与CDI的默认作用域ScopedProxyMode.NO几乎相同。唯一的区别是生命周期管理。在春季,“未调用已配置的销毁生命周期回调”,而CDI管理@Dependent bean的完整生命周期。

但是,当@Dependentprototype(或ScopedProxyMode.TARGET_CLASS)声明了范围的bean时,则在Spring中,它的行为与CDI的ScopedProxyMode.INTERFACES范围完全不同。 @Dependent bean不以任何方式绑定到注入它们的实例的生命周期。如果将此类原型bean注入到singleton bean中,则会代之以注入代理,并且每次调用任何代理方法时都会创建新的bean实例

这是什么意思?

看下面的代码:

prototype

一次调用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(); } } 方法将创建多少个myBean实例?你觉得一个吗?没有!将为一个控制器的方法调用创建MyController.hello()的三个实例。每次调用myBean时。一次myBean.doSomething();的调用将显示如下内容:

MyController.hello()

所有6行仅用于一个调用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!

它确实与CDI的MyController.hello()范围不同,在CDI的范围中,@Dependent的注入实例将为每个要注入的实例创建一次。它甚至与Spring的MyBean的{​​{1}}范围也不同。

例如,如果您创建另一个控制器prototype,其代码等于ScopedProxyMode.NO(请记住,Spring控制器始终具有MyController2的作用域)并将MyController的声明更改为:

singleton

然后,Spring将仅创建MyBean的两个实例-每个控制器一个。两者都将与控制器一起创建。在应用程序启动时,您将看到类似这样的内容:

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

每个调用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()

因此,从这个意义上讲,它实际上不是“原型” bean。它更像是没有范围的bean-它的范围完全取决于封装bean(控制器)的范围。就像CDI的MyBean范围一样。

请记住:默认情况下,以代理模式MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked! MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked! MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked! 创建的Spring @Dependent bean与prototype相同。

小心

通常,这不是您希望从名为ScopedProxyMode.DEFAULT的范围中获得的。还有其他方法可以将原型bean的新副本注入到范围更广的bean(即ScopedProxyMode.NO)中。其中之一:使用prototype

通过singleton,您可以在ObjectProvider中拥有原型bean,但可以将ObjectProvider而不是仅ScopedProxyMode.NO注入控制器。

这样,您可以使用控制器方法,该方法每次调用ObjectProvider<MyBean> myBeanProvider时都会获得MyBean myBean的新副本:

MyBean

此代码将打印:

myBeanProvider.getObject()

调用@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()的同一个实例中被调用了三次!

每次调用doSomething()方法时,都会创建 {}的另一个实例-每次调用MyBean时都会创建。

因此,基本规则:

  • 如果您需要在每次使用某种方法时懒惰地创建的Spring bean 在此bean上调用,请使用MyController.hello()MyBean
  • 如果您需要与封闭的Bean一起创建的Srping Bean(无论其范围如何),请使用myBeanProvider.getObject()
  • 如果您需要在决定创建它时延迟创建的Spring bean,请使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)并注入@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)而不是bean本身。