Spring MVC 启动过程源码分析

今天小编尝试从源码层面上对Spring mvc的初始化过程进行分析,一块儿揭开Spring mvc的真实面纱,也许咱们都已经学会使用spring mvc,或者说对spring mvc的原理在理论上已经能滚瓜烂熟。在开始以前,这可能须要你掌握Java EE的一些基本知识,好比说咱们要先学会Java EE 的Servlet技术规范,由于Spring mvc框架实现,底层是遵循Servlet规范的。前端

在开始源码分析以前,咱们可能须要一个简单的案例工程,不慌,小编已经安排好了:java

样例工程下载地址 : github.com/SmallerCode…git

那下面就让咱们开始吧!github

1、前置知识

你们都知道,咱们在使用spring mvc时一般会在web.xml文件中作以下配置:web

web.xmlspring

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    
    
    <!-- 上下文参数,在监听器中被使用 -->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
    	<param-value>
        	classpath:applicationContext.xml
        </param-value>
    </context-param>
    
    
    <!-- 监听器配置 -->
    <listener>
    	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- 前端控制器配置 -->
    <servlet>
    	<servlet-name>dispatcher</servlet-name>
    	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	<init-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:applicationContext-mvc.xml</param-value>
    	</init-param>
    	<load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    	<servlet-name>dispatcher</servlet-name>
    	<url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
复制代码

上面的配置总结起来有几点内容,分别是:spring-mvc

  • 一、配置Spring Web上下文监听器,该监听器同时是Spring mvc启动的入口,至于为何,后面第二节将会讲到
  • 二、前端控制器DispatcherServlet,该控制器是Spring mvc处理各类请求的入口及处理器

当咱们将spring mvc应用部署到tomcat时,当你不配置任何的context-paramlistener参数,只配置一个DispatcherServlet时,那么tomcat在启动的时候是不会初始化spring web上下文的,换句话说,tomcat是不会初始化spring框架的,由于你并无告诉它们spring的配置文件放在什么地方,以及怎么去加载。因此listener监听器帮了咱们这个忙,那么为何配置监听器以后就能够告诉tomcat怎么去加载呢?由于listener是实现了servlet技术规范的监听器组件,tomcat在启动时会先加载web.xml中是否有servlet监听器存在,有则启动它们。ContextLoaderListener是spring框架对servlet监听器的一个封装,本质上仍是一个servlet监听器,因此会被执行,但因为ContextLoaderListener源码中是基于contextConfigLocationcontextClass两个配置参数去加载相应配置的,所以就有了咱们配置的context-param参数了,servlet标签里的初始化参数也是一样的道理,即告诉web服务器在启动的同时把spring web上下文(WebApplicationContext)也给初始化了。tomcat

上面讲了下tomcat加载spring mvc应用的大体流程,接下来将从源码入手分析启动原理。bash

2、Spring MVC web 上下文启动源码分析

假设如今咱们把上面web.xml文件中的<load-on-startup>1</load-on-startup>给去掉,那么默认tomcat启动时只会初始化spring web上下文,也就是说只会加载到applicationContext.xml这个文件,对于applicationContext-mvc.xml这个配置文件是加载不到的,<load-on-startup>1</load-on-startup>的意思就是让DispatcherServlet延迟到使用的时候(也就是处理请求的时候)再作初始化。服务器

