简体   繁体   English

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

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

Is the Spring prototype scope the same as the CDI dependant scope. Spring原型范围是否与CDI相关范围相同。

Googling lead me to blog posts that claimed they are the same and others that claimed that they are similar but not quite the same without explaining the differences. 谷歌搜索引导我到博客帖子声称他们是相同的和其他声称他们相似但不完全相同而没有解释差异。

So what are the differences between the spring prototype scope and the cdi dependant scope? 那么弹簧原型范围和cdi依赖范围之间有什么区别?

According to the CDI documentation and javadoc 根据CDI 文档javadoc

When a bean is declared to have @Dependent scope: 当bean声明具有@Dependent范围时:

  • No injected instance of the bean is ever shared between multiple injection points. 在多个注入点之间不共享注入的bean实例。
  • ... ...

Similarly, the Spring documentation states 同样, Spring文档说明

The non-singleton, prototype scope of bean deployment results in the creation of a new bean instance every time a request for that specific bean is made. bean的非单例原型范围部署导致每次发出对该特定bean的请求时都会创建一个新的bean实例。

They are, behaviorally, the same. 它们在行为上是一样的。

The only difference I could find is on the lifecycle of the bean. 我能找到的唯一区别是bean的生命周期。 In Spring 在春天

Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. 因此,尽管无论范围如何都在所有对象上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。 The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding. 客户端代码必须清理原型范围的对象并释放原型bean所持有的昂贵资源。

In CDI however the container manages the full lifecycle of the bean, either directly when it is injected as a method invocation argument or indirectly when destroying the bean it was injected into. 然而在CDI中 ,容器管理bean的整个生命周期,直接在它作为方法调用参数注入时或在销毁注入的bean时间接管理。 The conditions are all described in the documentation linked. 这些条件都在链接的文档中描述。

As Luiggi mentions in the comments, it is important to note the default scope of a bean declaration. 正如Luiggi在评论中提到的那样,重要的是要注意bean声明的默认范围。 In the Spring docs state 在Spring docs状态中

The singleton scope is the default scope [...] 单例范围是默认范围[...]

while in CDI, the default scope is dependent . 在CDI中,默认范围是dependent

I think the right answer is: That depends on used proxyMode ! 我认为正确的答案是: 这取决于使用的proxyMode

The Spring's prototype scope works totally different in ScopedProxyMode.NO (which is typically equals to ScopedProxyMode.DEFAULT , unless a different default has been configured at the component-scan instruction level) and in ScopedProxyMode.TARGET_CLASS (or ScopedProxyMode.INTERFACES ). Spring的prototype范围在ScopedProxyMode.NO (通常等于ScopedProxyMode.DEFAULT ,除非在组件扫描指令级别配置了不同的默认值)和ScopedProxyMode.TARGET_CLASS (或ScopedProxyMode.INTERFACES )中完全不同。

In Spring, when prototype scoped bean declared with ScopedProxyMode.NO , then it's behavior is really almost the same as CDI's default scope @Dependent . 在Spring中,当使用ScopedProxyMode.NO声明prototype范围的bean时,它的行为实际上与CDI的默认范围@Dependent几乎相同。 The only difference is lifecycle management. 唯一的区别是生命周期管理。 In Spring, "configured destruction lifecycle callbacks are not called", while CDI manages full lifecycle of @Dependent beans. 在Spring中,“未调用已配置的销毁生命周期回调”,而CDI管理@Dependent bean的完整生命周期。

But, when prototype scoped bean declared with ScopedProxyMode.TARGET_CLASS (or ScopedProxyMode.INTERFACES ), then in Spring it's behavior is totally different from CDI's @Dependent scope. 但是,当使用ScopedProxyMode.TARGET_CLASS (或ScopedProxyMode.INTERFACES )声明prototype范围的bean时,那么在Spring中它的行为与CDI的@Dependent范围完全不同。 prototype beans is not bound in any way to lifecycle of instances where they are injected. prototype bean不会以任何方式绑定到注入它们的实例的生命周期。 If you inject such prototype bean into singleton bean, the proxy will be injected instead, and new bean instance will be created each time any proxy method will be invoked ! 如果将这样的原型bean注入到singleton bean中,则会注入代理, 每次调用任何代理方法时都会创建新的bean实例!

What does this means? 这意味着什么?

Look at this code: 看看这段代码:

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();
    }
}

