Spring学习笔记——Spring Scope(作用域)详解

本文深入探讨Spring中的自定义Scope,介绍如何创建并管理Bean。通过示例展示了如何定义一个作用域,以及作用域对象的生成过程。文章还解析了Spring源代码,阐述了BeanFactory与Scope的关系,以及Scope在Bean生命周期中的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

Spring学习笔记 —— 从IOC说起中,曾经提到过Spring中的Bean是根据Scope(作用域)来生成的,但是一直都没有详细解释过,除了Singleton(单例)和prototype(原型)作用域之外,另外一种重要的作用域——用户自定义作用域。

今天要写的就是如何自定义一个作用域,且如何在作用域内对Bean进行管理。本文还是会分成三部分,示例(包含一个简单的代码示例),代码解析(包含类图分析)和小结。

示例

首先是我们简单的Bean,SimpleBean.java这个类中有一个私有变量createdTime,在每次实例化的时候,都会被赋值为当前的时间戳,因此可以根据时间戳来判断是否属于同一个Bean。

public class BeanSample {
    private Long createdTime;

    public BeanSample() {
        createdTime = (new Date()).getTime();
    }

    public void printTime() {
        System.out.println(createdTime);
    }
}

然后是ScopeSample,我们自定义的作用域也要声明称一个Bean,在类实现里面,需要实现接口Scope.java

public class ScopeSample implements Scope{
    //这里做了一个简单的处理,直接用HashMap保存Bean,且为每一个用户,根据用户的userId,创建一个Bean的HashMap
    private final Map<Long, Map<String, Object>> scopeMaps = new ConcurrentHashMap<Long, Map<String,Object>>();
    //简单实现,直接将userId定义为pulic static
    public static Long curUserId = 1L;
    //get方法,必须实现
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> objectMap;
        if(scopeMaps.get(curUserId) == null) {
            Map<String,Object> newObjMap = new ConcurrentHashMap<String,Object>();
            scopeMaps.put(curUserId, newObjMap);
            objectMap = newObjMap;
        } else {
            objectMap = scopeMaps.get(curUserId);
        }

        if(!objectMap.containsKey(name)) {
            objectMap.put(name, objectFactory.getObject());
        }

        return objectMap.get(name);
    }
    //remove方法,必须实现。将某个Bean从当前的作用域移除。
    @Override
    public Object remove(String name) {
        Map<String, Object> objectMap = scopeMaps.get(curUserId);
        if(objectMap != null) {
            return objectMap.remove(name);
        }
        return null;
    }
    //选择实现,当某个特定的Bean从作用域中移除之后的回调函数。
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }
    //可选,返回某个指定Bean所处的上下文(context)
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
    //可选,返回当前这个作用域的ID
    @Override
    public String getConversationId() {
        return null;
    }

}

接下来是Bean的定义。scopeBean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <!--首先,我们的作用域对象也是一个Bean,这个Bean是默认的单例 -->
    <bean id="sampleScope" class="com.stduy.scope.ScopeSample"></bean>
    <!--然后,我们的简单Bean也要声明为一个Bean,但它的作用域就是我们自定义的作用域了-->
    <bean id="beanSample" class="com.stduy.scope.BeanSample" scope="sampleScope">
    </bean>
    <!--使用CustomScopeConfigure进行自定义作用域,其中key就是我们自定义作用域的名称。 -->
    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="sampleScope">
                    <ref bean="sampleScope" />
                </entry>
            </map>
        </property>
    </bean>

</beans>

然后是我们的主函数。ScopeMain.java

public class ScopeMain {
    public static Long curUserId = 1L;
    public static void main(String args[]) {
        ApplicationContext app = new ClassPathXmlApplicationContext("scopeSample.xml");

        BeanSample bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214619
        bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214619 未改变userId的时候,始终返回同一个Bean
        ScopeSample.curUserId = 2L; //改变userID

        bean = app.getBean(BeanSample.class);

        bean.printTime();
        //1478129214621 得到新的Bean。
    }
}

源代码解析

类图分析

ScopeBean

这个类图比较清晰地描述了BeanFactory和Scope之间的关系。DeafultListableBeanFactory持有0个到n个Scope,这种包含关系的声明,是在AbstractBeanFactory中完成的。

作用域注册代码解析

在前面我们提到过,我们是声明一个 CustomScopeConfigurer,并且将Scope田间道其构造函数参数中完成配置的。

那么,先来看看这个类的实现。

public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered 

原来是一个实现了BeanFactoryPostProcessor接口的Bean,那么接下来看postProcessBeanFactory方法。

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.scopes != null) {
            //对声明的所有scope进行处理(scope是一个String,Object的Map
            for (Map.Entry<String, Object> entry : this.scopes.entrySet()) {
                String scopeKey = entry.getKey();
                Object value = entry.getValue();
                //使用Scope Bean作为Value,可以直接注册,也就是上文的示例中提到的。
                if (value instanceof Scope) {
                    beanFactory.registerScope(scopeKey, (Scope) value);
                }
                //也可以使用Class进行注册,这个时候会对Class进行实例化。
                else if (value instanceof Class) {
                    Class<?> scopeClass = (Class<?>) value;
                    Assert.isAssignable(Scope.class, scopeClass);
                    beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
                }
                //还可以使用字符串进行实例化,使用字符串实例化的时候首先会把字符串转化为Class,后面的就同Class注册一样了。
                else if (value instanceof String) {
                    Class<?> scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader);
                    Assert.isAssignable(Scope.class, scopeClass);
                    beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
                }
                else {
                    throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" +
                            scopeKey + "] is not an instance of required type [" + Scope.class.getName() +
                            "] or a corresponding Class or String value indicating a Scope implementation");
                }
            }
        }
    }

然后我们再来看看AbstractBeanFactory中的registerScope做了什么事情。

public void registerScope(String scopeName, Scope scope) {
        Assert.notNull(scopeName, "Scope identifier must not be null");
        Assert.notNull(scope, "Scope must not be null");
        //不允许声明Spring中自带的两种Scope,singleton和prototype
        if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
            throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
        }
        //简单地把Scope对象加入到Map中。如果重复注册,则以最后一个注册的为准。
        Scope previous = this.scopes.put(scopeName, scope);
        //省略debug输出
    }

通过以上代码,作用域对象的注册就完成了。

作用域对象的生成

生成并注册了作用域之后,自然就是生成我们需要的Bean对象了。

也就是在AbstractBeanFactory.doGetBean方法中。因为前文已经包含了前面代码的分析了,这里就只展示跟Bean Scope相关的了。

String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
    //在这里,新建一个匿名内部类,实现ObjectFacotry接口。作为参数传入到Scope的get方法中。
    Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
        //直接调用了BeanFactory的beforeCreate,create和AfterCreate。
        @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);
}

从上面的代码我们可以看到,其实对于Bean的beforeCreate, afterCraete, Create,Scope并不进行管理,Scope只是负责对应的对象存取。

小结

这篇文章介绍了Spring的自定义Scope,也就是自定义的作用域来管理Bean。BeanFactory持有零个或多个Scope对象。在Scope对象中,我们不会对Bean的创建进行任何干预,只是负责根据一定的规则,对Bean进行存储和取用。

有了Scope Bean,我们就能够针对用户/用户组进行Bean存储,而不仅仅是所有人使用同一个Bean(singleton),又或者是全部人使用不同的Bean了(prototype)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值