写在前面
SpringBoot和传统的Spring项目相比优点如下:
快速整合第三方框架,比如redis,mybatis等等 全部采用注解方式,没有繁琐的xml配置 直接嵌入Tomcat、Jetty和Undertow服务器,不需要单独额外的下载集成工作
提供生产就绪功能。例如指标,健康检查和外部化配置。指标和监控检查可以很方便的帮助运维人员在运维期间监控项目运行情况;外部化配置可以很方便的让运维人员快速、方便的外部化配置和部署工作。
今天我们就上述的第三点——嵌入式Servlet进行深入的探究,本文基于SpringBoot最新的GA版本2.4.2进行讨论。官方地址如下:
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-embedded-container
由于SpringBoot默认是以jar包的方式启动,不存在web.xml文件,所以按照之前传统Spring项目配置原生组件的方式是行不通的。这里我们可以参考官方文档,Spring官方给我们提供了两种注册的方式:

如果要使用RegistrationBean方式配置,需要两步操作
定义Servlet、Filter和Listener



将定义的Web原生组件注册到Spring容器中,这里就使用到文档中提到的
ServletRegistrationBean FilterRegistrationBean ServletListenerRegistrationBean
可以使用配置类的方式将对应的Web原生组件加载的Spring容器中,代码如下:

其中Servlet和Filter需要针对一个或多个路径进行处理,所以在初始化的时候可以通过构造方法来传入想要匹配的路径。

这种方式就更加简单了,根据文档中的描述,我们只需要通过基于Servlet3.0以上规范提供的注解——@WebServlet、@WebFilter、@WebListener来标识我们要注入的Web原生组件就可以,所以我们可以将注册方式改造成如下:



@ServletComponentScan(可以指定路径,用于扫描Servlet组件对应的包),将Web原生组件加载进Spring容器中。
DispatchServlet的注册流程
SpringWebMVC的集中访问点,而且负责职责的分派,并与Spring Ioc容器无缝集成,从而可以获得Spring的优势。在传统的Spring项目中,DispatchServlet也是需要配置在web.xml文件中的,但在SpringBoot中,我们依然也不需要做额外的配置,那DispatchServlet是如何加载到容器中作为Servlet使用的呢?这里就应用到了上一章节中提到的ServletRegistrationBean。


而WebMvcProperties类中标注了
@ConfigurationProperties,说明我们可以在对应的application.properties中通过spring.mvc.xx配置SpringMVC的相关属性


通过查看继承关系链,我们得知
DispatchServletRegistrationBean其实就是一个ServletRegistrationBean





既然SpringBoot使用的是嵌入式的Servlet容器,那么就不需要我们额外安装部署Web容器,只需要启动应用就可以启动Web容器,那它内部是如何做到的呢?下面我们来一起分析一下
首先还是先来看一下官方文档

其实上面的文档翻译过来就是说
ServletWebServerApplicationContext作为嵌入式的Servlet容器的Ioc容器可以在启动时去找单例的ServletWebFactory,并且
ServletWebFactory通常有三个容器实现,分别是
TomcatServletServerFactory JettyServletServerFactory UndertowServletWebServerFactory
当我们执行主配置类的main()方法启动应用时,内部会最终调用
SpringApplication.run(String..args),在方法内部其实做了很多的事情,其中包括注册监听器、创建引导上下文环境、初始化容器等,这里我们只关心和本文相关的,也就是初始化Ioc容器,代码如下:


这里ApplicatioonContextFactory.create()使用了响应式编程,具体的方法如下:

可以看出,switch..case会根据
webApplicationType提供的不同的类型创建出不同的容器,而在前面初始化
SpringApplication对象时,
webApplicationType会根据当前导入的环境判断出是哪种类型的应用,具体代码如下:



容器创建完成后,接下来SpringBoot会调用refreshContext()方法将刚创建的容器进行初始化和刷新操作,具体代码如下:




在createWebServer()方法中,首先会去获取WebServerFactory工厂实例

官方文档中也提到,

在ServerProperties中标注了
@ConfigurationProperties,也进一步解释了为什么我们可以在对应的application.properties中通过server.xx配置Web容器的相关属性的原因

例如,spring-boot-start-web中默认就使用了Tomcat作为嵌入式Servlet容器,所以Maven中默认导入了Tomcat相关的jar包,如下图


既然最终加载的是
TomcatServletWebServerFactory,我们就来看下它里面实现的getWebServer()方法

调用构造方法创建TomcatWebServer对象,默认端口8080,并初始化。


启动Tomcat

至此,嵌入式Servlet容器启动并初始化完成~~
这里有一点要格外注意一点:
定制Servlet容器
配置文件

程序化定制
直接实现WebServerFactoryCustomizer接口,设置对应的属性

小伙伴们看到这里可能又会有疑问,这个customize是如何被调用的呢?
要搞清楚这个问题,我们得回看一下
ServletWebServerFactoryAutoConfiguration,在导入组件时,除了导入三种容器的WebServerFactory之外,还导入了一个类叫做
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar,具体实现如下:

这个类只做了一件事情,就是注册后置处理器
WebServerFactoryCustomizerBeanPostProcessor

根据后置处理器的特点,在对象初始化之前会做如下操作:


ServletWebServerFactoryAutoConfiguration自动配置类会初始化自定义配置类
ServletWebServerFactoryCustomizer,它和我们自定义的配置类一样,也实现了WebServerFactoryCustomizer接口

这个类做了两件事情,首先是将自己的优先级设置为最高,所以会优先于我们自定义的配置类加载,其次是将配置文件中的属性一次赋值给对应的WebServerFactory,具体代码如下:








