在默认状况下,Spring应用上下文中全部bean都是做为以单例(singleton)的形式建立的。也就是说,无论给定的一个bean被注入到其余bean多少次,每次所注入的都是同一个实例。若是你所使用的类是易变的(mutable),它们会保持一些状态,所以重用是不安全的。在这种状况下,将class声明为单例的bean就不是什么好主意了,由于对象会被污染,稍后重用的时候会出现意想不到的问题。安全
一、Spring定义了多种做用域,能够基于这些做用域建立bean,包括:session
单例是默认的做用域,要使用@Scope注解对bean进行做用域的设置,它能够与@Component或@Bean一块儿使用。spa
@Componentprototype
例如,使用@Scope注解,将其声明为原型bean:3d
这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型做用域。你固然也可使用@Scope("prototype"),可是使用SCOPE_PROTOTYPE常量更加安全而且不易出错。代理
@Bean:Java配置中使用对象
XML中使用blog
使用情景:接口
在Web应用中,若是可以实例化在会话和请求范围内共享的bean,那将是很是有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean表明用户的购物车。若是购物车是单例的话,那么将会致使全部的用户都会向同一个购物车中添加商品。另外一方面,若是购物车是原型做用域的,那么在应用中某一个地方往购物车中添加商品,在应用的另一个地方可能就不可用了,由于在这里注入的是另一个原型做用域的购物车。作用域
就购物车bean来讲,会话做用域是最为合适的,以下例子:
这里,咱们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每一个会话建立一个ShoppingCart。这会建立多个ShoppingCart bean的实例,可是对于给定的会话只会建立一个实例,在当前会话相关的操做中,这个bean实际上至关于单例的。
上面的例子中,@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求做用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性以前,咱们先来看一下proxyMode所解决问题的场景。
假设咱们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中,以下所示:
使用proxyMode的缘由:
一、由于StoreService是一个单例的bean,会在Spring应用上下文加载的时候建立。当它建立的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。可是ShoppingCart bean是会话做用域的,此时并不存在。直到某个用户进入系统,建立了会话以后,才会出现ShoppingCart实例。
二、另外,系统中将会有多个ShoppingCart实例:每一个用户一个。咱们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。咱们但愿的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例刚好是当前会话所对应的那一个。
三、Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,如上例子所示。这个代理会暴露与ShoppingCart相同的方法,因此StoreService会认为它就是一个购物车。可是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话做用域内真正的ShoppingCart bean。
如今,咱们带着对这个做用域的理解,讨论一下proxyMode属性。如配置所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这代表这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
若是ShoppingCart是接口而不是类的话,这是能够的(也是最为理想的代理模式)。但若是ShoppingCart是一个具体的类的话,Spring就没有办法建立基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。因此,若是bean类型是具体类的话,咱们必需要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来代表要以生成目标类扩展的方式建立代理。
尽管我主要关注了会话做用域,可是请求做用域的bean会面临相同的装配问题。所以,请求做用域的bean应该也以做用域代理的方式进行注入。
使用XML来声明会话或请求做用域的bean,要设置代理模式,咱们须要使用Spring aop命名空间的一个新元素:
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean建立一个做用域代理。默认状况下,它会使用CGLib建立目标类的代理。可是咱们也能够将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:
为了使用<aop:scoped-proxy>元素,咱们必须在XML配置中声明Spring的aop命名空间: