Springboot内置容器原理

2022-01-20 14:10:20 浏览数 (1)

springboot强大的地方就是,相比于传统spring架构,省去了很多繁杂的配置,其中一个就是springboot支持了内置容器,启动的时候框架层面帮我们初始化和启动容器,我们更多的关心代码和业务逻辑实现即可,那么它是如何支持内置容器的,以及内置容器是如何初始化和启动的,本篇文章将展开详细分析。

一、多容器使用和支持

Springboot支持三种内置容器,分别是Tomcat、Jetty和Undertow,默认是使用Tomcat,只需要引入相关依赖就能使用响应能力。

1.Tomcat

tomcat是默认内置容器,只需要引入starter-web就引入了容器。

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency> 

2.Jetty

使用jetty时,需要从starter-web中排出tomcat容器,然后引入jetty容器。

代码语言:javascript复制
<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>

3.Undertow

使用undertow和jetty一样,排出tomcat依赖,并引入undertow依赖即可。

代码语言:javascript复制
<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>

二、容器配置

容器配置主要从三个配置类和一个后置处理器分析,我们逐个分析一下。

1.EmbeddedWebServerFactoryCustomizerAutoConfiguration

内嵌web容器自动配置定义了几个WebServerFactoryCustomizer:

代码语言:javascript复制
@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容器工厂:

代码语言:javascript复制
@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如何初始化? ● WebServerFactoryCustomizer的customize逻辑如何调用? 先看第一个问题,虽然EmbeddedWebServerFactoryCustomizerAutoConfiguration上边加了@Configuration注解,但是我们之前一篇文章《@ComponentScan原理分析》有说到,应用启动的时候要么默认扫描启动类所在路径以及子路径,要么用户自己指定路径,那么如果没有做处理,外部引入的类路径是扫描不到的,包括框架层,那么就要思考如何将其初始化。搜索该配置类引用的地方,在spring.factories中有引用到:

从文件中可以看到其被EnableAutoConfiguration指向:

代码语言:javascript复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,

这样就保证了在springboot应用启动的时候将该EmbeddedWebServerFactoryCustomizerAutoConfiguration实例化。 然后再看第二个问题,直接搜WebServerFactoryCustomizer的customize方法调用,可以看到被WebServerFactoryCustomizerBeanPostProcessor调用:

WebServerFactoryCustomizerBeanPostProcessor是一个BeanPostProcessor:

重写了postProcessBeforeInitialization方法在实例化后初始化之前调用:

代码语言:javascript复制
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
    throws BeansException {
  if (bean instanceof WebServerFactory) {
    postProcessBeforeInitialization((WebServerFactory) bean);
  }
  return bean;
}

作用是拦截到WebServerFactory类型的bean,然后执行初始化之前的逻辑:

代码语言:javascript复制
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,在对应模块再详细分析。

2.ServletWebServerFactoryConfiguration

springboot具体支持的内置容器是在ServletWebServerFactoryConfiguration定义,我看一下对应配置:

代码语言:javascript复制
@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方法实现:

代码语言:javascript复制
@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导入了,也就是其初始化时机依赖于后者。

3.ServletWebServerFactoryAutoConfiguration

该类是web容器工厂自动配置类,可以理解为整合了前两个配置的能力,看一下配置内容:

代码语言:javascript复制
@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做一些通用的容器配置,比如端口、地址、上下文路径等等:

代码语言:javascript复制
@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方式引用,启动的时候实例化。

4.BeanPostProcessorsRegistrar

BeanPostProcessorsRegistrar在中定义并且导入:

代码语言:javascript复制
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方法:

代码语言:javascript复制
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。

代码语言:javascript复制
@Override
protected void onRefresh() {
  super.onRefresh();
  try {
    createWebServer();
  }
  catch (Throwable ex) {
    throw new ApplicationContextException("Unable to start web server", ex);
  }
}

然后调用私有方法创建web容器:

代码语言:javascript复制
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容器,先看一下获取容器工厂:

代码语言:javascript复制
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数量是合法。 ● 如果数量为0,说明starter-web引入的默认tomcat容器依赖被排出,并且没有引入jetty或者undertow依赖。 ● 如果数量大于1,说明starter-web没有排出tomcat容器依赖,并且同时引入了jetty或者undertow依赖。

然后调用BeanFactory的getBean方法将web容器工厂实例化并返回。接着看通过web容器工厂获取容器的实现(tomcat为例):

代码语言:javascript复制
@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:

代码语言:javascript复制
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
  return new TomcatWebServer(tomcat, getPort() >= 0);
}

这里是通过Tomcat包装成spring规范的TomcatWebServer,继续看创建web容器:

代码语言:javascript复制
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
  Assert.notNull(tomcat, "Tomcat Server must not be null");
  this.tomcat = tomcat;
  this.autoStart = autoStart;
  initialize();
}

然后调用initialize方法:

代码语言:javascript复制
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:

代码语言:javascript复制
@Override
protected void finishRefresh() {
  super.finishRefresh();
  WebServer webServer = startWebServer();
  if (webServer != null) {
    publishEvent(new ServletWebServerInitializedEvent(webServer, this));
  }
}

该方法调用私有方法startWebServer:

代码语言:javascript复制
private WebServer startWebServer() {
  WebServer webServer = this.webServer;
  if (webServer != null) {
    webServer.start();
  }
  return webServer;
}

基于前一步已经从web容器工厂获取到WebServer,比如TomcatWebServer,然后会调用WebServer的start方法:

代码语言:javascript复制
@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容器的实例化和启动理解,以及在出现问题时的排查应该都会有比较大的帮助,比如如果应用启动时出现如下异常:

那么很容器就能知道是因为要么没有配置内置容器依赖,要么就是引入了多种内置容器。

0 人点赞