Spring笔记(9)-------Bean作用域

来源:互联网 发布:linux mint 使用教程 编辑:程序博客网 时间:2024/06/08 13:29


配置Bean的时候也可以配置Bean作用域,作用域对Bean的生命周期和创建方式产生影响。低版本只有singleton和prototype两个作用域,从Spring2.0开始,针对WebApplicationContext增加了三个作用域:request,session,globalSession。

类别说明singleton在容器中仅存在一个Bean实例prototype每次从容器中调用Bean时,都返回一个新的实例request每次Http请求都会 创建一个新的Bean(只适用于WebApplicationContext)session同一个Http Session共享一个Bean,不同的Session使用不同的BeanglobalSession同一个全局Session共享一个Bean,一般应用于Porlet环境。
配置方式:scope="作用域类型"

除了以上的5个作用域,还可以自定义作用域,通过org.springframework.beans.factory.config.Scope定义新的作用域,通过org.springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryPostProcessor注册自定义的作用域。

Singleton作用域:

由于Dao类持有Connection这个非线程安全的变量,而且没有采用单例模式,Spring环境下,所有Dao类都可以采用单例模式,因为Spring的AOP和LocalThread的功能,对非线程安全的变量进行了特殊处理,变成了线程安全的类。

如下:

<bean id="car" class="com.baobaobao.Car" scope="singleton" /><bean id="boss1" class="com.baobaobao.Boss" p:car-ref="car" /><bean id="boss2" class="com.baobaobao.Boss" p:car-ref="car" /><bean id="boss3" class="com.baobaobao.Boss" p:car-ref="car" />
配置文件通过配置引入的是相同的car Bean,通过容器的getBean("car")得到的也是同一个实例。

默认情况下,Spring的ApplicationContext在启动的时候,自动实例化所有singleton的Bean并缓存于容器中,虽然启动会花费一点时间,不过有两点好处:

1.对Bean提前实例化会及早发现一些潜在的配置问题(一般启动容器的时候会报错)

2.Bean以缓存的方式保存,当运行期间用到该Bean就不需要实例化了,加快了运行的效率。


如果用户不希望在容器启动时提前实例化singleton的Bean,可以通过lazy-init属性控制:

<bean id="boss1" class="com.baobaobao.Boss" p:car-ref="car" lazy-init="true"/>
当设置为lazy-init = "true"时有时候依旧会提前实例化,那就是该Bean被其他需要提前实例化的Bean引用到的时候。


prototype作用域:

<bean id="car" class="com.baobaobao.Car" scope="prototype" /><bean id="boss1" class="com.baobaobao.Boss" p:car-ref="car"/><bean id="boss2" class="com.baobaobao.Boss" p:car-ref="car" /><bean id="boss3" class="com.baobaobao.Boss" p:car-ref="car" />

通过以上的配置,boss1 ,boss2,boss3都是引用的一个新的car实例,通过容器的getBean("car")得到的也是一个新的car实例。

Spring默认在启动时不实例化prototype作用域的Bean。而且Spring将prototype的Bean交给调用者后不再管理它的生命周期。

Web应用环境相关的Bean作用域:

如果用户使用WebApplicationContext,还可以使用session,request,globalSession作用域,不过必须要如下配置:

在低版本(Servlet2.3之前),要在web.xml做如下配置:

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app>  <display-name>Archetype Created Web Application</display-name>    <filter><filter-name>requestContextFilter</filter-name><filter-class>org.springframework.web.filter.RequestContextFilter</filter-class></filter><filter-mapping><filter-name>requestContextFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>  </web-app>

在高版本的web容器中,可以利用Http请求监听器进行配置:

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app>  <display-name>Archetype Created Web Application</display-name>    <listener>  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>  </listener>  </web-app>

有人发现,在以前我们已经通过ContextLoaderListener将Web容器与Spring容器结合,为什么这儿又需要引入RequestContextListener以支持Bean的另外3个作用域。

可以看一下源码:整合Spring容器的ContextLoaderListener实现了ServletContextListener监听器接口,ServletContextListener只负责坚挺Web容器的启动和关闭事件。而RequestContextListener实现了ServletRequestListener接口,该接口监听Http请求事件,Web服务器接收每一次请求都会通知该监听器。

Spring容器的开启与关闭由Web容器的启动和关闭事件触发,但如果Spring容器的Bean需要request,session,globalSession作用域的支持,Spring本身就必须获得Web容器的Http请求事件。以Http请求事件去驱动Bean作用域的控制逻辑。

其实Spring完全可以提供一个既实现ServletContextListener又实现ServletRequestListener接口的监听器。不过它分开做目测有两个原因:

1)版本兼容问题,这三个作用域是Spring2.0新加的。

2)真正使用这仨作用域的人不多。。。。


1.request作用域:

<bean id="car" class="com.baobaobao.Car" scope="request" />
每次Http请求到car Bean都会创建一个新的Car实例,Http请求完毕后销毁该实例。


2.session作用域:

<bean id="car" class="com.baobaobao.Car" scope="session" />

car Bean作用域横跨整个Http session,所有Http请求共享一个carBean。


3.globalSession作用域:

类似于session作用域,不过仅在Porlet应用中使用。


作用域的依赖问题:

如果想要将web应用作用域的Bean注入到singleton或prototype作用域的Bean中,需要借助AOP,不然不好办到。

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd"><bean id="car" class="com.baobaobao.Car" scope="request" ><!--创建代理  --><aop:scoped-proxy/></bean><bean id="boss" class="com.baobaobao.Boss" p:car-ref="car" /></beans>

car Bean作用域是request,boss Bean是singleton的,boss引用car,为了让boss能从适当作用域里获取car Bean,需要为car Bean配置一个代理类。

当boss Bean在Web环境下调用car Bean时,Spring AOP将启动动态代理来判断boss Bean位于哪个Http 请求线程中,并从对应的Http请求域中获取对应的car Bean。


boss的作用域是singleton的,也就是说,在Spring容器中只有一个Boss实例,而car的作用域是request,每一次Http 请求都会创建一个car Bean.。Spring通过动态代理技术,让boss Bean引用到对应Http请求的car Bean。


如果我们往配置中增加了<aop:scope-proxy/>后,注入到boss Bean的car Bean已经不是原来的car Bean了,而是car Bean的动态代理对象。这个动态代理是Car类的子类,Spring在动态代理子类中增加一段逻辑以判断当前的boss 需要获取哪个Http 请求的car Bean。

(动态代理子类添加的逻辑其实很简单:即判断当前boss位于哪个线程中,然后根据这个线程找到对应的HttpRequest,再从HttpRequest获取对应的car Bean。因为Web容器的特性,一般一个Http请求对应一个独立的线程。)

java语言只能对接口提供自动代理,如果需要对类提供代理,需要在类路径下增加CGLib的类库,这时Spring将使用CGLib为类生成动态代理的子类。



0 0