欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot中如何启动Tomcat流程

程序员文章站 2023-12-02 23:43:28
前面在一篇文章中介绍了 spring 中的一些重要的 context。有一些在此文中提到的 context,可以参看上篇文章。 springboot 项目之所以部署简单,...

前面在一篇文章中介绍了 spring 中的一些重要的 context。有一些在此文中提到的 context,可以参看上篇文章。

springboot 项目之所以部署简单,其很大一部分原因就是因为不用自己折腾 tomcat 相关配置,因为其本身内置了各种 servlet 容器。一直好奇: springboot 是怎么通过简单运行一个 main 函数,就能将容器启动起来,并将自身部署到其上 。此文想梳理清楚这个问题。

我们从springboot的启动入口中分析:

context 创建

// create, load, refresh and run the applicationcontext
context = createapplicationcontext();

在springboot 的 run 方法中,我们发现其中很重要的一步就是上面的一行代码。注释也写的很清楚:

创建、加载、刷新、运行 applicationcontext。

继续往里面走。

protected configurableapplicationcontext createapplicationcontext() {
  class<?> contextclass = this.applicationcontextclass;
  if (contextclass == null) {
    try {
     contextclass = class.forname(this.webenvironment
        ? default_web_context_class : default_context_class);
    }
    catch (classnotfoundexception ex) {
     throw new illegalstateexception(
        "unable create a default applicationcontext, "
           + "please specify an applicationcontextclass",
        ex);
   }
  }
  return (configurableapplicationcontext) beanutils.instantiate(contextclass);
}
 

逻辑很清楚:

先找到 context 类,然后利用工具方法将其实例化。

其中 第5行 有个判断:如果是 web 环境,则加载 default _web_context_class类。参看成员变量定义,其类名为:

annotationconfigembeddedwebapplicationcontext

此类的继承结构如图:

SpringBoot中如何启动Tomcat流程

直接继承 genericwebapplicationcontext。关于该类前文已有介绍,只要记得它是专门为 web application提供context 的就好。

refresh

在经历过 context 的创建以及context的一些列初始化之后,调用 context 的 refresh 方法,真正的好戏才开始上演。

从前面我们可以看到annotationconfigembeddedwebapplicationcontext的继承结构,调用该类的refresh方法,最终会由其直接父类:embeddedwebapplicationcontext 来执行。

@override
 protected void onrefresh() {
  super.onrefresh();
  try {
    createembeddedservletcontainer();
  }
  catch (throwable ex) {
    throw new applicationcontextexception("unable to start embedded container",
       ex);
  }
}

我们重点看第5行。

private void createembeddedservletcontainer() {
  embeddedservletcontainer localcontainer = this.embeddedservletcontainer;
  servletcontext localservletcontext = getservletcontext();
  if (localcontainer == null && localservletcontext == null) {
    embeddedservletcontainerfactory containerfactory = getembeddedservletcontainerfactory();
    this.embeddedservletcontainer = containerfactory
       .getembeddedservletcontainer(getselfinitializer());
  }
  else if (localservletcontext != null) {
   try {
     getselfinitializer().onstartup(localservletcontext);
   }
   catch (servletexception ex) {
     throw new applicationcontextexception("cannot initialize servlet context",
        ex);
   }
  }
  initpropertysources();
}

代码第5行,获取到了一个embeddedservletcontainerfactory,顾名思义,其作用就是为了下一步创建一个嵌入式的 servlet 容器:embeddedservletcontainer。

public interface embeddedservletcontainerfactory {
 
  /**
   * 创建一个配置完全的但是目前还处于“pause”状态的实例.
   * 只有其 start 方法被调用后,client 才能与其建立连接。
   */
  embeddedservletcontainer getembeddedservletcontainer(
     servletcontextinitializer... initializers);
 
}

第6、7行,在 containerfactory 获取embeddedservletcontainer的时候,参数为 getselfinitializer 函数的执行结果。暂时不管其内部机制如何,只要知道它会返回一个 servletcontextinitializer 用于容器初始化的对象即可,我们继续往下看。

由于 embeddedservletcontainerfactory 是个抽象工厂,不同的容器有不同的实现,因为springboot默认使用tomcat,所以就以 tomcat 的工厂实现类 tomcatembeddedservletcontainerfactory 进行分析:

 @override
 public embeddedservletcontainer getembeddedservletcontainer(
    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);
  tomcat.getengine().setbackgroundprocessordelay(-);
  for (connector additionalconnector : this.additionaltomcatconnectors) {
   tomcat.getservice().addconnector(additionalconnector);
  }
  preparecontext(tomcat.gethost(), initializers);
  return gettomcatembeddedservletcontainer(tomcat);
}

