赞
踩
springboot强大的地方就是,相比于传统spring架构,省去了很多繁杂的配置,其中一个就是springboot支持了内置容器,启动的时候框架层面帮我们初始化和启动容器,我们更多的关心代码和业务逻辑实现即可,那么它是如何支持内置容器的,以及内置容器是如何初始化和启动的,本篇文章将展开详细分析。
Springboot支持三种内置容器,分别是Tomcat、Jetty和Undertow,默认是使用Tomcat,只需要引入相关依赖就能使用响应能力。
tomcat是默认内置容器,只需要引入starter-web就引入了容器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
使用jetty时,需要从starter-web中排出tomcat容器,然后引入jetty容器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
使用undertow和jetty一样,排出tomcat依赖,并引入undertow依赖即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
容器配置主要从三个配置类和一个后置处理器分析,我们逐个分析一下。
内嵌web容器自动配置定义了几个WebServerFactoryCustomizer:
@Configuration @ConditionalOnWebApplication @EnableConfigurationProperties(ServerProperties.class) public class EmbeddedWebServerFactoryCustomizerAutoConfiguration { @Configuration @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class }) public static class TomcatWebServerFactoryCustomizerConfiguration { @Bean public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer( Environment environment, ServerProperties serverProperties) { return new TomcatWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class }) public static class JettyWebServerFactoryCustomizerConfiguration { @Bean public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer( Environment environment, ServerProperties serverProperties) { return new JettyWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass({ Undertow.class, SslClientAuthMode.class }) public static class UndertowWebServerFactoryCustomizerConfiguration { @Bean public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer( Environment environment, ServerProperties serverProperties) { return new UndertowWebServerFactoryCustomizer(environment, serverProperties); } } @Configuration @ConditionalOnClass(HttpServer.class) public static class NettyWebServerFactoryCustomizerConfiguration { @Bean public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer( Environment environment, ServerProperties serverProperties) { return new NettyWebServerFactoryCustomizer(environment, serverProperties); } } }
该配置类定义了针对Tomcat、Jetty、Undertow和Netty几个WebServerFactoryCustomizer类型的bean,用于将容器配置绑定到容器(使用Netty容器需要额外做配置,并且会改变原生spring web能力,本篇不做分析),并且配置哪种容器取决于哪种依赖被引入进来,拿Tomcat为例,会注册一个TomcatWebServerFactoryCustomizer。
它是一个WebServerFactoryCustomizer,重写customize方法,根据环境信息和用户配置属性信息自定义配置ConfigurableTomcatWebServerFactory容器工厂:
@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); propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive) .to((maxThreads) -> customizeMaxThreads(factory, tomcatProperties.getMaxThreads())); propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive) .to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads)); propertyMapper.from(this::determineMaxHttpHeaderSize).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::getMaxHttpPostSize).asInt(DataSize::toBytes) .when((maxHttpPostSize) -> maxHttpPostSize != 0) .to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory, maxHttpPostSize)); propertyMapper.from(tomcatProperties::getAccesslog) .when(ServerProperties.Tomcat.Accesslog::isEnabled) .to((enabled) -> customizeAccessLog(factory)); propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull() .to(factory::setUriEncoding); propertyMapper.from(properties::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)); customizeStaticResources(factory); customizeErrorReportValve(properties.getError(), factory); }
有两个点我们要思考一下:
先看第一个问题,虽然EmbeddedWebServerFactoryCustomizerAutoConfiguration上边加了@Configuration注解,但是我们之前一篇文章《@ComponentScan原理分析》有说到,应用启动的时候要么默认扫描启动类所在路径以及子路径,要么用户自己指定路径,那么如果没有做处理,外部引入的类路径是扫描不到的,包括框架层,那么就要思考如何将其初始化。搜索该配置类引用的地方,在spring.factories中有引用到:
从文件中可以看到其被EnableAutoConfiguration指向:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
这样就保证了在springboot应用启动的时候将该EmbeddedWebServerFactoryCustomizerAutoConfiguration实例化。
然后再看第二个问题,直接搜WebServerFactoryCustomizer的customize方法调用,可以看到被WebServerFactoryCustomizerBeanPostProcessor调用:
WebServerFactoryCustomizerBeanPostProcessor是一个BeanPostProcessor:
重写了postProcessBeforeInitialization方法在实例化后初始化之前调用:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
作用是拦截到WebServerFactory类型的bean,然后执行初始化之前的逻辑:
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
从BeanFactory中获取WebServerFactoryCustomizer列表,然后执行其customize方法,正如前边说的Tomcat、jetty和Undertow几种WebServerFactoryCustomizer。
这里只是搞清楚了WebServerFactoryCustomizer被谁调用,又引入了一个BeanPostProcessor,那么这个类处理器又是何时注册?由于涉及到另外一个配置类ServletWebServerFactoryAutoConfiguration,在对应模块再详细分析。
springboot具体支持的内置容器是在ServletWebServerFactoryConfiguration定义,我看一下对应配置:
@Configuration class ServletWebServerFactoryConfiguration { @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); } } @Configuration @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyServletWebServerFactory JettyServletWebServerFactory() { return new JettyServletWebServerFactory(); } } @Configuration @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowServletWebServerFactory undertowServletWebServerFactory() { return new UndertowServletWebServerFactory(); } } }
也是根据有没有引入相关依赖,注册对应类型容器的bean,并且返回的bean类型是WebServerFactory,拿TomcatServletWebServerFactory为例:
在应用启动时获取web容器是通过重写ServletWebServerFactory的getWebServer方法实现:
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); 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); }
所以这里并没有实例化任何容器,而是创建了所支持的容器的工厂,也即是这里只是声明了创建容器的工厂bean。
并且ServletWebServerFactoryConfiguration中声明的几种容器创建工厂被另外一个配置类ServletWebServerFactoryAutoConfiguration通过@Import导入了,也就是其初始化时机依赖于后者。
该类是web容器工厂自动配置类,可以理解为整合了前两个配置的能力,看一下配置内容:
@Configuration @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 { @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new ServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } }
首先通过@Import注解导入了一个ImportBeanDefinitionRegistrar类BeanPostProcessorsRegistrar以及三个容器配置类EmbeddedTomcat、EmbeddedJetty和EmbeddedUndertow。根据前边的分析,此处导入配置类会和当前配置类一起实例化,而BeanPostProcessorsRegistrar会在实例化之后被ConfigurationClassPostProcessor调用,参考《撩一撩ImportBeanDefinitionRegistrar》。
然后声明了两个WebServerFactoryCustomizer类型的bean;ServletWebServerFactoryCustomizer做一些通用的容器配置,比如端口、地址、上下文路径等等:
@Override public void customize(ConfigurableServletWebServerFactory factory) { PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); map.from(this.serverProperties::getPort).to(factory::setPort); map.from(this.serverProperties::getAddress).to(factory::setAddress); map.from(this.serverProperties.getServlet()::getContextPath) .to(factory::setContextPath); map.from(this.serverProperties.getServlet()::getApplicationDisplayName) .to(factory::setDisplayName); map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession); map.from(this.serverProperties::getSsl).to(factory::setSsl); map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp); map.from(this.serverProperties::getCompression).to(factory::setCompression); map.from(this.serverProperties::getHttp2).to(factory::setHttp2); map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader); map.from(this.serverProperties.getServlet()::getContextParameters) .to(factory::setInitParameters); }
TomcatServletWebServerFactoryCustomizer是在针对容器是Tomcat时用于定制化 TomcatServletWebServerFactory,仅在org.apache.catalina.startup.Tomcat类在classpath路径下生效。
和前两个配置一样,ServletWebServerFactoryAutoConfiguration也是springboot引入的外部配置,@Configuration是无法主动被启动类扫描到,搜索可以看到spring.factories中有引用:
也是通过EnableAutoConfiguration方式引用,启动的时候实例化。
BeanPostProcessorsRegistrar在中定义并且导入:
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) { if (ObjectUtils.isEmpty( this.beanFactory.getBeanNamesForType(beanClass, true, false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); } } }
在ConfigurationClassPostProcessor调用registerBeanDefinitions时会注册两个bean,分别是WebServerFactoryCustomizerBeanPostProcessor和ErrorPageRegistrarBeanPostProcessor,后者用于处理错误页面,WebServerFactoryCustomizerBeanPostProcessor的话我们前边有提到,用于将WebServerFactoryCustomizer的配置应用到对应的容器工厂。
通过第二节的分析,web容器配置已经准备完毕,那么我们就分析一下springboot应用启动时内嵌容器的实例化与启动。
springboot启动过程中会调用AbstractApplicationContext的refresh方法:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 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 { //...省略 // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); //...省略 // Initialize other special beans in specific context subclasses. onRefresh(); //...省略 finishRefresh(); } //...省略 } }
invokeBeanFactoryPostProcessors会解析@Configuration注解并将对应信息注册成BeanDefinition,registerBeanPostProcessors方法会将BeanPostProcessor注册到BeanFactory中,其中就包括前边分析的WebServerFactoryCustomizerBeanPostProcessor,而创建和实例化web容器入口就在onRefresh方法,看一下子类ServletWebServerApplicationContext的继承关系以及onRefresh实现:
极其复杂的继承关系,本质上是一个BeanFactory,间接继承了AbstractApplicationContext。
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
然后调用私有方法创建web容器:
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
启动流程尚未创建web容器,所以会走到if分支,获取web容器工厂,然后从工厂获取web容器,先看一下获取容器工厂:
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); }
从BeanFactory中获取ServletWebServerFactory类型的beanName信息(此时ServletWebServerFactory尚未实例化,但是BeanDefinition中有beanName),然后检查ServletWebServerFactory类型的BeanDefinition数量是合法。
然后调用BeanFactory的getBean方法将web容器工厂实例化并返回。接着看通过web容器工厂获取容器的实现(tomcat为例):
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); 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); }
创建tomcat做一些初始化配置和准备工作(设置根目录、连接器、引擎和上下文等),接着调用getTomcatWebServer方法返回WebServer:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
这里是通过Tomcat包装成spring规范的TomcatWebServer,继续看创建web容器:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
然后调用initialize方法:
private void initialize() throws WebServerException { synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
获取容器上下文,添加生命周期监听器,启动tomcat容器,然后启动非守护线程避免立即关闭。
到这里,springboot内置web容器的启动并没有算完成,我们回到AbstractApplicationContext的refresh方法,看到try代码块最后一行调用finishRefresh,会调用到ServletWebServerApplicationContext的重写方法finishRefresh:
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
该方法调用私有方法startWebServer:
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
基于前一步已经从web容器工厂获取到WebServer,比如TomcatWebServer,然后会调用WebServer的start方法:
@Override public void start() throws WebServerException { synchronized (this.monitor) { if (this.started) { return; } try { addPreviouslyRemovedConnectors(); Connector connector = this.tomcat.getConnector(); if (connector != null && this.autoStart) { performDeferredLoadOnStartup(); } checkThatConnectorsHaveStarted(); this.started = true; } catch (ConnectorStartFailedException ex) { stopSilently(); throw ex; } catch (Exception ex) { throw new WebServerException("Unable to start embedded Tomcat server", ex); } finally { Context context = findContext(); ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } } }
其实这一步的操作可以理解为web容器启动的检查和兜底,如果前边已经启动成功了直接返回,否则对于一些较旧的Servlet框架(例如Struts、BIRT)在此阶段使用线程上下文类加载器创建Servlet实例,然后检查如果启动失败则抛出异常给调用方。
整个web容器的实例化和启动流程图如下:
基于springboot我们可以很便捷的构建和启动应用,默认情况下它帮我们内置了tomcat容器,在应用启动时我们可以完全感觉不到其存在,如果有一些特定场景需要切换其他容器,只需要在依赖维度做一下简单配置和替换就能解决。本篇文章我们从使用和源码原理维度详细的剖析了springboot内置容器的支持和原理,对于springboot启动时web容器的实例化和启动理解,以及在出现问题时的排查应该都会有比较大的帮助,比如如果应用启动时出现如下异常:
那么很容器就能知道是因为要么没有配置内置容器依赖,要么就是引入了多种内置容器。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。