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。 }}
源代码解析
类图分析
这个类图比较清晰地描述了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)。
- Spring学习笔记——Spring Scope(作用域)详解
- Spring-bean作用域scope详解
- Spring-bean作用域scope详解
- Spring-bean作用域scope详解
- Spring-bean作用域scope详解
- Spring Scope属性作用域详解
- Spring-bean作用域scope详解
- spring scope 作用域
- Spring scope 学习笔记
- Spring Scope 学习笔记
- Spring scope 学习笔记
- Spring学习笔记03--bean的作用域(scope属性)
- Spring——scope详解
- Spring(四):Scope作用域
- Spring bean的作用域(scope)
- Spring的IOC容器—scope作用域
- spring中scope作用域
- spring中scope作用域
- 当JavaScript遇上UINT64
- MVC层页面报错:没有找到方法: 'Void System.Data.Entity.Infrastructure.DbEntityEntry`1.set_State(System.Data.Enti
- 标准地形图制作系列--接图表的制作方法
- MySQL数据库数据存放位置修改
- 学习数据结构的一个好网站,只能作为补充
- Spring学习笔记——Spring Scope(作用域)详解
- 输入一个正整数,判断其是否为回文数
- 如何获取ffmpeg返回的错误信息
- 删除Oracle锁
- 第十周:C语言:计算个人所得税
- 使用Glide加载图片
- 输出10000以内的所有回文数
- 判断回文数
- 关于计算机和变量