How many different instances of myBean will be created for one invoke of MyController.hello() method? 一次调用MyController.hello()方法会创建多少个不同的myBean实例? Do you think one? 你觉得吗? No! 没有! Three instances of myBean will be created for one controller's method invocation. 将为一个控制器的方法调用创建三个myBean实例。 Each time when myBean.doSomething(); 每次当myBean.doSomething(); is called. 叫做。 One invocation of MyController.hello() will print something like: 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!

All 6 lines just for one invocation of MyController.hello() . 所有6行只是一次调用MyController.hello()

It is really different from CDI's @Dependent scope, where injected instance of MyBean will be created only one time for each instance where it will be injected. 它与CDI的@Dependent范围完全不同,其中注入的MyBean实例将仅为其注入的每个实例创建一次。 And it is also different even from Spring's prototype scope with ScopedProxyMode.NO . 即使是Spring的prototype范围与ScopedProxyMode.NO也不同。

For example, if you create another controller MyController2 , with code equal to MyController (remember, Spring controllers always have singleton scope) and change declaration of MyBean to: 例如,如果你创建另一个控制器MyController2 ,代码等于MyController (记住,Spring控制器总是有singleton范围)并将MyBean声明更改为:

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

then only two instances of MyBean will be created by Spring - one for each controller. 那么Spring只会创建两个MyBean实例 - 每个控制器一个。 And both will be created eagerly together with controllers. 两者都将与控制器一起热切地创建。 You will see something like this on application start: 您将在应用程序启动时看到类似的内容:

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

And something like this on each invoke of MyController.hello() method (same MyBean instance every time): 每次调用MyController.hello()方法时都是这样的(每次都是相同的MyBean实例):

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

And something like this on each invoke of MyController2.hello() method (same MyBean instance every time): 每次调用MyController2.hello()方法时都是这样的(每次都是相同的MyBean实例):

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

So in this sence it is not really a "prototype" bean. 所以在这种情况下它并不是真正的“原型”bean。 It is more like a bean without scope - it's scope is totally dependent on scope of enclosing bean (controller). 它更像是没有范围的bean - 它的范围完全取决于封闭bean(控制器)的范围。 Just like CDI's @Dependent scope. 就像CDI的@Dependent范围一样。

And remember: by default Spring prototype beans created in proxy mode ScopedProxyMode.DEFAULT , which is the same as ScopedProxyMode.NO . 并记住:默认情况下,Spring代理模式ScopedProxyMode.DEFAULT创建的prototype bean,与ScopedProxyMode.NO相同。

Be careful 小心

Usually this is not what you expect from scope named prototype . 通常这不是您对范围命名prototype期望。 There are other ways to inject fresh copy of prototype bean into bean of more broader scope (ie singleton ). 还有其他方法可以将原型bean的新副本注入更广泛范围的bean(即singleton )。 One of them: use ObjectProvider . 其中之一:使用ObjectProvider

With ObjectProvider you can have a prototype bean in ScopedProxyMode.NO , but inject ObjectProvider<MyBean> myBeanProvider instead of just MyBean myBean into controller. 使用ObjectProvider您可以在ScopedProxyMode.NO拥有原型bean,但是将ObjectProvider<MyBean> myBeanProvider而不仅仅是MyBean myBean注入到控制器中。

That way, you can have controller method, that gets a fresh copy of MyBean each time when it calls myBeanProvider.getObject() : 这样,您可以使用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();
    }
}

This code will print: 此代码将打印:

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!

on invoke of MyController.hello() method. 在调用MyController.hello()方法时。

Note: doSomething() was invoked three times on the same instance of MyBean ! 注意: 在同一个MyBean实例上调用了doSomething()三次!

On each call of MyController.hello() method, another instance of MyBean will be created - each time myBeanProvider.getObject() invoked. 在每次调用MyController.hello()方法时,都会创建另一个 MyBean实例 - 每次调用myBeanProvider.getObject()

So, the basic rules: 所以,基本规则:

  • If you need a Spring bean that lazily created each time when some method invoked on this bean, use @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) or @Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES) . 如果你需要每次在这个bean上调用某个方法时懒惰创建的Spring bean,请使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
  • If you need a Srping bean that created together with it's enclosing bean (no matter what scope it has), use @Scope(value = "prototype", proxyMode = ScopedProxyMode.NO) . 如果你需要一个与它的封闭bean一起创建的Srping bean(无论它有什么范围),请使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)
  • If you need a Spring bean that lazily created when you decide to create it, use @Scope(value = "prototype", proxyMode = ScopedProxyMode.NO) and inject ObjectProvider instead of bean itself. 如果你需要,当决定创建它是懒洋洋地创建一个Spring bean,使用@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)并注入ObjectProvider而不是bean本身的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM