springboot切换其他嵌入servlet容器,及嵌入式Servlet容器自动配置原理

144 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

一、springboot切换servlet容器

上篇文章提到,springboot内置了tomcat/jetty/undertow 容器,默认使用tomcat,那如果一些场景需要使用jetty容器该如何切换呢,下面来进行介绍。

  • Spring Boot包含对嵌入式TomcatJettyUndertow服务器的支持
    • tomcat(默认)

    • Jetty(socket)-适用于长连接一些应用

    • Undertow(响应式)-使用与一些物阻塞,响应式的引用

1-1、将tomat切换为jetty

springboot默认使用tomcat,可以看到tomcat在spring-boot-starter-web下,那我们切换只需要将spring-boot-starter-tomcat排除,然后添加需要服务的starter就可以了。 image.png

1-1-1、排除tomcat启动器

image.png

1-1-2、添加jetty启动器或者其他启动器

image.png

1-1-3、测试

如下图,再次启动服务,已经变成了jetty image.png

1-2、小结

切换容器,首先需要排除默认的tomcat启动器,然后添加需要的容器启动器即可。如果有针对容器单独的配置,还需要单独修改。

二、嵌入式servlet容器自动配置原理

为了弄清楚servlet容器自动配置的原理,我们只有通过阅读源码才能了解,但是springboot源码那么多,怎么知道那个类是关于servlet的自动配置呢,这个时候可以根据要查看功能的英文名称去猜测,比如当前要看servlet自动配置的原理,那英文就应该和servlet和auto、configuration有关。因此根据这三个名称去进行试着查询一下。

如下图,这样就可以搜索到我们需要的自动配置类了 image.png

最终就可以确定ServletWebServerFactoryAutoConfiguration即为servlet容器自动配置类了。

2-1、ServletWebServerFactoryAutoConfiguration的源码阅读

//设为自动配置类
@Configuration(proxyBeanMethods = false)
//解析顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//只要依赖任意一个servlet容器,这个类就进行加载
@ConditionalOnClass(ServletRequest.class)
//当前引用环境必须是servlet环境
@ConditionalOnWebApplication(type = Type.SERVLET)
// 启用servet.xxx的所有的配置信息绑定到ServerProperties
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

这个类的上面注解已经进行标记,下面就剩下@Import注解,这个注解首先可以看到导入了内嵌的tomcat/jetty/undertow,这样简单理解就知道servlet内置容器引入了这三个。

image.png

2-1-1、以tomcat为例,了解加载过程

因为我这边创建的是父子项目,为了可以更加明确的演示,先将其他项目unload

image.png

这样当前idea加载的就只有使用的项目了

image.png

image.png

我们进入EmbeddedJetty.class就可以发现,因为在pom中为添加jetty的启动类,就无法找到相关Server/Loader/WebAppContext了,该注解就不会生效

0

下图看到的是jetty的配置,而tomcat/undertow也是这样的配置。只有添加了对应的启动器,其对应的servlet启动器就会生效。 image.png

2-2、怎么根据配置文件中server.xxx通过WebServerFactoryCustomizer去设置servlet容器

上面介绍了ServletWebServerFactoryAutoConfiguration的相关注解,接下来继续看下面的代码

image.png 这个Bean和上篇文章中介绍自己实现WebServerFactoryCustomizer,设置容器相关参数的比较类似,看下之前设置的Bean

image.png

下面再看下ServletWebServerFactoryCustomizer的类关系图

image.png

可以看到它的父类也是我们自己创建Bean实现的Bean
ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer ,说明它也是定制servlet容器的

image.png

2-2-1、定制实现过程

可以看到这个Bean返回了一个它自己的构造函数,传入了serverProperties(也就是所有server.xxx相关的配置信息都会传入) image.png

同时可以看到也是通过重写customize,然后使用server.xxx设置相关参数。和上篇文章中介绍自己设置的模式是一模一样的。 image.png

其中(jdk8新语法)

map.from(this.serverProperties::getPort).to(factory::setPort);

实际就是:

