一、前言
上一篇《Spring之依赖注入》,清楚了Spring中bean的装配过程,bean在被Spring容器装配时,作用域(Scope)分为singleton、prototype、request、session,其中request和session适用于WebApplicationContext环境。
- singleton——单例模式,Spring的容器中只有一个实例,有Spring容器创建,创建时间点,分为启动创建和延迟加载,根据bean对象配置的属性lazy-init属性决定,默认为false,配置为true时,会在第一次调用时创建该对象。
- prototype——非单例模式,每次从Spring容器中调用时,都返回一个新实例。创建bean对象的时间点为bean被调用时。所以都是lazy-init。
- request——每次request请求都会创建一个新的bean。
- session——同一个session共享一个bean。
二、Bean的作用域
在Spring中,由Spring IoC容器所管理的对象,称之为bean,由IoC容器初始化、装配及管理。
bean的作用域类别包含:singleton、prototype、request、session。即常说的scope类别。
2.1 singleton
Spring装配bean默认类别,单例模式,Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
2.2 Prototype
多实例对象模式,即每次请求该bean对象时,都会创建一个新的bean实例。
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
2.3 Request
表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
2.4 Session
表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
三、不同作用域的Bean的生命周期
Bean实例,生命周期的执行过程如下:
- Spring对bean进行实例化,默认bean时Singleton
- Spring对bean进行依赖注入,
- 如果bean实现了BeanNameAware接口,Spring将bean的名称,传给setBeanName()方法。
- 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory(),将beanFactoryc实例传入
- 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将应用的上下文引用传入bean中。
- 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization()方法将被调用。
- 如果bean中有方法添加了@PostConstruct注解,那么该方法将被调用。
- 如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet()接口方法,类似bean在xml中配置,使用init-method属性声明了初始化方法,该方法也会被调用。
- 此时bean已经就绪,可以被应用程序使用,它们将一直在应用山下文中,直到Spring容器销毁。
- 如果bean中有方法使用了@PreDestory注解,在Spring容器销毁时,将被调用。
- 如果bean中实现了DisposableBean接口,Spring容器在销毁时,将调用它的destroy()方法。同样的xml配置了destroy-method属性,一个原理。
Aware接口,正常情况下不需要使用,除非不得不用,用的比较多的也就是为bean起别名。
3.1 singleton模式
scope="singleton",是单例模式,Spring容器启动时会初始化,除非配置了lazy-init="true",配置了延迟加载后,会在第一次调用时创建bean实例。该bean实例的生命周期为Spring容器的生命,会一直在Spring容器内,期间由Spring容器管理bean实例,直到Spring容器关闭。
ps: Spring容器关闭,也是将初始化的所有bean对象引用只想null,有JVM回收。
<bean id="hello" class="com.cc.pojo.hello" lazy-init="true"/>
3.2 prototype模式
每次调用bean实例时,Spring容器都会创建一个bean对象,并返回给调用者。创建完对象返回后,Spring容器将不在管理bean对象。有调用者自己控制对象的销毁,最常见的,就是调用程序使用完,由JVM垃圾回收掉。
<bean id="hello" class="com.cc.Hello" scope="prototype" init-method="init" destroy-method="destory"/>
3.3 request模式
一次request请求都会创建一个新的bean,当request请求结束,bean结束,有JVM回收。
3.4 session模式
用户的session会共享一个bean,待session断开,bean生命结束,会被JVM回收。
四、思考
回忆前面的样例,每次向获取bean对象时,都是下面的代码方式。
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
每次都会产生一个上下文的引用对象,这样不太好,其实只需要在程序启动时,产生这个工厂就可以了,后面获取bean,都调用同样的工厂实例。
解决这个问题,也比较简单,在web容器启动的时候,将ApplicationContext转移到ServletContext中,因为Web应用中,所有的Servlet都共享一个ServletContext对象。使用ServletContextListener去监听ServletContext,当web应用启动的时候,将ApplicationContext加载到ServletContext中。
可以看下源码的实现,在加载类的初始化方法中,参考代码中注释
package org.springframework.web.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* 初始化方法加入ApplicationContext
*/
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
初始化方法,注释部分是将ApplicationContext上下文信息加入到ServletContext,其中键值为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
/**
* 重点,将context加入到servletContext,key值为:
* WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
} catch (Error | RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
}
}
}
接着,还有点小说明,在取出上述以key值WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE存储在ServletContext中的ApplicationContext时,我们却不需要感知这个复杂的key值,WebApplicationContextUtil工具类做了封装,如下:
WebApplicationContext context= WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
可以看下getWebApplicationContext()方法的实现,就可以明白了,封装方法自动传入了存储在ServletContext中ApplicationContext对象的key值。
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}