前言
我们在开发传统的SpringMVC项目的时候,是通过编写web.xml来讲DispatcherServlet配置进Servlet容器中。而使用SpringBoot来开发Web项目,我们不用配置web.xml,而是由框架自动将DispatcherServlet设置到Servlet上下文中。那框架是怎么实现的呢?我们来结合源码看一看。
SpringBoot自动装配SpringMVC
前置知识
在我们看源码之前,首先了解一点前置知识。在我们的SpringMVC里面提供了一个接口SpringServletContainerInitializer
。该接口继承了ServletContainerInitializer
接口。根据Servlet3.0规范,支持该规范的Web容器需要在容器启动的时候自动加载 ServletContainerInitializer
接口的实现类并执行其onStartup
方法,SpringMVC就是通过该回调方法来将DispatchServlet
设置到ServletContext
中的。如下图所示:

当然,该规范还有其他条件。就是需要在classpath下面新建META-INF/service/javax.servlet.ServletContainerInitializer文件,并在该文件中标明实现类的全限定类名。如下图所示:

这就是人们常说的SPI机制。熟悉Dubbo的小伙伴应该比较了解SPI这种动态扩展机制。这种机制的缺点在于我们自定义类的生命周期交给了外部容器来维护。SpringBoot就是基于这一点,来做了优化。重新定义了一个接口ServletContextInitializer
,利用它来进行DispatcherServlet和ServletContext的绑定。这个接口没有继承 ServletContainerInitializer
而是一个Spring的bean,所以他的生命周期在我们的容器内维护。了解了这一点之后,我们开始探究SpringBoot自动配置SpringMVC的原理吧。
开始探究
我们还是从配置类入手,发现关于Servlet的配置类有一个,便是org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
,如下图所示:

我们点开类看一下:

该类标明有@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
注解,也就是说需要加载完Tomcat的配置之后才加载该配置类。该类还注册了一个很关键的bean,就是DispatcherServletRegistrationBean
。我们进入该类:

发现没什么特别的,那我们就想上查看他的父类。我们想上找一直到最顶级会发现其实他是ServletContextInitializer
接口的实现类。我们找一下实现方法,在顶级父类RegistrationBean
里面我们找到了onStartup
方法的实现:

我们发现该方法就是把我们的DispatcherServlet注册进servletContext容器中。点进去看一下发现是抽象方法,一直找到子类的实现逻辑:

到此,我们就清楚了。在SpringBoot容器启动之后,会把我们的DispatcherServlet注册到servletContext中,并且该配置类的生命周期是交给了Spring容器管理的。那我们还有一个疑问没有弄明白,就是我们的ServletContextInitializer
接口的实现类是何时被调用的呢?由于其不是Servlet3.0规范中定义接口的子接口,是谁调用的该接口实现类的onStartup
方法呢?
我们知道,在容器启动过程中,会调用创建Web容器的方法。如图:

我们由此猜测,在创建web容器的时候,会把 ServletContextInitializer
接口的实现类设置进web容器中。在启动时会由web容器调用。其中,getSelfInitializer
方法会将我们容器中设置的实现了ServletContextInitializer
接口的监听器注册器,servlet注册器和过滤器注册器均返回,交给web服务器工厂设置进web服务器。我们点进方法看一下:

我们发现,设置 ServletContextInitializer
的位置在prepareContext
方法。点进方法:

发现配置方法放在了configureContext
方法里面,感觉终于快看到曙光了。点进去看一下:

这两步是关键,这个TomcatEmbeddedContext是标准ServletContext的子类。这个TomcatStarter
是ServletContainerInitializer
的实现类,这里 把ServletContainerInitializer
设置进Servlet容器中,容器启动时就会调用该实现类的onStartup
方法:

这里会调用我们ServletContextInitializer
接口实现类的onStartup
方法,来完成对Servlet、过滤器和监听器的注册。至此,我们的流程就明明白白了。
总结
SpringBoot设置的流程是摒弃了Servlet3.0规范中的外部引入机制,加入了内嵌服务器并手动设置初始化器的逻辑。使得我们的所有组件的生命周期都是在Spring容器的控制范围内,使容器更加的内聚。
文章评论