if(serverProperties.getPort()!=null){  
    factory.setPort(serverProperties.getPort()) 
}

这样就可以读取配置文件中的server.xxx,然后使用ServletWebServerFactoryCustomizer进行容器配置了

2-3、TomcatServletWebServerFactoryCustomizer Tomcat配置文件定制器

接着往下看,可以看到这个Bean是tomcat的的容器配置Bean image.png

进入TomcatServletWebServerFactoryCustomizer,可以看到也是重写了customize方法,不同的是,它获取的配置文件为:server.tomcat.xxx,如下:

image.png

2-4、怎么让所有的WebServerFactoryCustomizer Bean一一调用的

2-4-1、WebServerFactoryCustomizer注册WebServerFactoryCustomizerBeanPostProcessor

在Import的时候,还导入了这么一个类,实际上这个就是帮助我们实现所有的WebServerFactoryCustomizer Bean一一调用的。 image.png

实现ImportBeanDefinitionRegistrar 会提供一个方法,并且提供BeanDefinitionRegistar 让我们去注册bean image.png

注册了:WebServerFactoryCustomizerBeanPostProcessor image.png

2-4-2、BeanPostProcessor

进入这个Bean,实现了BeanPostProcessor,其作用是在spring底层在初始化IOC容器的的时候在bean创建时,初始化时就会调用 image.png

进入BeanPostProcessor,可以看到有postProcessBeforeInitializationpostProcessAfterInitialization两个方法,分别是Bean创建前,和创建后调用。

2-4-3、Bean创建前调用postProcessBeforeInitialization的实现逻辑

可以看到在Bean创建前的逻辑中进行了判断:if (bean instanceof WebServerFactory) {,其含义为:判断当前创建的bean是不是WebServerFactory,如果是则调用里面的方法,那WebServerFactory又是什么呢? image.png

2-4-3-1、WebServerFactory

可以看下tomcat的容器上层实现的就是WebServerFactroy image.png

jetty的容器上层实现的也是WebServerFactory

image.png 不用想,undertow一定也是实现的WebServerFactory

这样条件肯定成立,继续调用postProcessBeforeInitialization方法 image.png

2-4-3-2、postProcessBeforeInitialization实现过程

postProcessBeforeInitialization方法如下: image.png

2-4-3-2-1、调用getCustomizers()

image.png 如上图可以看到最终调用了getWebServerFactoryCustomizerBeans() 就获取了所有实现了WebServerFactoryCustomizer接口的Bean
其中包括:自定义的,和ServletWebServerFactoryCustomizer以及TomcatServletWebServerFactoryCustomizer

2-4-3-2-2、withLogger用于记录日志

这个就是一个记录日志的,不用深入了解 image.png

2-4-3-2-3、invoke

image.png 在invoke方法中循环调用所有实现了WebServerFactoryCustomizer接口的Bean的customize方法进行一一定制(比如我们自己定制的、通用的、tomcat的等)

2-5、嵌入式servlet容器是怎么启动的

自动配置中根据不同的依赖, 启动对应一个Embeddedxxxx, 然后配置一个对应的servlet容器工厂类, 比如tomcat:TomcatServletWebServerFactory

在springboot应用启动的时候 , 就会调用容器refresh方法, onRefresh , 调用getWebServer, 创建servlet及启动

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }

    Tomcat tomcat = new Tomcat();
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Iterator var4 = this.serverLifecycleListeners.iterator();

    while(var4.hasNext()) {
        LifecycleListener listener = (LifecycleListener)var4.next();
        tomcat.getServer().addLifecycleListener(listener);
    }

    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var8 = this.additionalTomcatConnectors.iterator();

    while(var8.hasNext()) {
        Connector additionalConnector = (Connector)var8.next();
        tomcat.getService().addConnector(additionalConnector);
    }

    this.prepareContext(tomcat.getHost(), initializers);
    return this.getTomcatWebServer(tomcat);
}

然后就会调用上面方法最下面的方法:getTomcatWebServer

image.png

再次调用tomcatWebServer image.png

接着调用initialize进行了tomcat启动 image.png