
6.4 DispatcherServlet自动配置流程
DispatcherServletAutoConfiguration自动配置类会在IOC容器中注册DispatcherServlet和DispatcherServletRegistrationConfiguration两个Bean,此为自动配置类的配置结果。那么这个自动配置类何时生效?配置流程又是如何的呢?
这里需要结合Spring Boot的启动流程来进行讲解和介绍。
6.4.1 注册至IOC容器
DispatcherServletAutoConfiguration类的条件注解@AutoConfigureAfter (ServletWebServerFactoryAutoConfiguration.class)定义了自动配置类生效的时间是在ServletWebServerFactory自动配置之后,那么首先要找到这个自动配置流程的生效时间点。
结合前文中SpringApplication.run()方法调用的过程,Spring Boot项目在启动过程中调用了AbstractApplicationContext.refresh()方法,源码如下所示:


点击进入onfresh()方法可以看到,onfresh()方法最终会调用ServletWebServerApplicationContext类的createWebServer()方法。在该方法中程序会进行ServletWebServerFactory对象的获取。在ServletWebServerFactory对象初始化完成后,程序就会进行DispatcherServlet的自动配置,也就是在ServletWebServerApplicationContext类的177行会完成ServletWebServerFactory对象的自动配置。
如果没有发生异常,在ServletWebServerFactory对象配置完成后会触发DispatcherServletAutoConfiguration类进行自动配置工作。也就是在这个时间点自动配置类会向IOC容器注入两个DispatcherServlet相关的Bean,createWebServer()方法的源码如下所示:


获取ServletWebServerFactory对象是为了获取WebServer对象(在本次实例中WebServer对象为Tomcat)。接下来就是创建内嵌的Tomcat实例并进行配置,在配置完成后服务器就启动了。这些步骤在178行的getWebServer()方法中可以查看。在启动内嵌的Tomcat服务器成功后程序才可以装载DispatcherServlet。
6.4.2 创建并启动嵌入式的Tomcat对象
DispatchServlet在上一个步骤中已经完成了注册。此时,在IOC容器中已经含有名称为“dispatcherServlet”的Bean。不过此时DispatcherServlet并没有生效,只是完成了在IOC容器中的注册。而Servlet的运行环境在Servlet容器中,如果没有Servlet容器或者说Servlet容器没有启动的话,Servlet是没有任何作用的。因此还需要一个创建并启动嵌入式的Tomcat对象的流程。
查看createWebServer()方法,源码如下所示:

通过源码可以发现,在ServletWebServerApplicationContext类的第177行可以获取嵌入式的Servlet容器创建工厂对象ServletWebServerFactory,在创建成功之后调用getWebServer()方法再创建Servlet容器对象。本案例中所创建的容器为Tomcat。
getWebServer()方法的作用是创建并启动Tomcat Server。最终调用的实例方法是TomcatServletWebServerFactory类的getWebServer()方法,源码如下所示:


在getTomcatWebServer()方法中,调用了TomcatWebServer类的构造方法,TomcatWebServer类源码如下所示:



在TomcatWebServer构造方法中最后执行了initialize()方法,在该方法中启动了嵌入式的Tomcat服务器。
在Spring Boot项目中嵌入式的Tomcat与平时使用的Tomcat在核心组件上是一样的,都包含Service、Connector、Engine、Host、Context,只是嵌入式的Tomcat服务器其初始化和启动流程都由Spring Boot来完成,开发人员无须操作。
Tomcat的启动过程分为初始化和启动两个步骤,这一小节都已经介绍完毕,接下来介绍装载DispatcherServlet的步骤。
6.4.3 装载至Servlet容器
ServletWebServerApplicationContext类第178行执行了getWebServer()方法,创建并启动了Tomcat,代码如下所示:

重点来看一下该方法的传参:getSelfInitializer(),点击查看该方法的实现,如下所示:

