The IoC Container 1.5

作者: 小鲍比大爷 | 来源:发表于2019-02-15 16:36 被阅读0次

    1.5. Bean Scopes

    Spring可以帮助我们装配对象,从而促使系统工作。另外,Spring还可以帮助我们管理对象的生命周期,Spring称之为Bean Scopes。Spring支持6种Scope,其中4种只能用于spring web。
    Bean scopes

    Scope Description
    singleton 单例
    prototype 每次使用都生成新实例,实例数目不限
    request 每次HTTP请求时生成实例,单个请求中为单例
    session 单个HTTP session中为单例
    application 单个ServletContext中为单例
    webSocket 单个websocket中为单例

    后四种scope针对web应用。

    1.5.1. The Singleton Scope

    单例(很基础,不多说了):


    singleton scope

    Spring中的单例跟设计模式中的单例稍微有点区别的地方在于,Spring中的单例是per-container and per-bean(IoC Container中的单例)。Spring中的默认scope是singleton,所以如果不指定bean的scope,那么获取的一定是在Spring中全局唯一的那个单例。
    xml配置示例:

    <bean id="accountService" class="com.something.DefaultAccountService"/>
    
    <!-- the following is equivalent, though redundant (singleton scope is the default) -->
    <bean id="accountService" class="com.something.DefaultAccountService" 
        scope="singleton"/>
    

    1.5.2. The Prototype Scope

    prototype

    注意,请不要被上图中的accountDao迷惑,因为accountDao一定是典型的单例,这块只是为了复用之前的单例图:

    (A data access object (DAO) is not typically configured as a prototype, because a typical DAO does not hold any conversational state. It was easier for us to reuse the core of the singleton diagram.)

    使用prototype scope的原则:

    As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.

    配置示例:

    <bean id="accountService" class="com.something.DefaultAccountService" 
      scope="prototype"/>
    

    关于prototype,需要注意的是,Spring只会每次生成实例后就将实例交给用户,用户需要负责销毁实例和释放实例资源的工作,而Spring只负责初始化实例时的工作。prototype在某种程度上可以等价为java的new操作,但是new完之后实例的生命周期需要由用户控制。本节提到可以通过实现bean post-processor来让Spring管理prototype scope对象的生命周期,不过本节没有说明如何实现。

    1.5.3. Singleton Beans with Prototype-bean Dependencies

    在Spring中如果试图将Prototype-bean注入到Singleton Bean中,那么只会注入一次,不会发生每次使用单例时都给用户重新生成Prototype-bean的事情。如果真的有这种需求,那么可以参考Method Injection

    1.5.4. Request, Session, Application, and WebSocket Scopes

    request, session, application, and websocket scopes。这四种scope必须在web类型的ApplicationContext(比如XmlWebApplicationContext)下才能正常工作,假设使用常规的ClassPathXmlApplicationContext容器,那么直接报IllegalStateException异常并提示错误。


    非法的scope

    由于还没看过spring web方面的文档,此节后续再补充。

    1.5.5. Custom Scopes

    用户自定义scope。Spring支持用户自定义scope类型,甚至可以覆盖重写原有类型,当然,覆盖重写原有类型Spring官方是不推荐的。
    实现用户自定义scope,需要继承实现Scope接口。

    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.lang.Nullable;
    
    public interface Scope {
        Object get(String var1, ObjectFactory<?> var2);
    
        @Nullable
        Object remove(String var1);
    
        void registerDestructionCallback(String var1, Runnable var2);
    
        @Nullable
        Object resolveContextualObject(String var1);
    
        @Nullable
        String getConversationId();
    }
    

    接口的文档说明可以参考:
    Scope接口

    官方文档建议直接看源码的具体实现。比如RequestScope。

    package org.springframework.web.context.request;
    
    import org.springframework.lang.Nullable;
    
    public class RequestScope extends AbstractRequestAttributesScope {
        public RequestScope() {
        }
    
        protected int getScope() {
            return 0;
        }
    
        @Nullable
        public String getConversationId() {
            return null;
        }
    }
    

    RequestScope没什么具体实现,干货都在AbstractRequestAttributesScope里面。getScope返回0,这个应该是类似用来枚举不同的scope,不同的值代表不同的scope类型,0代表request scope。

    package org.springframework.web.context.request;
    
    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.beans.factory.config.Scope;
    import org.springframework.lang.Nullable;
    
    public abstract class AbstractRequestAttributesScope implements Scope {
        public AbstractRequestAttributesScope() {
        }
    
        public Object get(String name, ObjectFactory<?> objectFactory) {
            RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
            Object scopedObject = attributes.getAttribute(name, this.getScope());
            if (scopedObject == null) {
                scopedObject = objectFactory.getObject();
                attributes.setAttribute(name, scopedObject, this.getScope());
                Object retrievedObject = attributes.getAttribute(name, this.getScope());
                if (retrievedObject != null) {
                    scopedObject = retrievedObject;
                }
            }
    
            return scopedObject;
        }
    
        @Nullable
        public Object remove(String name) {
            RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
            Object scopedObject = attributes.getAttribute(name, this.getScope());
            if (scopedObject != null) {
                attributes.removeAttribute(name, this.getScope());
                return scopedObject;
            } else {
                return null;
            }
        }
    
        public void registerDestructionCallback(String name, Runnable callback) {
            RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
            attributes.registerDestructionCallback(name, callback, this.getScope());
        }
    
        @Nullable
        public Object resolveContextualObject(String key) {
            RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
            return attributes.resolveReference(key);
        }
    
        protected abstract int getScope();
    }
    

    重要方法详解:
    Object get(String name, ObjectFactory objectFactory)
    用于获取bean,代码很简单,一看就懂,RequestScope的实现中,最终将生成的bean对象保存在RequestContextHolder类中名为requestAttributesHolder成员变量中,requestAttributesHolder的类型为ThreadLocal,ThreadLocal保证了线程中的bean唯一,从而才保证了bean是request scope级别。讲的更清楚点:相当于每次请求所在的线程中,该对象为单例。根据Spring之前给Container的单例解释,我给request scope中的bean取了个类似的英文名叫做per-thread and per-bean。

    Object remove(String name)
    get方法的反向操作。

    void registerDestructionCallback(String name, Runnable destructionCallback)
    注册回调,负责对象的一些销毁工作,同样是注册到ThreadLocal中。

    Using a Custom Scope
    当用户自定义scope实现完成之后,必须注册后才能使用。SimpleThreadScope是Spring自带的scope类型实现,但是默认不会注册,下面代码示例将会把这个scope注册进去。

    ApplicationContext context = new ClassPathXmlApplicationContext( "scope.xml");
    ((ClassPathXmlApplicationContext) context).getBeanFactory().registerScope("thread", new SimpleThreadScope());
    

    上面示例是代码的注册方式,还可以使用xml的配置注册方式,如下:

        <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
            <property name="scopes">
                <map>
                    <entry key="thread">
                        <bean class="org.springframework.context.support.SimpleThreadScope"/>
                    </entry>
                </map>
            </property>
        </bean>
    

    使用的话,跟指定Spring自带的scope没什么两样:

    <bean id="..." class="..." scope="thread">
    

    总结一下,Spring默认支持6种scope,其中4种scope必须在web类型的Container中才能使用,同时还支持用户自定义scope。

    相关文章

      网友评论

        本文标题:The IoC Container 1.5

        本文链接:https://www.haomeiwen.com/subject/ynhmeqtx.html