在spring中,那些组成用用程序的主体以及由springIOC容器所管理的对象称之为bean。也就是说bean是由IOC容器初始化、装配以及管理的对象,除此之外,bean就跟普通的对象一样。然而bean的定义以及bean之间的相互依赖关系是通过配置元数据来描述。
Spring中的bean默认都是单例的,这些单例的Bean在多线程中是怎么保证线程安全的呢?
例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程处理请求,引入spring之后,每个Action都是单例的,那么对于spring托管的单例Service Bean,如何保证其安全呢?Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在这个容器中只有一个,Java的单例是基于JVM的,每个JVM内只有一个实例。Spring中除了单例模式还有其他的作用域。
一、Bean的作用域
创建一个bean的定义,其实就是什么时候创建一个ban,在spring框架中支持五种作用域,分别如下:
或者默认不写scope。同时也支持注解的方式:
@Service
@Scope("singleton")
public class BeanHello {}
2、prototype(每次请求都会创建一个新的bean实例)
当设置bean的作用域为prototype时,说明一个bean会对应多个实例对象,也就是在getBean或者注入的方式都会创建一个新的bean实例。使用prototype作用域时,容器中并没有对这个bean进行实例化,而是每次调用的时候再去创建,那什么时候使用prototype呢?对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。配置方式也是两种:
注解:
@Service
@Scope("prototype")
public class BeanHello {}
后面的三种作用域就不一一讲解了,通常都是通过xml或者注解的方式进行配置。那么这几种作用域在spring源码中是怎么实现的呢?
二、源码
Spring中有一个很重要的类BeanDefinition,这个包含了bean的信息,在bean初始化的过程将信息封装到这个类中。
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
boolean isSingleton();
boolean isPrototype();
String getScope();
}
从中可以得出,在初始化bean实例的时候就会根据BeanDefinition来判断是singleton还是prototype。但是如果既不是singleton也是prototype那怎么处理呢?在spring中有具体的逻辑来处理其他情况,先看一个Scope类。
public interface Scope {
//通过一个名称获取Bean实例
Object get(String name, ObjectFactory> objectFactory);
//销毁指定名称的Bean
Object remove(String name);
//为指定名称的Bean注册销毁时的回调函数
void registerDestructionCallback(String name, Runnable callback);
Object resolveContextualObject(String key);
String getConversationId();
}
在spring容器启动的时候会对多个scope进行注册,从源码中也可以看出来request、session、global session是web项目特有的作用域。在容器中分别对这些作用域进行注册。
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
// 注册几个web项目特有的作用域
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
RequestScope、SessionScope都是Scope接口的子类,同时也是AbstractRequestAttributesScope的子类,AbstractRequestAttributesScope实现了获取bean的方法,也就是说会根据不同的作用域初始化bean。
设置了作用域时候在初始化的时候如何根据不同的作用域获取bean呢?
// Create bean instance. 单例模式
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory
那么除了singleton和prototype初始化bean的逻辑就在Scope的子类AbstractRequestAttributesScope 中实现了。
public abstract class AbstractRequestAttributesScope implements Scope {
/**
* 初始化bean
*/
public Object get(String name, ObjectFactory objectFactory) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null) {
// 如果这个对象不存在,那么创建一个同时保存到attributes
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
}
return scopedObject;
}
public Object remove(String name) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject != null) {
attributes.removeAttribute(name, getScope());
return scopedObject;
}
else {
return null;
}
}
public void registerDestructionCallback(String name, Runnable callback) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.registerDestructionCallback(name, callback, getScope());
}
public Object resolveContextualObject(String key) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
return attributes.resolveReference(key);
}
/**
* Template method that determines the actual target scope.
* @return the target scope, in the form of an appropriate
* {@link RequestAttributes} constant
* @see RequestAttributes#SCOPE_REQUEST
* @see RequestAttributes#SCOPE_SESSION
* @see RequestAttributes#SCOPE_GLOBAL_SESSION
*/
protected abstract int getScope();
}
主要是从RequestContextHolder获取attributes,attributes中包含了对应的bean,那么RequestContextHolder中的bean又是怎么获取到的?
/**
* 获取当前线程绑定的RequestAttributes
*/
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
/**
* 获取当前线程绑定的RequestAttributes
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("");
}
}
return attributes;
}
获取当前线程绑定的RequestAttributes ,如果RequestAttributes 为null,说明没有HttpRequest请求线程存在,也就是说当前容器还在初始化阶段,在容器初始化阶段就尝试实例化一个与HttpRequest绑定的Bean会报错,也就说scope为request或session的Bean不能在容器初始化阶段实例化,不能直接注入scope为singleton的Bean中。RequestContextHolder 有两个ThreadLocal类型的静态成员,专门用于存储Http请求的信息。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 创建request
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, startTime, failureCause);
}
}
protected ServletRequestAttributes buildRequestAttributes(
HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {
if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
return new ServletRequestAttributes(request);
}
else {
return null; // preserve the pre-bound RequestAttributes instance
}
}
private void initContextHolders(
HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
//将当前Http请求的Request,Response的相关信息设置到ThreadLocal对象中。
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
// 请求结束,清空threadLocal信息
private void resetContextHolders(HttpServletRequest request,
LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
}
这样spring的作用域就实现了。