由源码可知,该方法直接返回了一个lambda表达式作为getWebServer()方法的传参。为什么可以这样呢?点开查看getWebServer()方法的定义:

getWebServer()方法的参数被定义为ServletContextInitializer类型的可变参数。ServletContextInitializer接口在定义中标注了@FunctionalInterface注解,是一个函数式接口。因此它可以直接将lambda表达式作为传参给getWebServer()方法。而selfInitialize()方法暂时不会被执行,而是在Tomcat服务器启动后被回调。selfInitialize()方法的代码定义在ServletWebServerApplicationContext类的第225行,源码如下所示:


首先来了解一下Servlet 3.0的规范。Servlet 3.0提供了可以动态注册Servlet、Filter、Listener的ServletContext相关API,开发人员可以将web.xml相关配置通过编码的方式实现,并由javax.servlet.ServletContainerInitializer的实现类负责在Servlet容器启动后进行加载。Spring提供了一个实现类org.springframework.web.SpringServletContainerInitializer,该类会调用所有实现类的onStartup()方法将相关的组件装载到Servlet容器中。
selfInitialize()会调用getServletContextInitializerBeans()方法获取所有ServletContextInitializer接口的实现类,DispatcherServletRegistrationBean就是该特殊接口的实现类。在DispatcherServletAutoConfiguration执行过程中就已经在IOC容器中注册了名称为“dispatcherServletRegistrationBean”的Bean,它会在执行getServletContext InitializerBeans()方法时被获取,最后由执行它的onStartup()方法来装载DispatcherServlet至Tomcat服务器中。
装载Servlet具体方法的调用链路如下所示。
(1)onStartup()方法的实现在RegistrationBean类中,该方法调用了register()方法注册和配置Bean,源码如下所示:


(2)register()方法的实现在DynamicRegistrationBean类中,该方法被执行时会调用addRegistration()方法。这里所讲的DispatcherServlet就是在该方法内进行装载的,源码如下所示:

(3)addRegistration()方法的实现在ServletRegistrationBean类中,该方法被执行时会调用addServlet()方法装载Servlet,源码如下所示:


(5)addServlet()方法具体实现如下所示:



最终,通过onStartup()方法将DispatchServlet装载到Servlet容器中(即Tomcat服务器),在Tomcat服务器启动后就能够使用DispatchServlet进行请求映射和拦截处理了。
综上所述,由于Spring Boot的自动配置,开发人员在Spring Boot项目中引入spring-boot-starter-web场景启动器之后无须进行任何设置也可以进行Web开发。
本章的主要内容就是介绍Spring Boot中DispatcherServlet自动配置的全部流程和知识点,主要围绕以下三个问题展开了讨论。
(1)DispatcherServletAutoConfiguration自动配置类做了哪些事?
主要是向IOC容器中注册了两个Bean,名称分别为“DispatcherServlet”和“DispatcherServletRegistration”,即org.springframework.web.servlet.DispatcherServlet和org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean。
(2)DispatcherServletAutoConfiguration自动配置类是何时执行的?
结合Spring Boot项目启动过程可以得出,自动配置类的执行是在ServletWebServer Factory对象获取之后触发的,方法调用链如图6-16所示。

图6-16 Spring Boot项目启动方法调用链
(3)DispatcherServlet是如何被装载到Servlet容器中并生效的?
Servlet容器在启动之后会回调selfInitialize()方法,在该方法完成了DispatcherServlet的装载过程,该方法调用链如图6-17所示。

图6-17 DispatcherServlet装载方法的调用链
以上所涉及的类和方法读者可以按照书中的提示,自行查看Spring Boot的源码并手动调试,这样才能更好地理解整个DispatcherServlet自动配置和装载的过程。
另外,文章中涉及的源码都来自Spring Boot 2.3.7-RELEASE版本,与其他版本的代码可能些许不同。如果是Spring Boot 2.0之前的版本差异会更大,这一点需要注意。