前言
本文主要通过源码分析的方式,分析了嵌入式web容器的自动配置原理以及启动流程,以默认容器tomcat为例进行分析;涉及到SpringBoot自动装配原理以及Spring IOC容器启动过程,如果读者掌握上述知识理解起来更加容易。
内置Tomcat容器的自动配置原理
在SpringBoot的启动过程中会自动加载各个模块下的META-INF/spring.factories文件中定义的自动配置类,Tomcat的服务的加载也是如此,首先来看Tomcat自动配置类ServletWebServerFactoryAutoConfiguration
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
......
自动配置类引入了一个内部类BeanPostProcessorsRegistrar,它继承自ImportBeanDefinitionRegistrar接口,这个内部类在spring容器初始化时注入了webServerFactoryCustomizerBeanPostProcessor,即web容器定制器的后置处理器,下面我们来看这部分代码
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
......
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class,
WebServerFactoryCustomizerBeanPostProcessor::new);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
}
private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name,
Class<T> beanClass, Supplier<T> instanceSupplier) {
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier);
beanDefinition.setSynthetic(true);
//向beanFactory注入beanDefinition
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
registerBeanDefinitions方法注入了webServerFactoryCustomizerBeanPostProcessor类,是web容器定制器的后置处理器,它在springbean完成实例化之后执行,下面看一下这个后置处理器做了什么
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
......
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
......
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//获取了所有WebServerFactoryCustomizer并调用它们的定制方法
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}
通过上面的代码可以看到获取了所有类型为WebServerFactoryCustomizer的bean并调用customize定制方法来完成对webServerFactory容器的定制逻辑,在这里对web容器的所有定制器进行了统一调用
定制器具体做了什么?我们可以以tomcat为例来看一下
public class TomcatWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
@Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;
ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds)
.as(Long::intValue).to(factory::setBackgroundProcessorDelay);
customizeRemoteIpValve(factory);
ServerProperties.Tomcat.Threads threadProperties = tomcatProperties.getThreads();
propertyMapper.from(threadProperties::getMax).when(this::isPositive)
.to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
propertyMapper.from(threadProperties::getMinSpare).when(this::isPositive)
.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
propertyMapper.from(this.serverProperties.getMaxHttpHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
.when(this::isPositive)
.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize));
propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
.when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0)
.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled)
.to((enabled) -> customizeAccessLog(factory));
propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull()
.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
propertyMapper.from(tomcatProperties::getProcessorCache)
.to((processorCache) -> customizeProcessorCache(factory, processorCache));
propertyMapper.from(tomcatProperties::getKeepAliveTimeout).whenNonNull()
.to((keepAliveTimeout) -> customizeKeepAliveTimeout(factory, keepAliveTimeout));
propertyMapper.from(tomcatProperties::getMaxKeepAliveRequests)
.to((maxKeepAliveRequests) -> customizeMaxKeepAliveRequests(factory, maxKeepAliveRequests));
propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText()
.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText()
.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
customizeStaticResources(factory);
customizeErrorReportValve(properties.getError(), factory);
}
通过上面的代码知道tomcat定制器完成ServerProperties到tomcat配置的映射,例如编码方式,最大连接数,连接超时时间等配置进行了读取
那么这些定制器是怎么注入到容器中的呢?默认是使用的哪一个呢?我们来看一下WebServerFactoryCustomizer的注入过程
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
//这里启用了自动配置类
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration(proxyBeanMethods = false)
//因为springboot默认依赖了tomcat的jar,所以ConditionalOnClass注解是生效的
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
//下面还有Jetty、Undertow、Netty的定制器初始化逻辑,这里省略
......
由于springboot的jar中默认导入了tomcat的依赖,所以默认注入了TomcatWebServerFactoryCustomizer定制器,而在它的customize方法完成了servlet容器的基本配置
:::info
小结:
1.EmbeddedWebServerFactoryCustomizerAutoConfiguration根据依赖情况注入servlet容器的定制器,定制器有Tomcat、Jetty、Undertow、Netty四种
2.ServletWebServerFactoryAutoConfiguration向容器中注入了WebServerFactoryCustomizerBeanPostProcessor后置处理器,他来获取所有的定制器并对servlet容器进行定制
3.如果我们希望使用其他的web容器例如jetty,那么需要在springboot中排除默认的tomcat并引入jetty相关的依赖,根据自动配置原理,springboot会自动完成对jetty容器的定制
:::
嵌入式Tomcat容器的启动流程
通过前半部分的分析,嵌入式容器在启动后会进行自动配置,那么容器在什么时候启动的呢,可以猜到是在项目启动过程中
@SpringBootApplication
public class PandaAdminApplication {
public static void main(String[] args) {
SpringApplication.run(PandaAdminApplication.class, args);
}
}
跟着启动类run方法一直进入SpringApplication#run方法,这个方法获取了容器对象并执行了refreshContext()方法,而这个方法最终调用的是AbstractApplicationContext#refresh(),进行容器初始化
public ConfigurableApplicationContext run(String... args) {
.....
ConfigurableApplicationContext context = null;
.....
try {
......
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
......
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
createApplicationContext()获取的对象是ServletWebServerApplicationContext,它继承了ServletWebServerApplicationContext,这就是我们的web应用的默认容器对象,下面是简化版的继承关系图,从图上可以看到这个类顶层接口是BeanFactory
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
......
}
}
refresh方法是spring ioc容器的初始化的核心类,方法中解析我们配置的bean,生成BeanDefinition并注册到beanFactory中,并在执行完BeanFactoryPostProcessors后置处理、注册事件广播、国际化等等一系列复杂的初始化逻辑后,回调钩子方法onRefresh(),这个时候bean还没有实例化,我们可以对tomcat、servlet、filter的BeanDefinition进行定制
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//创建servlet容器
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
//TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
...
}
//下面时外置web容器的启动流程
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
getWebServerFactory()方法从spring容器中获取了web容器,我们看一下这个方法的代码,从容器中获取类型是ServletWebServerFactory的bean,并且判断了web容器只能存在一个,断点看到获取的是TomcatServletWebServerFactory,可以知道默认返回的web容器是tomcat的
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
那为什么默认是获取的tomcat的web容器呢?springboot的自动装配类ServletWebServerFactoryAutoConfiguration给了我们答案:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
通过@Import注解引入了web容器配置类,这个类的三个内部类即使web容器工厂,我们看一下ServletWebServerFactoryConfiguration这个类
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
.....
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedJetty {
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(
.....
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedUndertow {
@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(
.....
}
.....
}
}
我们重点注意ConditionalOnClass这个注解,他的作用是在我们引入相关依赖,比如引入了tomcat相关依赖时,条件便会成立,会向容器中注入TomcatServletWebServerFactory,而之前也讲过,springboot正是默认引入tomcat依赖的
ServletWebServerFactory有三个实现类,通过上面的分析我们知道默认注入的是TomcatServletWebServerFactory
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
......
// Start the server to trigger initialization listeners
this.tomcat.start();
......
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
可以看到在这里调用的tomcat.start()启动了tomcat容器,至此web容器便开始启动了。
:::info
小结:
1.SpringBoot应用启动运行run方法,最终会调用到AbstractApplicationContext#refresh方法,创建并初始化我们的IOC容器。
2.在refresh会调用钩子方法onRefresh,web容器重写了这个方法,来创建并初始化嵌入式Servlet容器。
3.通过分析springboot自动装配过程,我们知道默认启动的web容器类是TomcatWebServer,ServerProperties是对应的配置类
:::
参考:
SpringBoot内置tomcat启动原理_springboot启动tomcat原理_yygr的博客-CSDN博客
【springboot】自动整合Tomcat原理_springboot集成tomcat_morris131的博客-CSDN博客