bean的作用域解析

时间:2022-09-09 17:09:36

说明

  意义

    1.在Spring中,Bean的作用域可以通过scope属性来指定。

       2.指定作用域的目的是 存储在此类单例bean的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象。(本身的理念就是以空间换时间的思维,创建步骤繁杂,而且频繁用到,我就存起来,下次用的时候就不用了创建了)

       3.了解了目的之后,自然也就有了多种类型,大多数会使用singleton,当然也会有希望每次用到的就是新产生的故而出现prototype类型,还有就是某些范围经常用到,另一些范围不经常用到的,衍生了request和session的范围性质的单例

  类型与范围

    常见的有:

      1)singleton:代表单例的,也是默认值(singleton存储在三级缓存内,本质上是容器applicationcontext里面的三级缓存)

      2)prototype:代表多例的(prototype不会对bean进行存储,而是在每次需要的时候进行创建)

      3)request:代表范围性质的单例(request存储在对应的请求构建的请求对象里面setAttribute)

      4)session:代表范围性质的单例(session存储在对应的请求构建的请求对象里面setAttribute)

      5)application:application则是作用域整个应用里面多个applicationcontext共享

      6)包括自定义作用域

代码展示

// mbd 指的是前面部分的 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

else if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

else {
    String scopeName = mbd.getScope();
    // 这一步获取的就是存储单例的缓存,针对不同类型获取不同的缓存块【如request对应的RequestScope,session对应的SessionScope】
    final Scope scope = this.scopes.get(scopeName);
    if (scope == null) {
        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    }
    try {
        //类似于getSingleton的方式,在缓存中拿不到才会走工厂方法获取
        Object scopedInstance = scope.get(beanName, () -> {
            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);
    }
}

 

 

代码分析

  对于Prototype部分的分析

    1.先是涉及到检测循环依赖部分的

        beforePrototypeCreation(beanName);  //记录循环依赖,针对还没有创建完成的Bean进行记录

        afterPrototypeCreation(beanName);  //销毁记录,已创建完了就必须销毁,不然A依赖于B,B都创建完了,你还觉得别人还没创建

      2.涉及创建Bean部分的

        了解过源码的都知道,在创建过程中,如果bean实例化但是未初始化会有一个对外暴露的方式,就是存储于单例池中

        故对于多例情况,bean是不做缓存的

 

  对于Singleton部分的分析

    对于单例的bean有它自己的处理逻辑,getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    //加锁是保证单例创建的不冲突
    synchronized (this.singletonObjects) {
        //尝试从单例池中获取
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //记录循环依赖,针对还没有创建完成的Bean进行记录
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                //从工厂方法中,创建bean对象
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {}
            catch (BeanCreationException ex) {}
            finally {
                 //销毁记录,已创建完了就必须销毁
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //创建完了要添加进入单例池
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

 

  对于其余部分的分析(包括request,session等和自定义都是走这部分的逻辑)

    针对request,session等,代码  scope.get  这部分深入进去其实是通用方法(也是模板设计模式),AbstractRequestAttributesScope类#get方法

@Override
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());
        // Retrieve object again, registering it for implicit session attribute updates.
        // As a bonus, we also allow for potential decoration at the getAttribute level.
        Object retrievedObject = attributes.getAttribute(name, getScope());
        if (retrievedObject != null) {
            // Only proceed with retrieved object if still present (the expected case).
            // If it disappeared concurrently, we return our locally created instance.
            scopedObject = retrievedObject;
        }
    }
    return scopedObject;
}

 

    这块便是针对缓存的获取,通用理解为 attributes.getAttribute(name, getScope()); 等同于session.getAttribute(beanName)或 request.getAttribute(beanName

    工厂方法( Lambda表达式部分)针对的便是缓存没有时候的创建逻辑

 

分析汇总

  1.对于作用域,本质上是存储在此类单例bean的高速缓存中,并且对该命名bean的所有后续请求和引用都返回该高速缓存的对象,便是为了达到以空间换时间的优化方式。

  2.对于创建Bean,都要进行循环依赖的预防。

 

AbstractRequestAttributesScope