前言
這個系列是java spring mvc 源碼閱讀與分析的一個系列
閱讀源碼分支為
spring初始化流程
spring的初始化流程大概可以分為幾個階段
- web容器啟動
- spring bean容器初始化 (通過
ContextLoaderListener
啟動,是根上下文) - DispatcherServlet初始化(初始化另一個上下文,這個上下文是根上下文的一個子上下文)
但是這幾個階段也不是固定的
首先要科普的一點spring webmvc容器是分層的,有一個根容器,每一個DispatcherServlet擁有一個子容器
ContextLoaderListener
這一步不是必選的,使用父子容器的設計也是方便除了webmvc使用其他使用spring容器的地方。只使用自容器也是可以的。
文檔中介紹ContextLoaderListener
:
Non-Spring MVC implementations are preferable for some projects. Many teams expect to leverage their existing investment in skills and tools, for example with JSF. If you do not want to use Spring’s Web MVC, but intend to leverage other solutions that Spring offers, you can integrate the web MVC framework of your choice with Spring easily. Simply start up a Spring root application context through its ContextLoaderListener, and access it through its ServletContext attribute (or Spring’s respective helper method) from within any action object. No "plug-ins" are involved, so no dedicated integration is necessary. From the web layer’s point of view, you simply use Spring as a library, with the root application context instance as the entry point. Your registered beans and Spring’s services can be at your fingertips even without Spring’s Web MVC. Spring does not compete with other web frameworks in this scenario. It simply addresses the many areas that the pure web MVC frameworks do not, from bean configuration to data access and transaction handling. So you can enrich your application with a Spring middle tier and/or data access tier, even if you just want to use, for example, the transaction abstraction with JDBC or Hibernate.
翻譯過來大致是說
對于某些項目,非Spring MVC實現更為可取。許多團隊希望利用他們現有的技能和工具投資,例如使用JSF。如果您不想使用Spring的Web MVC,但打算利用Spring提供的其他解決方案,您可以輕松地將您選擇的Web MVC框架與Spring集成。通過其ContextLoaderListener簡單地啟動一個Spring根應用程序上下文,并通過任何動作對象中的ServletContext屬性(或Spring的各自的幫助方法)訪問它。沒有涉及“插件”,因此不需要專門的集成。從Web層的角度來看,您只需使用Spring作為庫,將根應用程序上下文實例作為入口點。即使沒有Spring的Web MVC,您的注冊bean和Spring的服務也可以在您的指尖。在這種情況下,Spring不會與其他Web框架競爭。它簡單地解決了純Web MVC框架從bean配置到數據訪問和事務處理的許多方面。所以您可以使用Spring中間層和/或數據訪問層來豐富您的應用程序,即使您只想使用JDBC或Hibernate的事務抽象。
一些重要的類與方法
org.springframework.web.context.support.XmlWebApplicationContext
這個類作文默認的webmvc 上下文類,其繼承結構如下
比較復雜,根據這個實現接口的圖,可以看出來這個類可以加載資源文件(實現
ResourceLoader
接口)、區分上下級的bean容器(實現BeanFactory
接口和HierarchicalBeanFactory
接口),可以發布和接收消息(實現ApplicationEventPublisher
和MessageSource
接口)
這樣就明白這個類的大致作用了
根容器初始化
web.xml中配置listener啟動
一般在web.xml中我們需要配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext-waimai-*.xml,
classpath:webmvc-config.xml,
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
注冊了一個ContextLoaderListener
listener,這個
在web環境啟動的生命周期中,會向 ContextLoaderListener 這個listener會接收到一條消息,接收到消息后調用 ContextLoader#initWebApplicationContext
方法。ContextLoaderListener
的繼承關系如下
具體執行的是定義在父類ContextLoader
中的方法
initWebApplicationContext
1、調用 initWebApplicationContext
initWebApplicationContext
這個方法的核心代碼如下
//如果context是空則創建一個context
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//設置根容器
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);
}
上面這段代碼中可以看到兩個比較關鍵的地方,一個是,調用createWebApplicationContext
創建context。另外一個是context創建完成,需要調用configureAndRefreshWebApplicationContext
來設置和刷新各種配置項
1.1 調用 createWebApplicationContext
createWebApplicationContext
用來生成一個具體的context,代碼如下
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//決定類,如果不通過參數指定默認值則使用定義在org/springframework/web/context/ContextLoader.properties里默認值,這個
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
1.2 調用 configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//CONFIG_LOCATION_PARAM 的默認值是 contextConfigLocation 也就是我們在web.xml中配置的xml位置
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// use in any post-processing or initialization that occurs below prior to #refresh 根據環境獲取數據配置
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
這個方法中依次做了這么幾件事
- 為bean容器(也就是XmlWebApplicationContext)設置id
- 從web.xml中讀取
contextConfigLocation
配置,并放到容器中 - 讀取環境配置文件并初始化
- 通過調用
customizeContext
定制bean容器其他初始化,方式是通過讀取globalInitializerClasses
和contextInitializerClasses
參數配置的類,實例化這些類(這些類需要實現ApplicationContextInitializer
接口)調用initialize
方法 - 調用bean容器的
refresh
方法。
在spring中,ApplicationContext
接口明確說明,所有的上下文對象都應該是只讀的(為了線程安全理所當然),但是這樣的類可以被刷新,可以看到``的繼承關系圖中的祖先類中存在AbstractRefreshableApplicationContext
,用于刷新整個上下文
2. 調用refresh
刷新上下文對象,這個方法定義在 AbstractApplicationContext
中,代碼如下
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
為了線程安全,這個方法是同步執行的,并且是按次序分別調用幾個函數,這里簡潔的代碼設計很漂亮,值得學習。
我們可以看到依次執行了這幾個方法
- prepareRefresh();
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
- prepareBeanFactory(beanFactory);
- postProcessBeanFactory(beanFactory);
- invokeBeanFactoryPostProcessors(beanFactory);
- registerBeanPostProcessors(beanFactory);
- initMessageSource();
- initApplicationEventMulticaster();
- onRefresh();
- registerListeners();
- finishBeanFactoryInitialization(beanFactory);
- finishRefresh();
下面我們具體分析下執行過程
2.1 prepareRefresh()
第一步,執行prepareRefresh();
,這一步主要配置開始時間、active flag 還有其他初始化配置文件
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();
}
2.2 執行ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
獲取
obtainFreshBeanFactory 這個方法如下
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//通知子類刷新bean Factory
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
看下子類AbstractRefreshableApplicationContext
如何執行刷refreshBeanFactory()
,代碼如下
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//創建一個默認beanFactory DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//設置序列化id
beanFactory.setSerializationId(getId());
//初始化BeanFactory
customizeBeanFactory(beanFactory);
//重要 載入bean定義
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
上述代碼中最重要的是loadBeanDefinitions
方法,XmlWebApplicationContext
實現的loadBeanDefinitions
方法如下
載入bean定義
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
其中loadBeanDefinitions
是核心方法,里面包含了讀取xml文件,從xml讀取配置,生成bean并且注入。這里比較復雜,之后專門來分析一下吧 [todo]
這一步完成后,bean已經完成刷新,成功從xml文件中取出內容加入容器中。
完成這一步之后,父容器初始化完成,如果配置了DispatchServlet相關配置的開始進行子容器的初始化。