咱们已经知道spring web是基于servlet标准去封装的,那么很明显,servlet怎么初始化,WebApplicationContextweb上下文就应该怎么初始化。咱们先看看ContextLoaderListener的源码是怎样的。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    // 初始化方法
    @Override
    public void contextInitialized(ServletContextEvent event) {
    	initWebApplicationContext(event.getServletContext());
    }
    // 销毁方法
    @Override
    public void contextDestroyed(ServletContextEvent event) {
    	closeWebApplicationContext(event.getServletContext());
    	ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
复制代码

ContextLoaderListener类实现了ServletContextListener,本质上是一个servlet监听器,tomcat将会优先加载servlet监听器组件,并调用contextInitialized方法,在contextInitialized方法中调用initWebApplicationContext方法初始化Spring web上下文,看到这焕然大悟,原来Spring mvc的入口就在这里,哈哈~~~赶忙跟进去initWebApplicationContext方法看看吧!

initWebApplicationContext()方法:

// 建立web上下文,默认是XmlWebApplicationContext
if (this.context == null) {
    this.context = createWebApplicationContext(servletContext);
}

if (this.context instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    // 若是该容器尚未刷新过
    if (!cwac.isActive()) {
    	if (cwac.getParent() == null) {
    		ApplicationContext parent = loadParentContext(servletContext);
    		cwac.setParent(parent);
    	}
    	// 配置并刷新容器
    	configureAndRefreshWebApplicationContext(cwac, servletContext);
    }
}
复制代码

上面的方法只作了两件事:

  • 一、若是spring web容器尚未建立,那么就建立一个全新的spring web容器,而且该容器为root根容器,下面第三节讲到的servlet spring web容器是在此根容器上建立起来的
  • 二、配置并刷新容器

上面代码注释说到默认建立的上下文容器是XmlWebApplicationContext,为何不是其余web上下文呢?为啥不是下面上下文的任何一种呢?

咱们能够跟进去createWebApplicationContext后就能够发现默认是从一个叫ContextLoader.properties文件加载配置的,该文件的内容为:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
复制代码

具体实现为:

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 自定义上下文,不然就默认建立XmlWebApplicationContext
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
        	return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
        	throw new ApplicationContextException(
        			"Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        // 从属性文件中加载类名,也就是org.springframework.web.context.support.XmlWebApplicationContext
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
        	return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
        	throw new ApplicationContextException(
        			"Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}
复制代码

上面能够看出其实咱们也能够自定义spring web的上下文的,那么怎么去指定咱们自定义的上下文呢?答案是经过在web.xml中指定contextClass参数,所以第一小结结尾时说contextClass参数和contextConfigLocation很重要~~至于contextConfigLocation参数,咱们跟进configureAndRefreshWebApplicationContext便可看到,以下图:

总结:

spring mvc启动流程大体就是从一个叫ContextLoaderListener开始的,它是一个servlet监听器,可以被web容器发现并加载,初始化监听器ContextLoaderListener以后,接着就是根据配置如contextConfigLocationcontextClass建立web容器了,若是你不指定contextClass参数值,则默认建立的spring web容器类型为XmlWebApplicationContext,最后一步就是根据你配置的contextConfigLocation文件路径去配置并刷新容器了。

3、DispatcherServlet控制器的初始化

好了,上面咱们简单地分析了Spring mvc容器初始化的源码,咱们永远不会忘记,咱们默认建立的容器类型为XmlWebApplicationContext,固然咱们也不会忘记,在web.xml中,咱们还有一个重要的配置,那就是DispatcherServlet。下面咱们就来分析下DispatcherServlet的初始化过程。

DispatcherServlet,就是一个servlet,一个用来处理request请求的servlet,它是spring mvc的核心,全部的请求都通过它,并由它指定后续操做该怎么执行,咋一看像一扇门,所以我管它叫“闸门”。在咱们继续以前,咱们应该共同遵照一个常识,那就是-------不管是监听器仍是servlet,都是servlet规范组件,web服务器均可以发现并加载它们。

下面咱们先看看DispatcherServlet的继承关系:

看到这咱们是否是一目了然了,DispatcherServlet继承了HttpServlet这个类,HttpServlet是servlet技术规范中专门用于处理http请求的servlet,这就不难解释为何spring mvc会将DispatcherServlet做为统一请求入口了。

由于一个servlet的生命周期是init()->service()->destory(),那么DispatcherServlet怎么初始化呢?看上面的继承图,咱们进到HttpServletBean去看看。

果不其然,HttpServletBean类中有一个init()方法,HttpServletBean是一个抽象类,init()方法以下:

能够看出方法采用final修饰,由于final修饰的方法是不能被子类继承的,也就是子类没有一样的init()方法了,这个init方法就是DispatcherServlet的初始化入口了。

接着咱们跟进FrameworkServletinitServletBean()方法:

在方法中将会初始化不一样于第一小节的web容器,请记住,这个新的spring web 容器是专门为dispactherServlet服务的,并且这个新容器是在第一小节根ROOT容器的基础上建立的,咱们在<servlet>标签中配置的初始化参数被加入到新容器中去。

至此,DispatcherSevlet的初始化完成了,听着有点蒙蔽,但其实也是这样,上面的分析仅仅只围绕一个方法,它叫init(),全部的servlet初始化都将调用该方法。

总结:

dispactherServlet的初始化作了两件事情,第一件事情就是根据根web容器,也就是咱们第一小节建立的XmlWebApplicationContext,而后建立一个专门为dispactherServlet服务的web容器,第二件事情就是将你在web.xml文件中对dispactherServlet进行的相关配置加载到新容器当中。

3、每一个request调用请求经历了哪些过程

其实说到这才是dispatcherServlet控制器的核心所在,由于web框架无非就是接受请求,处理请求,而后响应请求。固然了,若是dispactherServlet只是单纯地接受处理而后响应请求,那未免太弱了,所以spring设计者加入了许许多多的新特性,好比说拦截器、消息转换器、请求处理映射器以及各类各样的Resolver,所以spring mvc很是强大。

dispatcherServlet类不作相关源码分析,由于它就是一个固定的执行步骤,什么意思呢?一个request进来,大体就经历这样的过程:

接受请求 -----> 是否有各类各样的处理器Handler -------> 是否有消息转换器HandlerAdapter --------> 响应请求

上面每一步若是存在相应的组件,固然前提是你在项目中有作相关的配置,则会执行你配置的组件,最后响应请求。所以明白大体的流程以后,若是你想调试一个request,那么你彻底能够在dispatcherServlet类的doDispatch方法中打个断点,跟完代码以后你就会发现其实大体流程就差很少了。

4、后话

本文的工程是基于传统的web.xml加载web项目,固然在spring mvc中咱们也能够彻底基于注解的方式进行配置,咱们能够经过实现WebApplicationInitializer来建立本身的web启动器,也能够经过继承AbstractAnnotationConfigDispatcherServletInitializer来建立相应的spring web容器(包括上面说到的根容器和servlet web容器),最后经过继承WebMvcConfigurationSupport再一步进行自定义配置(相关拦截器,bean等)

感谢你的阅读,期待与你共同进步,欢迎下方发表评论~~~

相关文章
相关标签/搜索