从第8行一直到第16行完成了 tomcat 的 connector 的添加。tomcat 中的 connector 主要负责用来处理 http 请求,具体原理可以参看 tomcat 的源码,此处暂且不提。

第17行的 方法有点长,重点看其中的几行:

 if (isregisterdefaultservlet()) {
  adddefaultservlet(context);
 }
 if (isregisterjspservlet() && classutils.ispresent(getjspservletclassname(),
    getclass().getclassloader())) {
  addjspservlet(context);
  addjasperinitializer(context);
  context.addlifecyclelistener(new storemergedwebxmllistener());
 }
servletcontextinitializer[] initializerstouse = mergeinitializers(initializers);
configurecontext(context, initializerstouse);

前面两个分支判断添加了默认的 servlet类和与 jsp 相关的 servlet 类。

对所有的 servletcontextinitializer 进行合并后,利用合并后的初始化类对 context 进行配置。

第 18 行,顺着方法一直往下走,开始正式启动 tomcat。

private synchronized void initialize() throws embeddedservletcontainerexception {
  tomcatembeddedservletcontainer.logger
     .info("tomcat initialized with port(s): " + getportsdescription(false));
  try {
    addinstanceidtoenginename();
 
    // remove service connectors to that protocol binding doesn't happen yet
    removeserviceconnectors();
 
   // start the server to trigger initialization listeners
   this.tomcat.start();

   // we can re-throw failure exception directly in the main thread
   rethrowdeferredstartupexceptions();

   // unlike jetty, all tomcat threads are daemon threads. we create a
   // blocking non-daemon to stop immediate shutdown
   startdaemonawaitthread();
  }
  catch (exception ex) {
   throw new embeddedservletcontainerexception("unable to start embedded tomcat",
      ex);
  }
}

第11行正式启动 tomcat。

现在我们回过来看看之前的那个 getselfinitializer 方法:

private servletcontextinitializer getselfinitializer() {
  return new servletcontextinitializer() {
   @override
   public void onstartup(servletcontext servletcontext) throws servletexception {
     selfinitialize(servletcontext);
   }
  };
}
private void selfinitialize(servletcontext servletcontext) throws servletexception {
  prepareembeddedwebapplicationcontext(servletcontext);
  configurablelistablebeanfactory beanfactory = getbeanfactory();
  existingwebapplicationscopes existingscopes = new existingwebapplicationscopes(
     beanfactory);
  webapplicationcontextutils.registerwebapplicationscopes(beanfactory,
     getservletcontext());
  existingscopes.restore();
  webapplicationcontextutils.registerenvironmentbeans(beanfactory,
     getservletcontext());
  for (servletcontextinitializer beans : getservletcontextinitializerbeans()) {
   beans.onstartup(servletcontext);
  }
}

在第2行的prepareembeddedwebapplicationcontext方法中主要是将 embeddedwebapplicationcontext 设置为rootcontext。

第4行允许用户存储自定义的 scope。

第6行主要是用来将web专用的scope注册到beanfactory中,比如("request", "session", "globalsession", "application")。

第9行注册web专用的environment bean(比如 ("contextparameters", "contextattributes"))到给定的 beanfactory 中。

第11和12行,比较重要,主要用来配置 servlet、filters、listeners、context-param和一些初始化时的必要属性。

以其一个实现类servletcontextinitializer试举一例:

@override
 public void onstartup(servletcontext servletcontext) throws servletexception {
  assert.notnull(this.servlet, "servlet must not be null");
  string name = getservletname();
  if (!isenabled()) {
    logger.info("servlet " + name + " was not registered (disabled)");
    return;
  }
  logger.info("mapping servlet: '" + name + "' to " + this.urlmappings);
  dynamic added = servletcontext.addservlet(name, this.servlet);
  if (added == null) {
   logger.info("servlet " + name + " was not registered "
      + "(possibly already registered?)");
   return;
  }
  configure(added);
}

可以看第9行的打印: 正是在这里实现了 servlet 到 urlmapping的映射。

总结

这篇文章从主干脉络分析找到了为什么在springboot中不用自己配置tomcat,内置的容器是怎么启动起来的,顺便在分析的过程中找到了我们常用的 urlmapping 映射 servlet 的实现。