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();
   		}
   	}
}

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