Spring RequestScope和SessionScope的来龙去脉

Spring在bean配置时可以配置scope(bean的作用域),主要用来控制bean的生命周期,在spring2.0之前bean只有2种作用域即:singleton(单例)、non-singleton(也称prototype), Spring2.0以后,增加了session、request、global session三种专用于Web应用程序上下文的Bean。所以,默认情况下Spring2.0现在有五种类型的Bean。
1、singleton
当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时候,spring的IOC容器中只会存在一个该bean。
2、prototype
prototype作用域部署的bean,每一次请求(以程序的方式调用容器的getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。也就是说spring只负责对prototype的bean产生,不负责销毁,这种bean的消息需要自己来处理
3、request
request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
4、session
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效

5、global session
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用。

事实上,在Spring核心中只有singleton和prototype两种作用域,request、session、global session这3种scope都是基于prototype来实现的,对这3种方式产生的bean,spring的核心也是不负责销毁的,负责管理的是他们对应的Scope实现类,而且spring还允许自定义Scope。

request、session、global session使用的时候首先要在初始化web的web.xml中配置对应的listener或者 filter,listener:org.springframework.web.context.request.RequestContextListener(servlet2.4 以上)或者filter:org.springframework.web.filter.RequestContextFilter,这两个类的作用是一样的,就是从java的web Servlet机制中获取请求的HttpServletRequest和HttpSession对象

以request为例:

在RequestContextListener中获取到HttpServletRequest对象,以此来生成ServletRequestAttributes对象,然后把ServletRequestAttributes对象放入RequestContextHolder线程对象中,在调用BeanFactory.getBean()方法时Spring会调用RequestScope.get(String name, ObjectFactory objectFactory)方法,具体代码:

public Object get(String name, ObjectFactory objectFactory) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		Object scopedObject = attributes.getAttribute(name, getScope());
		if (scopedObject == null) {
			scopedObject = objectFactory.getObject();
			attributes.setAttribute(name, scopedObject, getScope());
		}
		return scopedObject;
	}
 ,从RequestContextHolder中获取ServletRequestAttributes对象,ServletRequestAttributes.getAttribute(String name, int scope)在request的Scope下就是从HttpServletRequest对象的attribute属性中取值,如果取不到再调用ObjectFactory以prototype方式重新生成一个并放入HttpServletRequest的attribute中,以便下次获取,放HttpServletRequest的bean随着请求的结束,生命周期也就结束了

 --------------------------------------------------------

自定义的Scope

Spring允许我们自定义Scope来管理bean,下面有一个简单例子,假设一个bean能被对个request线程复用,但是只能被使用5次(每个线程算一次)

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class MyScope implements Scope {
	private Map<String, ThreadsShareBean> map = new ConcurrentHashMap<String, ThreadsShareBean>();

	@Override
	public Object get(String name, ObjectFactory objectFactory) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		Object scopedObject = attributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
		if (scopedObject == null) {
			ThreadsShareBean threadsShareBean = this.map.get(name);
			if (threadsShareBean != null) {
				scopedObject = threadsShareBean.get();
			}

			if (scopedObject == null) {
				scopedObject = objectFactory.getObject();
				this.map.put(name, new ThreadsShareBean(scopedObject));
			}
			attributes.setAttribute(name, scopedObject, RequestAttributes.SCOPE_REQUEST);
		}
		return scopedObject;
	}

	public Object remove(String name) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		ThreadsShareBean threadsShareBean = this.map.remove(name);
		Object scopedObject = attributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
		if (scopedObject != null) {
			attributes.removeAttribute(name, RequestAttributes.SCOPE_REQUEST);
		} else {
			scopedObject = threadsShareBean.obj;
		}
		return scopedObject;
	}

	public void registerDestructionCallback(String name, Runnable callback) {
		RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
		attributes.registerDestructionCallback(name, callback, RequestAttributes.SCOPE_REQUEST);
	}

	@Override
	public String getConversationId() {
		return null;
	}

	class ThreadsShareBean {
		private AtomicInteger atomicInteger = new AtomicInteger(1);
		private Object obj = null;

		public ThreadsShareBean(Object obj) {
			this.obj = obj;
		}

		public Object get() {
			if (atomicInteger.getAndIncrement() < 5) {
				return obj;
			}
			return null;
		}
	}
}
 首先要实现org.springframework.beans.factory.config.Scope接口,然后借用了RequestContextHolder的存放当前线程的bean

另外必须在bean初始化之前把MyScope注册到Spring ioc中,否则Spring启动的时候会报错,我是用BeanFactoryPostProcessor来实现的

public class RegisterMyScope implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		beanFactory.registerScope("myScope", new MyScope());
	}
}
 最要只要在spring配置文件中注册这个bean就可以了 <bean class="com.xxx.RegisterMyScope" />

<bean id="testBean" class="com.xxx.TestBean" scope="myScope"/>
这样就可以用myScope对bean进行作用域管理了

猜你喜欢

转载自lan-qie.iteye.com/blog/1769445