Spring内常见的Scope有singleton,prototype,request,session;还有globalSession,application。
如果用注解定义Bean的作用域,则可以使用@Scope,将@Scope标识在一个Bean的类上,就可以定义这个Bean在容器中的作用域,如果未标识则默认是singleton。对于不同的Scope类型的Bean,Spring区分对待。在实例化Bean时,先通过判断其BeanDefinition是不是singleton或者prototype,可以看BeanDefinition接口的代码:
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
......
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
boolean isSingleton();
boolean isPrototype();
String getScope();
}
如果isSingleton() == true,则指明是singleton类型的Bean,只实例化一次,之后此Bean复用。
如果isPrototype() == true,则指明是prototype类型的Bean,每次获取这个Bean时都是创建一个新的对象。
如果以上两种情况均不符合,那么就需要用另外的逻辑来处理了,我们主要讲的就是这种情况。
Scope是一个接口(还有@Scope),当作用域是singleton或prototype时,没有用到这个接口,当作用域不是这两种类型时,就需要用这个接口了。可以先看Scope这个接口的代码:
public interface Scope {
//通过一个名称获取Bean实例
Object get(String name, ObjectFactory> objectFactory);
//销毁指定名称的Bean
Object remove(String name);
//为指定名称的Bean注册销毁时的回调函数
void registerDestructionCallback(String name, Runnable callback);
Object resolveContextualObject(String key);
String getConversationId();
}
Scope接口有三个关键方法,获取Bean,销毁Bean,以及注册销毁时的回调函数,就相当于生成周期结束时的触发方法。
Spring容器在初始化的起始阶段,就会往BeanFactory内注册多个Scope的实例,看代码:
public abstract class WebApplicationContextUtils {
/**
* Register web-specific scopes ("request", "session", "globalSession", "application")
* with the given BeanFactory, as used by the WebApplicationContext.
* @param beanFactory the BeanFactory to configure
* @param sc the ServletContext that we're running within
*/
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
}
在调用这步方法时,Spring在BeanFactory实例中注册了很多scope名称与Scope接口实现类的映射,可以查看AbstractBeanFactory的registerScope()方法。这些Scope实例中都有相应的通过Bean名称获取Bean实例的方法。同时注册了一些Bean的依赖实现,比如若Spring容器内注入ServletRequest,则会得到RequestObjectFactory实例,如果RequestObjectFactory实例是一个ObjectFactory接口的实现类,则返回getObject()方法得到的对象。
下面看当想要获取request或session作用域的Bean时的处理逻辑:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected T doGetBean(
final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
......
// Create bean instance.
if (mbd.isSingleton()) {
......
}
else if (mbd.isPrototype()) {
......
}
else {
//当既不是singleton也不是prototype时
String scopeName = mbd.getScope();
//通过scope的名称获取注册的实例,Scope接口的实现类,在上文中注册的
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//通过Scope实例获取Bean
Object scopedInstance = scope.get(beanName, new ObjectFactory
Request和Session作用域对应的Scope接口实例均继承自AbstractRequestAttributesScope,下面看这个类里面的方法逻辑:
public abstract class AbstractRequestAttributesScope implements Scope {
@Override
public Object get(String name, ObjectFactory> objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
//如果这个对象还不存在,则通过objectFactory生成一个,然后存入RequestAttributes 中
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
}
return scopedObject;
}
@Override
public Object remove(String name) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject != null) {
attributes.removeAttribute(name, getScope());
return scopedObject;
}
else {
return null;
}
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.registerDestructionCallback(name, callback, getScope());
}
@Override
public Object resolveContextualObject(String key) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
return attributes.resolveReference(key);
}
/**
* Template method that determines the actual target scope.
* @return the target scope, in the form of an appropriate
* {@link RequestAttributes} constant
* @see RequestAttributes#SCOPE_REQUEST
* @see RequestAttributes#SCOPE_SESSION
* @see RequestAttributes#SCOPE_GLOBAL_SESSION
*/
protected abstract int getScope();
}
以上的方法有个关键点,从RequestContextHolder获取当前的RequestAttributes ,然后再从RequestAttributes 里获取相应的Bean,或者删除相应的Bean。RequestContextHolder,从字面理解为请求上下文的持有者,可以通过它获取到当前请求。下面就看RequestContextHolder的如何获取到RequestAttributes 及获取到的RequestAttributes 是什么。
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal requestAttributesHolder =
new NamedThreadLocal("Request attributes");
private static final ThreadLocal inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal("Request context");
//将一个RequestAttributes 与当前线程绑定
public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
//获取当前现场绑定的RequestAttributes
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
//获取当前线程绑定的RequestAttributes ,如果RequestAttributes 为null,说明没有HttpRequest请求线程存在,
//也就是说当前容器还在初始化阶段,在容器初始化阶段就尝试实例化一个与HttpRequest绑定的Bean会报错
//也就说scope为request或session的Bean不能在容器初始化阶段实例化,不能直接注入scope为singleton的Bean中
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
"Are you referring to request attributes outside of an actual web request, " +
"or processing a request outside of the originally receiving thread? " +
"If you are actually operating within a web request and still receive this message, " +
"your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
}
RequestContextHolder 有两个ThreadLocal类型的静态成员,专门用于存储Http请求的信息。如果使用的是SpringMVC作为接受Http请求的框架,可以如下代码:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
......
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
......
initContextHolders(request, localeContext, requestAttributes);
......
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
}
}
//通过HttpServletRequest ,HttpServletResponse 生成RequestAttributes 实例
protected ServletRequestAttributes buildRequestAttributes(
HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {
if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
return new ServletRequestAttributes(request, response);
}
else {
return null; // preserve the pre-bound RequestAttributes instance
}
}
private void initContextHolders(
HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
//将当前Http请求的Request,Response的相关信息设置到ThreadLocal对象中。
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
//在当前请求结束时,还原ThreadLocal的线程绑定对象,清空此次请求的信息
private void resetContextHolders(HttpServletRequest request,
LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
}
}
也就是说每接收到一个Http请求,将请求的相关HttpServletReqeust,HttpServletResponse封装成一个ServletRequestAttributes 对象,和当前线程绑定,赋值到RequestContextHolder的ThreadLocal静态成员中。下面看ServletRequestAttributes 对象内的关键方法:
public class ServletRequestAttributes extends AbstractRequestAttributes {
private final HttpServletRequest request;
private HttpServletResponse response;
private volatile HttpSession session;
//根据作用域的值将从request还是session中获取指定名称的对象
public Object getAttribute(String name, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot ask for request attribute - request is not active anymore!");
}
return this.request.getAttribute(name);
}
else {
HttpSession session = getSession(false);
if (session != null) {
try {
Object value = session.getAttribute(name);
if (value != null) {
this.sessionAttributesToUpdate.put(name, value);
}
return value;
}
catch (IllegalStateException ex) {
// Session invalidated - shouldn't usually happen.
}
}
return null;
}
}
//根据作用域的值将指定对象存储到request还是session中
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
HttpSession session = getSession(true);
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
}
也就是说ServletRequestAttributes 在存储Bean时根据scope的值存入request或者session中,在获取Bean根据scope的值从request或者session中获取。
scope为request和session的Bean可以定义销毁时的方法,如用注解@PreDestroy定义,那么在request结束或者session结束时,会调用定义的方法作为其生命周期的一部分。Spring对request和session作用域的Bean负责其生命周期的处理。
总结: