SSM項目的啓動流程深入解析

1 環境說明

本文的內容基於Tomcat9.0.10、Spring 4.3

2 Tomcat加載應用的順序

在我們正式介紹SSM項目是怎麼啓動之前,我們要先來簡單介紹一下Tomcat。很多人在介紹Tomcat的時候,都把Tomcat叫做Servlet容器,之所以這樣稱呼Tomcat,是因爲Tomcat就是一個圍繞着Servlet工作的一個服務器軟件,最直觀、也最簡單粗暴的解釋就是在Tomcat上運行的應用,都是基於servlet-api.jar這個jar包來開發的。打開tomcat安裝目錄下的conf文件夾中的server.xml,我們可以看到它的主要節點如下:

<Server>
	<Service>
		<Connector/>
		<Connector/>
		<Engine>
			<Host>
				<!--  現在常常使用自動部署,所以在配置文件中找不到Context元素,可以手動添加 -->
				<Context/>
			</Host>
		</Engine>
	</Service>
...
</Server>

其中,Engine、Host、Context都是繼承於一個叫做Container的類,此外還有一個比較特殊的類:Wrapper,這個類是跟項目中我們寫的每一個Servlet類(在SpringMVC中對應着Controller類中的一個方法)是一一對應的,在其具體實現類中,就是使用了一個Stack類來存儲所有其對應着的Servlet類的實例。大家記住這個結論就行,如果你真的想了解其中的具體細節,就需要去看一下Tomcat的源碼了,我後面如果有時間的話,可能也會出一個看Tomcat源碼的系列博客。爲了方便大家理解,我畫了一個圖來幫助大家理解。每個不同的部件我都用不同的顏色標了出來,通過下面的圖可以很清楚的看到,一個Tomcat就是最外面棕色的框,然後一個Tomcat可以有多個Service組件,一個Service可以有多個Connector組件和一個Engine組件,值得注意的是,不同的Connector的端口號都必須是唯一的,不能跟其他的端口重複,這個只要是學過計算機網絡的應該知道這個規則。而每個Engine組件對應着多個Host組件,每個Host組件又對應着多個Context組件,每個Context組件下面對應的則是Wrapper(一般情況下,大家理解爲Servlet也是沒有問題的)。這裏說一下Host(虛擬主機)的作用:運行多個Web應用(一個代表一個Web應用),並負責安裝、展開、啓動和結束每個Web應用。其中,Context組件對應着的則是我們所開發的應用。
在這裏插入圖片描述

Tomcat的各個組件
Tomcat在啓動時通過它自定義的類加載器去各個Host(虛擬主機)指定的路徑下加載相關的Web應用,然後將Web應用部署其到對應的Host虛擬主機中,如果Host主機設置了自動部署,Tomcat 在運行時會定期檢查是否有新的Web應用或Web應用是否進行了更新。

Tomca自定義的類加載器會按照如下的順序掃描並加載相關應用和檢查應用是否有更新:

  1. 掃描虛擬主機指定的xmlBase下的XML配置文件
  2. 掃描虛擬主機指定的appBase下的WAR文件
  3. 掃描虛擬主機指定的appBase下的應用目錄

3 SSM的啓動流程

當Tomcat把加載了應用之後,會先讀取項目中的web.xml的內容,讀取順序是從上到下一行一行的讀的,但是加載順序是先加載context-param元素中的值,然後加載listener元素中的值,接着是加載filter元素中的值,最後纔是servlet元素中的值。這裏給一下我的web.xml對應的內容

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--定義web應用的名稱-->
    <display-name>ssmdemo</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/spring-context.xml</param-value>
    </context-param>

    <!-- Spring上下文監聽器,用來加載Spring的上下文配置並初始化Spring -->
    <listener>
        <description>啓動spring容器</description>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- LOG4J上下文監聽器,用來加載LOG4J2的配置並初始化LOG4J -->
    <listener>
        <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
    </listener>

    <listener>
        <listener-class>com.yixiaojun.ssmdemo.listener.MySessionListener</listener-class>
    </listener>


    <!-- 字符編碼過濾器,將編碼改爲UTF-8-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <!--對所有的請求都進行過濾-->
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- SpringMVC前置控制器,攔截匹配的請求,把攔截下來的請求,根據相應的規則分發到目標Controller來處理-->
    <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定路徑SpringMVC上下文配置路徑,也可以使用默認的規則,即:/WEB-INF/<servlet-name>-servlet.xml,如spring-mvc-servlet.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/spring-mvc.xml</param-value>
        </init-param>
        <!-- 隨web應用啓動而啓動 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-mvc</servlet-name>
        <!--指定所有請求都通過DispatcherServlet來處理-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

