当我使用spring框架编写业务代码时,我总是使用单例,并避免使用原型。我认为原型和单例之间存在性能差异。由于有了原型,它在每次调用时都会创建一个新实例。而且我认为它比单例要慢。我对吗?而且我是否对性能有太多考虑?
答案 0 :(得分:1)
是的,对的原型范围内的bean会消耗更多的资源。根据{{3}}:
每次部署特定bean的请求时,bean部署的非单一原型范围都会导致创建一个新bean实例。
在每个请求处调用构造函数,而不是对单例作用域的bean调用一次。但是还有另一个方面要考虑。 documentation说:
因此,尽管在不考虑作用域的情况下在所有对象上都调用了初始化生命周期回调方法,但对于原型,则不会调用已配置的销毁生命周期回调。客户端代码必须清除原型作用域内的对象并释放原型Bean拥有的昂贵资源。
如果要避免出现内存不足异常,则必须注意避免破坏bean。
除非确实需要,否则最好使用单例作用域的bean。
答案 1 :(得分:0)
从最基本的意义上讲,我认为您通常不会在原型和单例之间看到任何明显的性能差异。
确实,在应用程序上下文中只有一个单例bean实例,并且针对该bean的每个请求都创建了具有“原型”范围定义的bean实例,但是,我们正在谈论新对象创建的性能而且它现在非常便宜,并且针对非昂贵的对象进行了优化。
但是这里有一些警告:
如果Bean的构造函数调用一些非常昂贵的代码怎么办,那当然不是spring的错,因为程序员是用这种方式编写代码的,但是实际上,每次都会有一些非常昂贵的代码被称为 < / strong>该bean被创建,并且会消耗性能。
稍微有趣一点:我们通常不将这样的代码放在构造器中,而是使用诸如@PostConstruct
/ @PreDestroy
之类的“生命周期”方法。
对于Singletons,spring在创建Singleton时将调用post-construct方法一次,然后将bean置于应用程序上下文中。 当应用程序上下文关闭时(通常是在应用程序关闭时),将调用pre-destroy方法释放资源等。
但是,对于Prototype范围的bean,情况并非如此: Spring根据对bean的每次请求创建它们,然后调用post-construct方法,再次为其创建的bean的每个单个实例,然后不将实例存储在应用程序上下文中。这意味着spring不会调用所有销毁前的方法(因为它不知道这些对象是谁,因此在创建它们之后并没有真正对其进行管理)。 因此,这可能(同样不是弹簧的错误)导致两者之间的严重性能差异。
如果我们做得更进一步,有时spring必须将对象包装成某种代理。这些代理通常在有接口的情况下使用java.lang.Proxy
或基于继承的Cglib
来完成。现在,仅当我们谈论将原型作用域的bean封装到代理中时,这个事实就可能成为性能严重差异的根源,因为代理是在运行时生成的,并且使用代理(尤其是CGlib)包装对象非常昂贵。
答案 2 :(得分:0)
我可以提出这个问题,最近我一直在研究它。对象的构造非常便宜,除非它们实际上在构造函数中做了一些繁重的工作。对于在一个语句中构造一堆lambda,您永远不会三思而后行。那不是问题。
问题是Spring的效率低下,没有正确设计架构。我最近以Spring vs Guice为基准。使用Spring时,单例会稍微快一点,但是原型会慢20倍。
从代码健全性的角度来看,您应该始终偏爱原型。 Guice默认为原型。 javax.inject.Inject
文档默认为原型。 Spring列出了一些模糊的历史原因,并做了Singletons。
原型更安全,因为它们不会意外地存储状态并在错误的上下文中使用它。他们不能存储一个请求中的“ userId”,而不能在另一个请求中使用它,因为通过原型,每次都会创建一个具有干净状态的全新实例。这就是我学习这种模式的方式:将Singleton与RequestScoped Providers一起使用时,我们意外地缓存了错误的用户上下文。哎哟。避免该错误很重要。获取CPU是一个小得多的问题。
总结:使用Prototypes获得更好的代码,如果性能非常重要,则不要使用Spring。