配置嵌入式Servlet容器

832 阅读4分钟

传统开发过程

写一个web应用,将web打成war包,需要在web配好Tomcat环境,Tomcat就是一个Servlet容器,把我们的war包放在Tomcat上,即可启动。

嵌入式Servlet容器

Spring Boot默认使用的是嵌入式的Servlet容器(比如Tomcat);

spring-boot-starter-web  --> spring-boot-starter-tomcat --> 下图中Tomcat版本为9.0.36

通过上图依赖树中可以看出,Spring Boot默认使用嵌入式的Servlet容器。

问题?

  1. 如何定制和修改Servlet容器的相关配置;

    • 改ServerProperties类中的设置(在application.yml配置文件中写)1.  通用的容器Servlet容器设置

      server.xxx server.port=8081 //修改和server有关的配置

    2.  跟Tomcat有关的设置

    server.tomcat.uri-encoding = UTF-8
    
    • 编写一个WebServerFactoryCustomizer(嵌入式的Servlet容器的定制器) (写一个配置类)
      扩展:在Spring Boot中会有很多的xxxCustomizer帮助我们进行定制配置。
  2. Spring Boot能否支持其他Servlet容器;

注册Servlet, Filter, Listner

创建的Spring Boot应用,默认是以Jar包的方式启用嵌入式的Tomcat,而不是Web应用的目录结构,因为没有web-app/WEB-INF/web.xml(注册web三大组件的配置文件)。

所以在Spring Boot中是如何注册三大组件的呢?

Spring Boot提供了ServletRegistrationBean, FilterRegistrationBean, 和ServletListenerRegistrationBean.

下图为如何定制Servlet及如何注册到容器中:

注册自定制Filter和Listner如下图:

Filter是什么可以参考博文:Servlet之Filter详细讲解,这篇博文讲了如何利用filter做“禁用所有动态页面的缓存过滤器” 及 “分IP统计网站的访问次数过滤器

拦截路径:

SpringBoot帮我们自动配置Spring MVC的时候,帮我们自动注册了SpringMVC的前端控制器:DipatcherServlet;

public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
            registration.setName("dispatcherServlet");
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

有注册dispatcherServlet的源代码可以看到,默认拦截的请求路径为webMvcProperties.getServlet().getPath()

所以我们可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径

使用其他嵌入式Servlet容器

Spring Boot不只支持Tomcat作为嵌入式Servlet容器,还支持以下两种,他们各自的特性如下

  • Jetty(长连接)
  • Undertow(不支持JSP)

可以自动切换:

1. 排除原有依赖中对spring-boot-starter-tomcat的依赖

2. 添加对spring-boot-starter-jetty的依赖

如果不做修改,就是默认使用Tomcat。

当然,如果以上三种不满足要求时,也可以使用外置的Servlet,以下是使用外置Tomcat的步骤:

1. 必须创建一个war项目

2. 将嵌入式的Tomca指定为provided

3. 必须编写一个SpringBootServletInitialzer的子类,并调用configure方法

4. 启动服务器

这个时候在main函数直接启动程序,是不会成功的。

原理:

jar包:执行Spring Boot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器;

war包:启动服务器,服务器启动Spring Boot容器[SpringBootServletInitialzer], 启动IOC容器

嵌入式Servlet配置原理

根据@ConditainalOnClass里的判断条件,判定该创建那个类型的Servlet工厂,所以验证刚才前面所说的可以通过改依赖的方式切换Servlet类型。

这之后的源码非常难懂,但是一般项目不会改动到Servlet类型,也不会触及怎样修改Tomcat本身的属性,之后再来看。

嵌入式Servlet容器启动原理

获取嵌入式的Servlet容器工厂:

1. Spring  Boot应用启动运行run方法

2. run方法中有一步是refreshContext(context); Spring Boot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件(bean)】

刷新容器之前,执行过一步创建容器createApplicationContext,创建时会做判断,如果是web应用,则创建AnnotationConfigServletWebServerApplicationContext👇

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}

Web类型的IOC容器,和普通IOC容器:

DEFAULT_SERVLET_WEB_CONTEXT_CLASSAnnotationConfigServletWebServerApplicationContext

DEFAULT_CONTEXT_CLASS:AnnotationConfigApplicationContext

3. refresh(context);刷新刚才创建好的IOC容器

4. 在上一步的刷新中,会执行onRefresh()方法,web的ioc容器重写了onRefresh方法

5. web IOC容器会创建servlet方法 createEmbeddedServletContainer()

6. 获取嵌入式的Servlet容器工厂

7. 使用容器工厂获取嵌入式的servlet容器

8. 嵌入式的Servlet容器创建对象并启动Servlet容器

先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;

总结:IOC容器启动的时候,创建Servlet容器(默认Tomcat)