持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
一、springboot切换servlet容器
上篇文章提到,springboot内置了tomcat/jetty/undertow 容器,默认使用tomcat,那如果一些场景需要使用jetty容器该如何切换呢,下面来进行介绍。
- Spring Boot包含对嵌入式Tomcat,Jetty和Undertow服务器的支持
-
tomcat(默认)
-
Jetty(socket)-适用于长连接一些应用
-
Undertow(响应式)-使用与一些物阻塞,响应式的引用
-
1-1、将tomat切换为jetty
springboot默认使用tomcat,可以看到tomcat在spring-boot-starter-web下,那我们切换只需要将spring-boot-starter-tomcat排除,然后添加需要服务的starter就可以了。
1-1-1、排除tomcat启动器
1-1-2、添加jetty启动器或者其他启动器
1-1-3、测试
如下图,再次启动服务,已经变成了jetty
1-2、小结
切换容器,首先需要排除默认的tomcat启动器,然后添加需要的容器启动器即可。如果有针对容器单独的配置,还需要单独修改。
二、嵌入式servlet容器自动配置原理
为了弄清楚servlet容器自动配置的原理,我们只有通过阅读源码才能了解,但是springboot源码那么多,怎么知道那个类是关于servlet的自动配置呢,这个时候可以根据要查看功能的英文名称去猜测,比如当前要看servlet自动配置的原理,那英文就应该和servlet和auto、configuration有关。因此根据这三个名称去进行试着查询一下。
如下图,这样就可以搜索到我们需要的自动配置类了
最终就可以确定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内置容器引入了这三个。
2-1-1、以tomcat为例,了解加载过程
因为我这边创建的是父子项目,为了可以更加明确的演示,先将其他项目unload
这样当前idea加载的就只有使用的项目了
我们进入EmbeddedJetty.class就可以发现,因为在pom中为添加jetty的启动类,就无法找到相关Server/Loader/WebAppContext了,该注解就不会生效
下图看到的是jetty的配置,而tomcat/undertow也是这样的配置。只有添加了对应的启动器,其对应的servlet启动器就会生效。
2-2、怎么根据配置文件中server.xxx通过WebServerFactoryCustomizer去设置servlet容器
上面介绍了ServletWebServerFactoryAutoConfiguration的相关注解,接下来继续看下面的代码
这个Bean和上篇文章中介绍自己实现
WebServerFactoryCustomizer,设置容器相关参数的比较类似,看下之前设置的Bean
下面再看下ServletWebServerFactoryCustomizer的类关系图
可以看到它的父类也是我们自己创建Bean实现的Bean
ServletWebServerFactoryCustomizer 也实现了WebServerFactoryCustomizer ,说明它也是定制servlet容器的
2-2-1、定制实现过程
可以看到这个Bean返回了一个它自己的构造函数,传入了serverProperties(也就是所有server.xxx相关的配置信息都会传入)
同时可以看到也是通过重写customize,然后使用server.xxx设置相关参数。和上篇文章中介绍自己设置的模式是一模一样的。
其中(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
进入TomcatServletWebServerFactoryCustomizer,可以看到也是重写了customize方法,不同的是,它获取的配置文件为:server.tomcat.xxx,如下:
2-4、怎么让所有的WebServerFactoryCustomizer Bean一一调用的
2-4-1、WebServerFactoryCustomizer注册WebServerFactoryCustomizerBeanPostProcessor
在Import的时候,还导入了这么一个类,实际上这个就是帮助我们实现所有的WebServerFactoryCustomizer Bean一一调用的。
实现ImportBeanDefinitionRegistrar 会提供一个方法,并且提供BeanDefinitionRegistar 让我们去注册bean
注册了:WebServerFactoryCustomizerBeanPostProcessor
2-4-2、BeanPostProcessor
进入这个Bean,实现了BeanPostProcessor,其作用是在spring底层在初始化IOC容器的的时候在bean创建时,初始化时就会调用
进入BeanPostProcessor,可以看到有postProcessBeforeInitialization和postProcessAfterInitialization两个方法,分别是Bean创建前,和创建后调用。
2-4-3、Bean创建前调用postProcessBeforeInitialization的实现逻辑
可以看到在Bean创建前的逻辑中进行了判断:if (bean instanceof WebServerFactory) {,其含义为:判断当前创建的bean是不是WebServerFactory,如果是则调用里面的方法,那WebServerFactory又是什么呢?
2-4-3-1、WebServerFactory
可以看下tomcat的容器上层实现的就是WebServerFactroy
jetty的容器上层实现的也是WebServerFactory
不用想,undertow一定也是实现的WebServerFactory
这样条件肯定成立,继续调用postProcessBeforeInitialization方法
2-4-3-2、postProcessBeforeInitialization实现过程
postProcessBeforeInitialization方法如下:
2-4-3-2-1、调用getCustomizers()
如上图可以看到最终调用了getWebServerFactoryCustomizerBeans() 就获取了所有实现了WebServerFactoryCustomizer接口的Bean
其中包括:自定义的,和ServletWebServerFactoryCustomizer以及TomcatServletWebServerFactoryCustomizer
2-4-3-2-2、withLogger用于记录日志
这个就是一个记录日志的,不用深入了解
2-4-3-2-3、invoke
在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
再次调用tomcatWebServer
接着调用initialize进行了tomcat启动