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

来源:互联网 发布:程序员出差是去干什么 编辑:程序博客网 时间:2024/06/05 12:01

  • 引言
  • 示例
  • 源代码解析
    • 类图分析
    • 作用域注册代码解析
    • 作用域对象的生成
  • 小结

引言

在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)。

0 0
原创粉丝点击