3.5小节 http://docs.spring.io/spring/docs/3.0.x/reference/beans.html#beans-dependencies
bean scopes笔记:
bean的作用域:
Bean scopes
Scope | Description |
---|---|
singleto n |
(Default) Scopes a single bean definition to a single object instance per Spring IoC container. |
prototype |
Scopes a single bean definition to any number of object instances. |
request |
Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring |
session |
Scopes a single bean definition to the lifecycle of an HTTP |
global session |
Scopes a single bean definition to the lifecycle of a global HTTP |
spring2.0之前,bean只有两种作用域:singleton和prototype,2.0之后,又引入3中scope类型:request,session,globalsession
bean 默认的scope即为singleton类型,当bean的scope被设置为singleton时,Spring IoC容器会创建bean定义的唯一实例,并且这个实例会被保存在单例缓存(singleton cache)中,所有对该bean的后续请求和引用都会返回缓存中的对象实例。
注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中 只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时 候,spring的IoC容器中只会存在一个该bean。如下定义一个singleton作用域的bean:
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
对于prototype作用域的bean,每一次请求都会都会生成一个新的bean,一般来说,我们把prototype用于有状态的bean,而singleton用于无状态的bean。
下图说明了一个Spring prototype scope(要说明的是,图中的一个数据访问对象(DAO)一般不会被指为prototype,因为典型的DAO不会保存会话状态,仅仅是为了使读者自上面singleton图后能更好的理解)
<!-- using spring-beans-2.0.dtd --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>值得注意的是,Spring不会对prototype bean的整个生命周期负责,在容器初始化、配置。装配一个prototype bean之后,就会交给客户端处理,之后就不在操作prototype 实例了,尽管不论何种作用域,初始化生命周期的回调方法都会被调用,但是对prototype实例而言,任何析构生命周期的回调方法都不会被调用,清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的指责(让Spring容器释放被prototype-scope bean占用的资源是一种可行方法,通过使用一个bean 后置处理器,该处理器持有要被清除的bean的引用。) (??为什么很多资料上看到的是“让Spring 容器释放被singleton bean占用的资源。。。”我在文档上命名看到的是这样的:)
To get the Spring container to release resources held byprototype-scopedbeans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up.
对于prototype作用域的bean,某些方面可以将spring容器的角色看成是java的new操作,所有的生命周期的管理都交给客户端来处理。spring容易每次都要生成一个新的bean,如果都要记住这些bean,未免太消耗内存,所以对于bean的释放交给客户端进行处理。要慎用。
之前说到singleton bean只会实例化一次,如果要在singleton bean中注入property bean,没办法每次都实例化一次prototype bean,这个可以通过方法注入去解决,在之前已经提到过,详见这个里面的方法注入
Request,session,and global session scopes
在web-base application中,spring还支持request,session和global session 三种作用域,你可以使用Spring ApplicationContext实现(例如XmlWebApplicationContext),如果在普通的Spring IoC中,比如像xmlBeanFactory或ClassPathXmlApplicationContext,尝试使用这些作用域是,会得到一个IlleagleStateException异常。
为了使用request,session,和global session,需要先配置一些环境。
DispatcherServlet
for Spring MVCRequestContextListener
for Servlet 2.4+ web containerRequestContextFilter
for older web container (Servlet 2.3)如下面bean的定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>针对每次HTTP请求,Spring容器会根据
loginAction
bean定义创建一个全新的
LoginAction
bean实例,且该
loginAction
bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据
loginAction
bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
例如下面的例子:新建一个web工程
web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Spring-Req-Session-scope-Eg</display-name> <servlet> <servlet-name>HelloWeb</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>HelloWeb</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> </web-app>
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"> <mvc:annotation-driven /> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/"/> <property name="suffix" value=".jsp"/> </bean> <context:component-scan base-package="scope"></context:component-scan> <context:annotation-config /> <bean id = "requestscope" class ="scope.HelloRequestScopeDataImpl" scope = "request"> <constructor-arg> <bean class = "java.util.Date"></bean> </constructor-arg> <aop:scoped-proxy proxy-target-class="false" /> </bean> </beans>
其中:
package scope; public interface HelloRequestScopeData { public String getDate(); }
package scope; import java.util.Date; public class HelloRequestScopeDataImpl implements HelloRequestScopeData{ private final Date date; public HelloRequestScopeDataImpl(Date date) { this.date = date; } @Override public String getDate() { System.out.println("i have imp"); return date.toString(); } }
package scope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @Autowired private HelloRequestScopeData requestScopeData; @RequestMapping(value = "/") public String hellodate(Model model) { model.addAttribute("requestscopedate", requestScopeData.getDate()); return "hello"; } }注意的是
@Autowired private HelloRequestScopeData requestScopeData;HelloRequestScopeData不要写成HelloRequestScopeDataImpl类型,虽然在我们在spring-mvc.xml中定义了<bean id = "requestscope" class ="scope.HelloRequestScopeDataImpl" scope = "request">,但是这个bean在request作用域下才会生成,如果写成了private HelloRequestScopeDataImpl requestScopeData;会说找不到bean
hello.jsp页面如下:
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>hello world</title> </head> <body> <h2>---Request Scope Date----</h2> <h2>${requestscopedate}</h2> <h2>---------------------------</h2> </body> </html>
如下面bean的定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session
,Spring容器会根据userPreferences
bean定义创建一个全新的userPreferences
bean实例,且该userPreferences
bean仅在当前HTTP Session
内有效。与request作用域
一样,你可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session
中根据userPreferences
创建的实例,将不会看到这些特定于某个HTTP Session
的状态变化。当HTTP Session
最终被废弃的时候,在该HTTP Session
作用域内的bean也会被废弃掉。
如下面bean的定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
global session
作用域类似于标准的HTTP Session
作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session
的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session
作用域中定义的bean被限定于全局portlet Session
的生命周期范围内。
请注意,假如你在编写一个标准的基于Servlet的web应用,并且定义了一个或多个具有global session
作用域的bean,系统会使用标准的HTTP Session
作用域,并且不会引起任何错误。
能够在HTTP request或者Session
(甚至自定义)作用域中定义bean固然很好,但是Spring IoC容器除了管理对象(bean)的实例化,同时还负责协作者(或者叫依赖)的实例化。如果你打算将一个Http request范围的bean注入到另一个bean中,那么需要注入一个AOP代理来替代被注入的作用域bean。也就是说,你需要注入一个代理对象,该对象具有与被代理对象一样的公共接口,而容器则可以足够智能的从相关作用域中(比如一个HTTP request)获取到真实的目标对象,并把方法调用委派给实际的对象。
注意:
<aop:scoped-proxy/>
不能和作用域为singleton
或prototype
的bean一起使用。为singleton bean创建一个scoped proxy将抛出BeanCreationException
异常。
让我们看一下将相关作用域bean作为依赖的配置,配置并不复杂(只有一行),但是理解“为何这么做”以及“如何做”是很重要的。
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!-- a HTTP Session-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- this next element effects the proxying of the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="userService" class="com.foo.SimpleUserService"> <!-- a reference to the proxied 'userPreferences' bean --> <property name="userPreferences" ref="userPreferences"/> </bean> </beans>
<aop:scoped-proxy/>
子元素即可(你可能还需要在classpath里包含CGLIB库,这样容器就能够实现基于class的代理;还可能要使用基于XSD的配置)。上述XML配置展示了“如何做”,现在讨论“为何这么做”。在作用域为request
、session
以及globalSession
的bean定义里,为什么需要这个<aop:scoped-proxy/>
元素呢?下面我们从去掉<aop:scoped-proxy/>
元素的XML配置开始说起:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
从上述配置中可以很明显的看到singleton bean userManager
被注入了一个指向HTTP Session
作用域bean userPreferences
的引用。singleton userManager
bean会被容器仅实例化一次,并且其依赖(userPreferences
bean)也仅被注入一次。这意味着,userManager
在理论上只会操作同一个userPreferences
对象,即原先被注入的那个bean。而注入一个HTTP Session
作用域的bean作为依赖,有违我们的初衷。因为我们想要的只是一个userManager
对象,在它进入一个HTTP Session
生命周期时,我们希望去使用一个HTTP Session
的userPreferences
对象。
当注入某种类型对象时,该对象实现了和UserPreferences
类一样的公共接口(即UserPreferences
实例)。并且不论我们底层选择了何种作用域机制(HTTP request、Session
等等),容器都会足够智能的获取到真正的
UserPreferences
对象,因此我们需要将该对象的代理注入到userManager
bean中, 而userManager
bean并不会意识到它所持有的是一个指向UserPreferences
引用的代理。在本例中,当UserManager
实例调用了一个使用UserPreferences
对象的方法时,实际调用的是代理对象的方法。随后代理对象会从HTTP Session
获取真正的UserPreferences
对象,并将方法调用委派给获取到的实际的UserPreferences
对象。
这就是为什么当你将request
、session
以及globalSession
作用域bean注入到协作对象中时需要如下正确而完整的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
在Spring 2.0中,Spring的bean作用域机制是可以扩展的。这意味着,你不仅可以使用Spring提供的预定义bean作用域; 还可以定义自己的作用域,甚至重新定义现有的作用域(不提倡这么做,而且你不能覆盖内置的singleton
和prototype
作用域)。
作用域由接口org.springframework.beans.factory.config.Scope
定义。要将你自己的自定义作用域集成到Spring容器中,需要实现该接口。它本身非常简单,只有两个方法,分别用于底层存储机制获取和删除对象。自定义作用域可能超出了本参考手册的讨论范围,但你可以参考一下Spring提供的Scope
实现,以便于去如何着手编写自己的Scope实现。
在实现一个或多个自定义Scope
并测试通过之后,接下来就是如何让Spring容器识别你的新作用域。ConfigurableBeanFactory
接口声明了给Spring容器注册新Scope
的主要方法。(大部分随Spring一起发布的BeanFactory
具体实现类都实现了该接口);该接口的主要方法如下所示:
void registerScope(String scopeName, Scope scope);
registerScope(..)
方法的第一个参数是与作用域相关的全局唯一名称;Spring容器中该名称的范例有singleton
和prototype
。registerScope(..)
方法的第二个参数是你打算注册和使用的自定义Scope
实现的一个实例。
假设你已经写好了自己的自定义Scope
实现,并且已经将其进行了注册:
// note: the ThreadScope class does not exist; I made it up for the sake of this example Scope customScope = new ThreadScope(); beanFactory.registerScope("thread", scope);
Scope
的作用域规则相吻合的bean定义了:
<bean id="..." class="..." scope="thread"/>
如果你有自己的自定义Scope
实现,你不仅可以采用编程的方式注册自定义作用域,还可以使用BeanFactoryPostProcessor
实现:CustomScopeConfigurer
类,以声明的方式注册Scope
。BeanFactoryPostProcessor
接口是扩展Spring IoC容器的基本方法之一,在本章的BeanFactoryPostProcessor中将会介绍。
使用CustomScopeConfigurer
,以声明方式注册自定义Scope
的方法如下所示:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread" value="com.foo.ThreadScope"/> </map> </property> </bean> <bean id="bar" class="x.y.Bar" scope="thread"> <property name="name" value="Rick"/> <aop:scoped-proxy/> </bean> <bean id="foo" class="x.y.Foo"> <property name="bar" ref="bar"/> </bean> </beans>
CustomScopeConfigurer
既允许你指定实际的
Class
实例作为entry的值,也可以指定实际的
Scope
实现类实例;详情请参见
CustomScopeConfigurer
类的JavaDoc。
本文主要参考的文章:http://doc.javanb.com/spring-framework-reference-zh-2-0-5/ch03s04.html