Bean scopes

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 SpringApplicationContext.

session

Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware SpringApplicationContext.

global session

Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext.


spring2.0之前,bean只有两种作用域:singleton和prototype,2.0之后,又引入3中scope类型:request,session,globalsession


The singleton scope

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"/>

The prototype scope

对于prototype作用域的bean,每一次请求都会都会生成一个新的bean,一般来说,我们把prototype用于有状态的bean,而singleton用于无状态的bean。

下图说明了一个Spring prototype scope(要说明的是,图中的一个数据访问对象(DAO)一般不会被指为prototype,因为典型的DAO不会保存会话状态,仅仅是为了使读者自上面singleton图后能更好的理解)

Bean scopes_第1张图片
下面显示了一个prototype作用域的bean的用法:

<!-- 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 beans with prototype-bean dependencies

之前说到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异常。

Initial web configuration

为了使用request,session,和global session,需要先配置一些环境。

  1. DispatcherServlet for Spring MVC
  2. RequestContextListener for Servlet 2.4+ web container
  3. RequestContextFilter for older web container (Servlet 2.3)

Request scope

如下面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>

spring-mvc.xml

<?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>

requestscope的scope是“request”类型。

其中:

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 scopes_第2张图片


Session scope

如下面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也会被废弃掉。

global session scope

如下面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作用域,并且不会引起任何错误。


scoped beans and dependencies

能够在HTTP request或者Session(甚至自定义)作用域中定义bean固然很好,但是Spring IoC容器除了管理对象(bean)的实例化,同时还负责协作者(或者叫依赖)的实例化。如果你打算将一个Http request范围的bean注入到另一个bean中,那么需要注入一个AOP代理来替代被注入的作用域bean。也就是说,你需要注入一个代理对象,该对象具有与被代理对象一样的公共接口,而容器则可以足够智能的从相关作用域中(比如一个HTTP request)获取到真实的目标对象,并把方法调用委派给实际的对象。

注意:

<aop:scoped-proxy/>不能和作用域为singletonprototype的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>

在XML配置文件中,要创建一个作用域bean的代理,只需要在作用域bean定义里插入一个<aop:scoped-proxy/>子元素即可(你可能还需要在classpath里包含CGLIB库,这样容器就能够实现基于class的代理;还可能要使用基于XSD的配置)。上述XML配置展示了“如何做”,现在讨论“为何这么做”。在作用域为requestsession以及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 SessionuserPreferences对象。

当注入某种类型对象时,该对象实现了和UserPreferences类一样的公共接口(即UserPreferences实例)。并且不论我们底层选择了何种作用域机制(HTTP request、Session等等),容器都会足够智能的获取到真正的UserPreferences对象,因此我们需要将该对象的代理注入到userManager bean中, 而userManager bean并不会意识到它所持有的是一个指向UserPreferences引用的代理。在本例中,当UserManager实例调用了一个使用UserPreferences对象的方法时,实际调用的是代理对象的方法。随后代理对象会从HTTP Session获取真正的UserPreferences对象,并将方法调用委派给获取到的实际的UserPreferences对象。

这就是为什么当你将requestsession以及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>

Custom scopes(自定义作用域)

在Spring 2.0中,Spring的bean作用域机制是可以扩展的。这意味着,你不仅可以使用Spring提供的预定义bean作用域; 还可以定义自己的作用域,甚至重新定义现有的作用域(不提倡这么做,而且你不能覆盖内置的singletonprototype作用域)。

作用域由接口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容器中该名称的范例有singletonprototyperegisterScope(..)方法的第二个参数是你打算注册和使用的自定义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类,以声明的方式注册ScopeBeanFactoryPostProcessor接口是扩展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

你可能感兴趣的:(Bean scopes)