所以我們就可以得出SSM的加載流程了,具體流程如下圖:
在這裏插入圖片描述
我們也可以通過觀看日誌的輸出順序來查看項目的啓動流程是怎麼樣的,下面是我從一個SSM項目的啓動過程中抽取的一部分關鍵日誌,說明就以註釋形式寫在裏面了。

#Tomcat啓動成功後,開始連接到Tomcat
Connected to server

#發現項目的war包,開始部署項目
Artifact ssmdemo:war exploded: Artifact is being deployed, please wait...

#加載監聽器中配置的org.springframework.web.context.ContextLoaderListener的父類ContextLoader中的initWebApplicationContext 方法,初始化應用上下文
org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started

#初始化SpringIOC容器
org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing Root WebApplicationContext

#加載從context-param中配置的contextConfigLocation的值所對應的文件spring-context.xml
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/spring-context.xml]

#加載spring-context.xml中配置的/mybatis-config.xml
org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:496) - Parsed configuration file: 'ServletContext resource [/WEB-INF/mybatis/mybatis-config.xml]'

#加載spring-context.xml指定的mapper目錄下的xml文件
org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:528) - Parsed mapper file: 'file [D:\Project\ssmdemo\target\ssmdemo\WEB-INF\classes\mapper\UserMapper.xml]'

# 應用初始化完成
org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization completed in 1605 ms

#開始開始加載Servlet配置的內容,即調用org.springframework.web.servlet.DispatcherServlet的父類FrameworkServlet 的initServletBean
org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'spring-mvc': initialization started

#初始化SpringMVC的IOC容器
org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing WebApplicationContext for namespace 'spring-mvc-servlet': startup date [Wed May 27 00:25:46 CST 2020]; parent: Root WebApplicationContext

#通過servlet容器配置的contextConfigLocation所對應的的文件來生成SpirngMVC的BeanDefinitions
org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/spring-mvc.xml]

#生成靜態資源的映射url路徑
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping.registerHandler Mapped URL path [/js/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'

#生成Controller類的中的方法的映射url路徑
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.register Mapped "{[/user/login],methods=[GET]}" onto public void com.yixiaojun.ssmdemo.controller.UserController.login(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException

#初始化AOP要攔截的切點
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'spring-mvc-servlet': startup date [Wed May 27 00:25:46 CST 2020]; parent: Root WebApplicationContext

#SpirngMVC初始化完成
org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'spring-mvc': initialization completed in 1584 ms

4 部分Spring相關源碼

4.1ContextLoaderListener.java

package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader
implements ServletContextListener  {
	public void contextInitialized(ServletContextEvent event)  {
		this.initWebApplicationContext(event.getServletContext());
	}

}

4.2 ContextLoader.java

package org.springframework.web.context;

public class ContextLoader {

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	this.context  =  this.createWebApplicationContext(servletContext);
	this.configureAndRefreshWebApplicationContext(cwac, servletContext);
	//將初始化之後的上下文存到servletContext中
	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
	return this.context;
	}
	
	protected  void configureAndRefreshWebApplicationContext( ConfigurableWebApplicationContext wac,ServletContext sc) {
		wac.setServletContext(sc);
		//從ServletContext獲取contextConfigLocation的值
		configLocationParam  =  sc.getInitParameter("contextConfigLocation");
		if(configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
			}
			//刷新XmlWebApplicationContext(初始化SpringIOC容器)
			wac.refresh();
	}
}

4.3 AbstractApplicationContext.java

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
   public void refresh() throws BeansException, IllegalStateException {
   	Object var1 = this.startupShutdownMonitor;
   	synchronized(this.startupShutdownMonitor){
   	//刷新前準備工作,記錄容器啓動時間和標記
   	this.prepareRefresh();
   	//創建bean工廠,讀取XML配置裏的Bean信息,裝載XML配置裏面的BeanDefinition
   	ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
   	//準備Bean工廠需要的上下文信息,包括對 @Autowired和@Qualifier註解的屬性注入
   	this.prepareBeanFactory(beanFactory); 
   	try {
   		this.postProcessBeanFactory(beanFactory);
   		//激活各種  BeanFactory  處理器
   		this.invokeBeanFactoryPostProcessors(beanFactory);
   		//註冊各種BeanPostProcessors,用於在bean被初始化時進行攔截,進行額外初始化操作,具體可以查看Bean的生命週期
   		this.registerBeanPostProcessors(beanFactory); 
   		this.initMessageSource(); 
   		this.initApplicationEventMulticaster();
   		this.onRefresh();
   		this.registerListeners();
   		// 初始化所有未初始化的非懶加載的單例Bean
   		this.finishBeanFactoryInitialization(beanFactory);
   		this.finishRefresh();
   		}catch(BeansException  var9){
   		}
   		this.destroyBeans();
   		this.cancelRefresh(var9); throw  var9;
   		} finally  {
   		this.resetCommonCaches();
   		}
   	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章