Spring Bean的作用域(Scope)的原理及源码解析

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() {
                    @Override
                    public Object getObject() throws BeansException {
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                    }
                });
                bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
            catch (IllegalStateException ex) {
                throw new BeanCreationException(beanName,
                        "Scope '" + scopeName + "' is not active for the current thread; consider " +
                        "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                        ex);
            }
        }
    }
}

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负责其生命周期的处理。

总结

  1. 可以通过@Scope注解来指定Bean的作用域,若未指定默认是singleton
  2. Spring注册了一些Scope接口的实例用于处理scope非singleton和prototype的Bean
  3. Scope实例从Reqeust从存取scope=request的Bean,从Session中存取scope=session的Bean
  4. 每接收到一个Http请求时,将HttpServletRequest和HttpServletResponse封装成一个ServletRequestAttributes 绑定到RequestContextHolder的ThreadLocal变量中,在请求结束时清除绑定
  5. Scope从RequestContextHolder中获取当前线程的ServletRequestAttributes ,然后从其内的HttpServletRequest存入或取出或者删除指定名称及作用域的Bean对象。

你可能感兴趣的:(spring,spring,scope)