使用spring的小伙伴都知道,一个bean实例是有其作用于的,比如:单例(singleton),多例(prototype),这些都是spring容器管理实例的一种方式,那么在spring中有没有办法按照我们自己的意愿去管理我们的实例呢?答案是肯定的,那就是spring中自定义scope。
那么如何自定义呢?
先看一个简单的bean标签:
<bean class="com.zhuguang.jack.scope.CustomScopeBean" id="customScopeBean" scope="jackScope"/>
其中我定义了一个scope=“JackScope”的自定义bean作用域,那么如何让这个作用域生效呢?
1、定义一个类实现BeanFactoryPostProcessor
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("jackScope",new CustomScope());
}
}
2、定义一个类实现Scope
public class CustomScope implements Scope {
private ThreadLocal local = new ThreadLocal();
/*
* 这个方法就是自己管理bean
* */
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("=============CustomScope========");
if(local.get() != null) {
return local.get();
} else {
//这个方法就是掉createbean方法获得一个实例
Object object = objectFactory.getObject();
local.set(object);
return object;
}
}
@Override
public Object remove(String name) {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
在get方法中我们定义了获取bean的方法,我们定义了一个ThreadLocal,不同的用户获取到的bean的实例不一样,也就是说实例和用户绑定了,这样就不会有线程安全问题,当然这只是一个我们自己管理bean的例子,prototype也是这样的管理方式。
3、测试
public static void customScopeTest(final ApplicationContext context) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 2; i1++) {
System.out.println(Thread.currentThread().getId() + "-->" + context.getBean("customScopeBean"));
}
}
}).start();
}
}
测试结果:
从测试结果来看,相同线程ID打印出来的实例的hashCode是相同的,所以达到了我们的设计预期。OK,自定义scope就搞定了,那么为什么要这么做呢?来我们看源码。
1、首先为什么我们要定义一个类实现Scope接口
我们把spring源码定位到bean实例化的地方:
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
getBean(dep);
}
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
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();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@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);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
读过spring源码的都知道,doGetBean方法就是spring对bean的实例化过程调用的方法,其中有单例实例、多例实例化
request、session、自定义scope作用的实例化。
自定义的scope,我们把目光放到最后的else里面
else {
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 {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@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);
}
}
那么什么情况下,代码会走到这个else呢?就如最前面我的那个例子哪里的bean标签定义:
<bean class="com.zhuguang.jack.scope.CustomScopeBean" id="customScopeBean" scope="jackScope"/>
如果作用域既不是单例也不是多例,那么bean标签解析封装成BeanDefinition对象时,mbd.isSingleton(),mbd.isPrototype()这两个属性都是为false的。所以就会走到这个else。
我们先看前两行代码:
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
第一行是获取bean标签中配置的scopename,既 “jackScope”.
第二行代码,从scopes容器中根据scopeName获取到一个Scope类型的实例,这里就是关键。那么scopes容器中有些什么样的东西呢?
在AbstractBeanFactory类中定义了scopes容器变量。
/** Map from scope identifier String to corresponding Scope */
private final Map<String, Scope> scopes = new LinkedHashMap<String, Scope>(8);
那么这个容器什么时候初始化的呢?
其实这个容器的初始化,需要我们自己动手调用方法的。
AbstractBeanFactory中的如下方法就是scopes容器初始化的方法,需要我们自己调用的。
@Override
public void registerScope(String scopeName, Scope scope) {
Assert.notNull(scopeName, "Scope identifier must not be null");
Assert.notNull(scope, "Scope must not be null");
if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
}
Scope previous = this.scopes.put(scopeName, scope);
if (previous != null && previous != scope) {
if (logger.isInfoEnabled()) {
logger.info("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
}
}
}
Scope previous = this.scopes.put(scopeName, scope);
这行代码就是对scopes容器的初始化,那么这个方法什么时候掉的呢。
问题就很简单了,我们就只要获取到AbstractBeanFactory类的实例就 可以调到这个registerScope方法。
获取AbstractBeanFactory,即BeanFactory容器对象,我们就只要实现
BeanFactoryPostProcessor接口就可以了:
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("jackScope",new CustomScope());
}
}
实现BeanFactoryPostProcessor接口,然后spring就会调到这个postProcessBeanFactory方法,把BeanFactory对象传进来,这样我们就拿到了容器对象,就可以调用registerScope方法了。至于为什么实现BeanFactoryPostProcessor接口就会被spring调到
postProcessBeanFactory方法,请读源码:refresh()方法中的:invokeBeanFactoryPostProcessors(beanFactory);方法。