引入
為什麼突然說一下Spring啟動原理呢,因為之前面試的時候,回答的那可謂是坑坑窪窪,前前後後,補補貼貼。。。
總而言之就是不行,再次看一下源碼發掘一下。。。
在Spring Boot還沒有廣泛到家家在用的時候,我們都還在書寫繁瑣的配置,什麼web.xml、spring.xml、bean.xml等等。雖然現在很少,可以說幾乎沒有企業在去使用Spring的老一套,而會去使用Spring Boot約定大于配置來進行快速開發,但是,Spring的也要去學習,去挖掘,畢竟是我們Java程式員的基礎呀。
spring的啟動是建築在servlet容器之上的,所有web工程的初始位置就是web.xml,它配置了servlet的上下文(context)和監聽器(Listener)
web.xml
<!--上下文監聽器,用于監聽servlet的啟動過程-->
<listener>
<description>ServletContextListener</description>
<!--這裡是自定義監聽器,個性化定制項目啟動提示-->
<listener-class>com.trace.app.framework.listeners.ApplicationListener</listener-class>
</listener>
<!--dispatcherServlet的配置,這個servlet主要用于前端控制,這是springMVC的基礎-->
<servlet>
<servlet-name>service_dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/services/service_dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--spring資源上下文定義,在指定位址找到spring的xml配置檔案-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/application_context.xml</param-value>
</context-param>
<!--spring的上下文監聽器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--Session監聽器,Session作為公共資源存在上下文資源當中,這裡也是自定義監聽器-->
<listener>
<listener-class>
com.trace.app.framework.listeners.MySessionListener
</listener-class>
</listener>
複制
Spring啟動過程
spring的上下文監聽器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/application_context.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
複制
spring的啟動其實就是IOC容器的啟動過程,通過上述的第一段配置
<context-param>
是初始化上下文,然後通過後一段的的
<listener>
來加載配置檔案,其中調用的spring包中的
ContextLoaderListener
這個上下文監聽器,
ContextLoaderListener
是一個實作了ServletContextListener接口的監聽器,他的父類是 ContextLoader,在啟動項目時會觸發contextInitialized上下文初始化方法。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
複制
調用了父類ContextLoader的initWebApplicationContext(event.getServletContext());方法,很顯然,這是對ApplicationContext的初始化方法,也就是到這裡正是進入了springIOC的初始化。
接下來看一下initWebApplicationContext(event.getServletContext())的工作:
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
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);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
複制
總結:
- 建立WebApplicationContext。
- 加載對應的spring配置檔案中的Bean。
- 将WebApplicationContext放入ServletContext(Java Web的全局變量)中。
接下來,來到了
configureAndRefreshWebApplicationContext()
方法
作用:
就是用來加載spring配置檔案中的Bean執行個體的。這個方法于封裝ApplicationContext資料并且初始化所有相關Bean對象。它會從
web.xml
中讀取名為 contextConfigLocation的配置,這就是spring xml資料源設定,然後放到ApplicationContext中,最後調用傳說中的
refresh
方法執行所有Java對象的建立。
總結:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwIjNx8CX39CXy8CXycXZpZVZnFWbp9zZlBnauETZjdjZwIWNzIjN1kTZwkDM2UWOjVjM1ATZjBDM5UmMvw1M0UDM4ETOtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.jpeg)
總結
- 首先對于一個web應用,需要部署到web容器中,web容器提供了一個全局的上下文環境,ServletContext,SpringIOC的宿主環境。
-
其次,在web容器啟動時,觸發容器初始化,web.xml中提供的有ContextLoaderListener監聽器會監聽這個事件,初始化方法contextInitialized被調用,初始化spring上下文
WebApplicationContext接口,實作類時XmlWebApplicationContext即SpringIOC容器,對應的Bean定義是有context-param标簽定義指定,然後存儲到ServletContext中,友善擷取。
-
ContextLoaderListener監聽初始化完成後,開始初始化web.xml中配置的Servlet,指DisapatchServlet前端控制器,用來比對,轉發,處理每個Servlet請求,DisaptchServlet初始化時會建立自己的IOC上下文,用來持有Spring MVC的相關bean。
首先會從之前初始化存儲在ServletContext中的上下文左右parent上下文,再初始化自己的上下文,大概的工作就是初始化處理器映射、視圖解析等。這個servlet自己持有的上下文預設實作類也是xmlWebApplicationContext。然後存儲到ServletContext。每個Servlet擁有自己的上下文,也會共享parent的上下文。
下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。
下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。
下期講解refresh()做了什麼,以及Spring Boot的啟動原理,敬請觀看,謝謝。。。