spring:Bean作用域

在配置文件中定义Bean时,用户不但可以配置Bean的属性值及相互之间的依赖关系,还可以定义Bean的作用域。作用域将对Bean的生命周期和创建方式产生影响。
spring 4.0中所支持的作用域:
① singleton:
在Spring IoC容器中仅存在一个Bean实例,Bean以单例的方式存在。
② prototype:
每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时相当于执行new xxxBean()操作。
③ request:
每次HTTP请求都会创建一个新的Bean。该作用域仅适用于WebApplicationContext环境。
④ session:
同一个HTTP session共享一个Bean,该作用域仅适用于WebApplicationContext环境。
⑤ globalSession:
同一个全局session共享一个Bean,一般用于Portlet应用环境。该作用域仅适用于WebApplicationContext环境。

在低版本的Spring中,仅支持两个Bean的作用域,所以采用singleton="true|false"的配置方式。Spring为了向后兼容,依然支持这种方式。不过Spring推荐采用新的配置方式<bean id="xxx" scope="作用域类型">。
除了以上5中预定义的Bean作用域以外,Spring还允许用户自定义Bean的作用域。可以通过org.springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryPostProcessor注册自定义的Bean作用域。感兴趣的可以自行阅读Scope接口的javadoc文档。

1 singleton作用域

单例模式是最重要的设计模式之一。在传统的应用开发中,需要手工为每个单实例类编写特定代码,在这种情况下,类的业务逻辑代码和模式代码紧密耦合在一起。Spring以容器的方式提供天然的单例模式功能,任何POJO无需编写特殊的代码,仅通过配置就可以使用单例模式。
一般情况下,无状态或者状态不可变的类适合使用单例模式,不过Spring对此实现了超越。在传统开发中,由于DAO类持有Connection这个非线程安全的变量,因此往往未采用单例模式。而在Spring中,所有的DAO类都可以采用单例模式,因为Spring利用AOP和LocalThread功能,对非线程安全的变量进行了特殊处理,使这些非线程安全的类变成了线程安全的类。
因为Spring的这一超越,所以在实际应用中大部分Bean都能以单例的方式运行,这也是为什么Spring将Bean的默认作用域定位singleton的原因。
singleton的Bean在同一Spring IoC容器中只有一个实例。
不但在配置文件中通过bean的属性注入的bean引用是单例的,任何通过容器的getBean("beanId")方法获取的实例也指向同一个Bean。
在默认的情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中。虽然启动时会花费一些时间,但它带来两个好处:首先,对Bean提前进行实例化操作会及早发现一些潜在的配置问题;其次,Bean以缓存的方式保存,当运行时用到该Bean时就无需在实例化了,提高了运行的效率。如果用户不希望在容器启动时提前实例化singleton的Bean,则可以通过lazy-init属性进行控制。
lazy-init="true" 的Bean在某些情况下依然会提前实例化:如果该Bean被其他需要提前实例化的Bean所引用,那么Spring将会忽略延迟实例化的设置。

2 prototype作用域

采用scope="prototype"指定非单例作用域的Bean,每次通过容器的getBean("beanId")方法返回的都是一个新的实例。
在默认情况下,Spring容器在启动时不实例化prototype的Bean。此外,Spring容器将prototype的Bean交给调用者后,就不在管理它的生命周期。

3 与Web应用环境相关的Bean作用域

如果用户使用Spring的WebApplicationContext,则可以使用另外的3种Bean的作用域:request、session、globalSession。不过在使用这些作用域之前,首先要在Web容器中进行一些额外的配置。

① 在web.xml中添加HTTP请求监听器进行配置:

<web-app>
	<listener>
		<listener-class>
			org.springframework.web.context.request.RequestContextListener
		</listener-class>
	</listener>
</web-app>

大家可能会发现之前WebApplicationContext初始化时,已经通过ContextLoaderListener或ContextLoaderServlet将Web容器与Spring容器进行了整合,为什么在这里又要额外的引入RequestContextListener以支持Bean的另外3个作用域呢?
在整合Spring容器时使用ContextLoaderListener,它实现了ServletContextListener监听器接口,ServletContextListener只负责监听Web容器启动和关闭事件。而RequestContextListener实现了ServletRequestListener监听器接口,该监听器监听了HTTP请求事件,Web服务器接收的每一次请求都会通知该监听器。
Spring容器启动和关闭操作由Web容器的启动和关闭事件触发,但如果Spring容器中的Bean需要request、session和globaalSession作用域的支持,Spring容器本身就必须获得Web容器的HTTP请求事件,以HTTP请求事件驱动Bean作用域的控制逻辑。也就是说通过配置RequestContextListener,Spring容器和Web容器的结合更加密切,Spring容器对Web容器就可以实施相应的Bean作用域控制了。

② request作用域

request作用域的Bean对应一个HTTP请求和声明周期:scope="request" 。
这样,每次HTTP请求调用的Bean,Spring容器都会创建一个新的Bean,请求处理完毕后,就会销毁这个Bean。

③ session作用域

当Bean的作用域为session作用域时,此Bean的作用域在整个HTTP Session中的所有HTTP请求都共享一个Bean。当HTTP Session结束后,Bean被销毁:scope="session"。

④ globalSession作用域

globalSession作用域类似于session作用域,不过仅在Portlet的Web应用中使用。Portlet规范定义了全局Session的概念,它被组成PortletWeb应用的所有子Portlet共享。如果不在PortletWeb应用华静霞,那么globalSession作用域就等价于session作用域。

4 作用域依赖问题

假设将Web相关作用域的Bean注入singleton或prototype的Bean中,我们当然希望它能够按照预定的方式工作,即引用者应该从指定的域中取得它的引用。但如果没有进行一些额外的配置,那么它的运行不会按照我们所期望的步骤进行:
① 引入aop schema  在beans标签中添加:

xmlns:aop="http://www.springframework.org/schema/aop"

② 声明一个request作用域的bean,并在bean中创建代理

<bean name="xxx" class="xxx.xxx" scope="request">
    <aop:scoped-proxy/>
</bean>

③ 引用request作用域的bean:

<bean id="x" class="x.x">
    <property name="xxx" ref="xxx" />
</bean>

在上述例子中一个singleton作用域的bean引用了一个request作用域的bean。为了singleton的作用域的bean能获取到request作用域的bean,需要使用Spring AOP的语法为request作用域的bean配置一个代理类,为了能够在配置文件中使用AOP的配置标签,则需要在beans中声明中定义aop命名空间。
当singleton作用域的Bean在Web环境下调用request作用域的Bean时,Spring AOP将启动动态代理判断当前Bean位于哪个HTTP请求线程中,并从对应的HTTP请求线程域中获取对应的request作用域的Bean。
反过来说,在配置文件中添加<aop:scoped-proxy/>后,注入的Bean已经不是在配置文件中声明的Bean对象了,而是通过动态代理获取的Bean的实例。
Spring在动态代理类中加入一段逻辑,以判断当前的request作用域的Bean需要取得那个HTTP请求相关的Bean,首先判断当前request作用域的Bean在哪个线程中,然后根据这个线程获取对应的HttpRequest对象,在使用HttpRequest域中获取对应的request作用域的bean。因为Web容器的特性,一般情况下,一个HTTP请求对应一个独立的线程。
Java语言只能对接口提供自动代理,所以,如果需要对类提供代理,则需要在类路径中加入CGLib的类库,这时Spring将使用CGLib为类生成动态代理的子类。

猜你喜欢

转载自blog.csdn.net/yongqi_wang/article/details/86707468