Spring bean的作用域 -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

   

默认作用域

    Spring IOC容器中,默认的bean作用域有两种:

singleton,这种作用域的bean一旦创建后bean的生命周期和容器同步,只有容器关闭时才会销毁这种bean,相同id的bean在容器的生命周期内只会被实例化一次,通过相同的id向容器请求时会返回相同的实例prototype,这种作用域的bean不会交给容器托管,创建之后不会注册到容器,它的生老病死完全由应用层决定,每次通过相同的id向容器请求bean时,容器都会创建一个不同的实例

自定义作用域

    Spring框架提供了扩展机制让使用者自定义作用域,框架提供了两个关键的类实现自定义作用域

    org.springframework.beans.factory.config.Scope接口,需要实现以下几个关键方法:

Object get(String name, ObjectFactoryobjectFactory),使用者可以通过调用这个方法获取该作用域的bean实例,实现的大致逻辑应该,先作用域范围查找是否存在相应name的bean,如果存在直接返回,如果不存在调用objectFactory参数的getObject方法创建bean并且缓存在该作用域内Object remove(String name),使用者可以调用这个方法在该作用域下删除指定name的beanvoid registerDestructionCallback(String name, Runnable callback),注册bean的析构回调,作用域对象需要保存这写回调并且在bean被销毁触发这些回调Object resolveContextualObject(String key)

    org.springframework.beans.factory.config.CustomScopeConfigurer,这是一个BFPP,它的职责是把作用域注册到容器中

示例代码

    下面通过一个例子来演示怎么自定义作用域并且分析框架中的代码自定义作用域是怎么实现,这个自定义scope的功能是把bean缓存到一个LRU缓存中,当bean被踢出缓存时触发析构回调

    实现Scope接口,LRU缓存中最多只能放两个bean,被踢掉的bean会触犯析构回调,在removeEldestEntry方法中,析构回调保存在destructionCallback哈希表中:

public class LRUCacheScope implements Scope {	private class BeanCache extends LinkedHashMap<String, Object>{		private static final long serialVersionUID = -887300667768355251L;		@Override		protected boolean removeEldestEntry(Entry<String, Object>eldest) {			boolean flag = size() >maxBeanNumber;			if (flag) {				executeDesCallback(eldest.getKey());			}			return flag;		}	}	private static final int DEFAULT_MAX_BEAN_NUMBER = 2;	private Map<String, Object>beanCache = Collections			.synchronizedMap(new BeanCache());	private int maxBeanNumber;	private Map<String, Runnable>destructionCallback = new HashMap<String, Runnable>();	public LRUCacheScope() {		this(DEFAULT_MAX_BEAN_NUMBER);	}	public LRUCacheScope(int maxBeanNumber) {		super();		this.maxBeanNumber = maxBeanNumber;	}	@Override	public Object get(String name, ObjectFactory<?>objectFactory) {		Object bean = beanCache.get(name);		if (bean == null) {			bean = objectFactory.getObject();			beanCache.put(name, bean);		}		return bean;	}	@Override	public Object remove(String name) {		destructionCallback.remove(name);		return beanCache.remove(name);	}	@Override	public void registerDestructionCallback(String name, Runnable callback) {		destructionCallback.put(name, callback);	}	@Override	public Object resolveContextualObject(String key) {		return null;	}	@Override	public String getConversationId() {		return null;	}	private void executeDesCallback(String beanName) {		Runnable callBack = destructionCallback.get(beanName);		if (callBack != null) {			callBack.run();		}		destructionCallback.remove(beanName);	}}
定义CustomScopeConfigurer注册Scope,并且定义其它的测试bean

<b></bean><b></bean><b></bean><b><property name="scopes"><map><entry key="lruCache"><b></bean></entry></map></property></bean>
JUnit测试代码

@Testpublic void test() {	BeanFactory context = new ClassPathXmlApplicationContext(			"spring/beans/scope/scope.xml");	ScopedBean bean1 = (ScopedBean) context.getBean("scopedBean1");	ScopedBean bean11 = (ScopedBean) context.getBean("scopedBean1");	assertEquals(bean1, bean11);	ScopedBean bean2 = (ScopedBean) context.getBean("scopedBean2");	ScopedBean bean3 = (ScopedBean) context.getBean("scopedBean3");	bean11 = (ScopedBean) context.getBean("scopedBean1");	assertNotEquals(bean1, bean11);}

    执行测试代码发现代码执行通过,可以发现最后一次取出的scopedBean1和前面的scopedBean1已经不是一个实例了,

Spring bean的作用域

。查看控制台日志发现有如下信息:

