当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot中如何启动Tomcat流程

SpringBoot中如何启动Tomcat流程

2019年07月19日  | 移动技术网IT编程  | 我要评论

前面在一篇文章中介绍了 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

此类的继承结构如图:

直接继承 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 的实现。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网