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