22:28:48,738 DEBUG DefaultListableBeanFactory:432 - Creating instance of bean 'scopedBean1'22:28:48,738 DEBUG DefaultListableBeanFactory:460 - Finished creating instance of bean 'scopedBean1'22:28:48,738 DEBUG DefaultListableBeanFactory:432 - Creating instance of bean 'scopedBean2'22:28:48,738 DEBUG DefaultListableBeanFactory:460 - Finished creating instance of bean 'scopedBean2'22:28:48,738 DEBUG DefaultListableBeanFactory:432 - Creating instance of bean 'scopedBean3'22:28:48,738 DEBUG DefaultListableBeanFactory:460 - Finished creating instance of bean 'scopedBean3'22:28:48,738 DEBUG DisposableBeanAdapter:227 - Invoking destroy() on bean with name 'scopedBean1'destroy:spring.beans.scope.ScopedBean@18235ed22:28:48,738 DEBUG DefaultListableBeanFactory:432 - Creating instance of bean 'scopedBean1'22:28:48,738 DEBUG DefaultListableBeanFactory:460 - Finished creating instance of bean 'scopedBean1'22:28:48,738 DEBUG DisposableBeanAdapter:227 - Invoking destroy() on bean with name 'scopedBean2'destroy:spring.beans.scope.ScopedBean@1a28362
从日志可以看出在创建完scopedBean3并且添加到缓存中之后scopedBean1被踢掉了并且触发了析构回调,我们ScopedBean实现了DisposableBean接口,它的destroy方法被调用了:

   

public class ScopedBean implements DisposableBean {	@Override	public void destroy() throws Exception {		System.out.println("destroy:" + this);	}}

框架实现原理

    现在大致分析一下自定义作用域时如何实现的

    首先看下CustomScopeConfigurer类,看下它的postProcessBeanFactory方法:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {	if (this.scopes != null) {		for (Map.Entry<String, Object>entry : this.scopes.entrySet()) {			String scopeKey = entry.getKey();			Object value = entry.getValue();			if (value instanceof Scope) {				beanFactory.registerScope(scopeKey, (Scope) value);			}			else if (value instanceof Class) {				Class scopeClass = (Class) value;				Assert.isAssignable(Scope.class, scopeClass);				beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));			}			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");			}		}	}}
在这个方法中把所有scope都注册到beanFactory中,来看看bean工厂的registerScope方法,在AbstractBeanFactory类中,在这个方法中把所有的作用域对象都存储到了scopes哈希表属性中,作用域名称作为哈希表的key:

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'");	}	this.scopes.put(scopeName, scope);}
接下来看看bean的获取方法,在AbstractBeanFactory的doGetBean方法中,看doGetBean方法的代码片段:
if (mbd.isSingleton()) {		...	}else if (mbd.isPrototype()) {		...	}else {	String scopeName = mbd.getScope();	final Scope scope = this.scopes.get(scopeName);	if (scope == null) {		throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");	}	try {		Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {			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);	}}
可以看到获取自定义scope的bean调用了Scope的get方法,如果作用域没有缓存要找bean,那么会调用createBean来创建一个实例,这块创建bean实例的逻辑和prototype bean的是一样的,

电脑资料

Spring bean的作用域》(https://www.unjs.com)。

    下面来看看注册析构回调的代码,在AbstractBeanFactory类的registerDisposableBeanIfNecessary方法中,在bean创建(AbstractAutowireCapableBeanFactory的doCreateBean方法)完成之后:

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {	AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);	if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {		if (mbd.isSingleton()) {			// Register a DisposableBean implementation that performs all destruction			// work for the given bean: DestructionAwareBeanPostProcessors,			// DisposableBean interface, custom destroy method.			registerDisposableBean(beanName,					new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));		}		else {			// A bean with a custom scope...			Scope scope = this.scopes.get(mbd.getScope());			if (scope == null) {				throw new IllegalStateException("No Scope registered for scope '" + mbd.getScope() + "'");			}			scope.registerDestructionCallback(beanName,					new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));		}	}}
代码中可以看到自定义scope的bean创建完成之后会注册一个DisposableBeanAdapter析构回调到到Scope,看看DisposableBeanAdapter这个类的代码,在scope中执行回调时调用run方法,而run方法会直接调用destroy方法,主要代码在destroy方法中,从代码中可以看出在destroy方法中执行了所有的bean的析构回调包括DestructionAwareBeanPostProcessor析构处理器、DisposableBean的destroy、bean定义中的destroy-method。

public void destroy() {	if (this.beanPostProcessors != null && !this.beanPostProcessors.isEmpty()) {		for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {			processor.postProcessBeforeDestruction(this.bean, this.beanName);		}	}	if (this.invokeDisposableBean) {		if (logger.isDebugEnabled()) {			logger.debug("Invoking destroy() on bean with name '" + this.beanName + "'");		}		try {			if (System.getSecurityManager() != null) {				AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {					public Object run() throws Exception {						((DisposableBean) bean).destroy();						return null;					}				}, acc);			}			else {				((DisposableBean) bean).destroy();			}		}		catch (Throwable ex) {			String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";			if (logger.isDebugEnabled()) {				logger.warn(msg, ex);			}			else {				logger.warn(msg + ": " + ex);			}		}	}	if (this.destroyMethod != null) {		invokeCustomDestroyMethod(this.destroyMethod);	}	else if (this.destroyMethodName != null) {		Method methodToCall = determineDestroyMethod();		if (methodToCall != null) {			invokeCustomDestroyMethod(methodToCall);		}	}}

   

框架自定义作用域

    在Spring框架中也定义了一些自定义作用域:

web框架的request:bean在request范围内共享,实现类org.springframework.web.context.request.RequestScopeweb框架的session:bean在session范围内共享,实现类org.springframework.web.context.request.SessionScopeweb框架的application:ServletContextScope,bean在web应用共享,实现类org.springframework.web.context.support.ServletContextScopeorg.springframework.context.support.SimpleThreadScope:bean在线程内共享

最新文章