springboot如何启动内置tomcat?(源码详解)

简介: springboot如何启动内置tomcat?(源码详解)

传统项目为了启动,会放在tomcat下面,那么springboot为何不需要放在tomcat启动呢??因为springboot有内置tomcat启动项目,这篇文章从源码分析springboot如何启动内置tomcat。


// Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);
        // Register bean processors that intercept bean creation.
        registerBeanPostProcessors(beanFactory);
        // 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();

registerBeanPostProcessors我们onRefresh就是实现了这个类,前面的postBeanFactory是对容器扩展,这里的是对bean的对象进行扩展,有着beanPostProcessorBeaforeInstantiation和beanPostProcessorAfterInstantiation后置处理器,保证单实例 和 实例化完毕。

initApplicationEventMuliticaster是注册广播对象到容器,在实际代码开发中,会和applicationEvent和applicationListener使用。

熟悉了这里面的大致逻辑之后,我们这篇文章主要介绍onRefresh()方法,核心方法在这里:

//ServletWebServerApplicationContext.java
  @Override
  protected void onRefresh() {
    super.onRefresh();
    try {
      createWebServer();
    }
    catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
    }
  }
//父类的onRefresh
  @Override
  protected void onRefresh() {
    this.themeSource = UiApplicationContextUtils.initThemeSource(this);
  }

他super的父类没什么逻辑,主要看这个createWenServer():

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
      getBeanFactory().registerSingleton("webServerGracefulShutdown",
          new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop",
          new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
      try {
        getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
        throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
    }
    initPropertySources();
  }

这里面逻辑主要是当servletContext为空或者webServer为空,则就通过servletWebServerFactory创建一个webServer,或者走else if 里面的onStartup方法,最后都会执行initPropeertySources()。

那这几个组件是什么呢,首先上面说了servletWebServerFactory就是管理webServer的工厂,用来创建webServer。

servletContext指整个web请求是上下文对象,在tomcat中通常整个请求的上下文都封装在这个对象中。

webServer则是核心的组件,里面可以看到封装了web容器的启动和停止,获取端口的核心操作,也就是webServer是web容器的抽象封装。

@FunctionalInterface
public interface ServletWebServerFactory {
  /**
   * Gets a new fully configured but paused {@link WebServer} instance. Clients should
   * not be able to connect to the returned server until {@link WebServer#start()} is
   * called (which happens when the {@code ApplicationContext} has been fully
   * refreshed).
   * @param initializers {@link ServletContextInitializer}s that should be applied as
   * the server starts
   * @return a fully configured and started {@link WebServer}
   * @see WebServer#stop()
   */
  WebServer getWebServer(ServletContextInitializer... initializers);
}
public interface WebServer {
  /**
   * Starts the web server. Calling this method on an already started server has no
   * effect.
   * @throws WebServerException if the server cannot be started
   */
  void start() throws WebServerException;
  /**
   * Stops the web server. Calling this method on an already stopped server has no
   * effect.
   * @throws WebServerException if the server cannot be stopped
   */
  void stop() throws WebServerException;
  /**
   * Return the port this server is listening on.
   * @return the port (or -1 if none)
   */
  int getPort();
  /**
   * Initiates a graceful shutdown of the web server. Handling of new requests is
   * prevented and the given {@code callback} is invoked at the end of the attempt. The
   * attempt can be explicitly ended by invoking {@link #stop}. The default
   * implementation invokes the callback immediately with
   * {@link GracefulShutdownResult#IMMEDIATE}, i.e. no attempt is made at a graceful
   * shutdown.
   * @param callback the callback to invoke when the graceful shutdown completes
   * @since 2.3.0
   */
  default void shutDownGracefully(GracefulShutdownCallback callback) {
    callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
  }
}



getWebServer方法上面的注释翻译就是:

获取一个新的完全配置但暂停的{@link WebServer}实例。客户应不能连接到返回的服务器,直到{@link WebServer#start()}是调用(当{@code ApplicationContext}被完全调用时发生刷新)。

也就是说获取一个配置完成的webServer实例,当调用start方法的时候,applicationContext容器也就是spring容器已经刷新。

也就是说ServletWebServerFactory可以可以获取一个webServer,webServer可以通过start和stop操作容器。也就是springboot对web容器的抽象封装成为了webServer。

默认的我们会进入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);
  }

1)这里方法的入参是jdk1.8引入的函数表达式,这个表达式放了这个对象的数组,这个数组传进来的接口就是ServletContextInitializer。

2)创建了核心的tomcat组件。

3)创建了connector,newConnector,以及springboot特有的coustomizeConnector。

4)通过configureEngine配置了tomcat的引擎。

5)准备tomcat和context相关的属性。

6)真正的启动tomcat。


image.png


点开new 的tomcat我们可以看到,里面有port端口号,hostname:localhost,是不是都非常熟悉。

1)还有wrapper相关操作的方法,比如addServlet方法就是返回一个wrapper。

2)有一堆组件的get方法,比如前面的engine、service、host,connection。

3)有一些context相关的方法,比如createContext。

基本可以看到,这个tomcat类封装了几乎tomcat的所有核心组件。

下面看connector的基本创建和扩展:

public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private String protocol = DEFAULT_PROTOCOL;
public WebServer getWebServer(ServletContextInitializer... initializers) {
   //其他
   Tomcat tomcat = new Tomcat();
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   connector.setThrowOnFailure(true);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   //其他
}

这里可以看到connector传递的参数是上面的protocol,这个名称为什么用全名呢,我们从构造函数源码的 反射就可以看到:

public static ProtocolHandler create(String protocol, boolean apr)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        if (protocol == null || "HTTP/1.1".equals(protocol)
                || (!apr && org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.http11.Http11AprProtocol();
            } else {
                return new org.apache.coyote.http11.Http11NioProtocol();
            }
        } else if ("AJP/1.3".equals(protocol)
                || (!apr && org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.ajp.AjpAprProtocol();
            } else {
                return new org.apache.coyote.ajp.AjpNioProtocol();
            }
        } else {
            // Instantiate protocol handler
            Class<?> clazz = Class.forName(protocol);
            return (ProtocolHandler) clazz.getConstructor().newInstance();
        }
    }

也就是说new connection核心就是创建了http的组件,这里还有个扩展的关键点,就是

customizeConnector()方法,这里面有给connector配置很多属性和配置。
  // Needs to be protected so it can be used by subclasses
  protected void customizeConnector(Connector connector) {
    int port = Math.max(getPort(), 0);
    connector.setPort(port);
    if (StringUtils.hasText(getServerHeader())) {
      connector.setProperty("server", getServerHeader());
    }
    if (connector.getProtocolHandler() instanceof AbstractProtocol) {
      customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
    }
    invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
    if (getUriEncoding() != null) {
      connector.setURIEncoding(getUriEncoding().name());
    }
    // Don't bind to the socket prematurely if ApplicationContext is slow to start
    connector.setProperty("bindOnInit", "false");
    if (getSsl() != null && getSsl().isEnabled()) {
      customizeSsl(connector);
    }
    TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
    compression.customize(connector);
    for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
      customizer.customize(connector);
    }
  }
  private void customizeProtocol(AbstractProtocol<?> protocol) {
    if (getAddress() != null) {
      protocol.setAddress(getAddress());
    }
  }

通过上面可以看到对protocolHandler和connector进行了扩展,分别同的对应方法是invokeProtocoHandlerCustomizers和customizer.customize方法。这里面又跟这两个属性有关,

tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers

仔细可以看到这两个参数属于谁,TomcatServletWebServerFactory,前面说了webServer是通过ServletWebServerFactory获取的,那么这个TomcatServletWebServerFactory是哪里获取的呢,答案就是前面的onRefresh()。

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);
  }


image.png


其实从源码可以看到,虽然是从spring工厂按servletWebServerFactory类型查找bean的name,但是获取到的是beanDefinition。不过没关系,getBean容器中如果没有beanDefinition,它会进行bean实例化。那么bean的实例化会做什么呢,也就是servletWebServerFactory实例化会做什么呢,其实除了构造器之外,会给其扩展设置很多属性,而tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers就是他的属性,从而进行赋值。

大体就是初始化protocol相关的配置,比如setMaxThreads默认200、minSpareThreads默认10、maxHttpHeaderSize默认8192byte、maxSwallowSize 2097152等等。

熟悉了这个扩展点的逻辑后,其实最关键的是如何使用它,你可以通过ServerProperties扩展配置值,也可以自定义tomcatConnectorCustomizers或者tomcatProtocolHandlerCustomizers,只要实现对应的接口就可以了。这个才是领悟了SpringBoot的设计思路后最关键的。


TOMCAT的engine、context、host、wrapper关系


分析完connector组件后,再看看tomcat里面的组件建立关系,他们的关系也并不复杂,就是tomcat的基础知识,每个组件负责特定的事件处理。

image.png

springboot也对他们进行了扩展,比如对engine和context的阀门扩展,也是通过engineValues和contextValues进行扩展的。

//TomcatServletWebServerFactory.java
private List<Valve> engineValves = new ArrayList<>();
private List<Valve> contextValves = new ArrayList<>();

springboot主要用TomcatServletWebServerFactory对tomcat进行封装和扩展的。


下面就开始看prepareContext预处理servletContext:


protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
      context.setResources(new LoaderHidingResourceRoot(context));
    }
    context.setName(getContextPath());
    context.setDisplayName(getDisplayName());
    context.setPath(getContextPath());
    File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
    context.setDocBase(docBase.getAbsolutePath());
    context.addLifecycleListener(new FixContextListener());
    context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
        : ClassUtils.getDefaultClassLoader());
    resetDefaultLocaleMapping(context);
    addLocaleMappings(context);
    try {
      context.setCreateUploadTargets(true);
    }
    catch (NoSuchMethodError ex) {
      // Tomcat is < 8.5.39. Continue.
    }
    configureTldPatterns(context);
    WebappLoader loader = new WebappLoader();
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    loader.setDelegate(true);
    context.setLoader(loader);
    if (isRegisterDefaultServlet()) {
      addDefaultServlet(context);
    }
    if (shouldRegisterJspServlet()) {
      addJspServlet(context);
      addJasperInitializer(context);
    }
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    configureContext(context, initializersToUse);
    postProcessContext(context);
  }

1)new TomcatEmbeddedContext

2)为tomcat的这个Context设置了很多值

3)执行了一个扩展点ServletContextInitializer

下面我们来看看ServletContextInitializer怎么执行的,我们可以看到这行代码:

//ServletWebServerApplicationContext.java
private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
     beans.onStartup(servletContext);
  }
}

这里面主要有四个实现:

result = {ServletContextInitializerBeans@6345}  size = 4
0 = {DispatcherServletRegistrationBean@6339} "dispatcherServlet urls=[/]"
1 = {FilterRegistrationBean@6350} "characterEncodingFilter urls=[/*] order=-2147483648"
2 = {FilterRegistrationBean@6351} "formContentFilter urls=[/*] order=-9900"
3 = {FilterRegistrationBean@6352} "requestContextFilter urls=[/*] order=-105"

完整的分析webServlet创建之后,基本就知道了springboot如何整合的tomcat,里面有个tomcatServletWebServerFactory可以创建WebServer,最终怎么启动的呢?

webServcer里面的方法有个strat(),调用之后才会真正启动。

/TomcatServletWebServerFactory.java
   @Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
     //省略 Tomcat的创建、connector的创建和扩展、其他组件的创建、prepareContext的执行和扩展
    return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
     return new TomcatWebServer(tomcat, getPort() >= 0);
}
   public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
           Assert.notNull(tomcat, "Tomcat Server must not be null");
           this.tomcat = tomcat;
           this.autoStart = autoStart;
           initialize();
  }
  private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
      try {
        addInstanceIdToEngineName();
        Context context = findContext();
        context.addLifecycleListener((event) -> {
          if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
            // Remove service connectors so that protocol binding doesn't
            // happen when the service is started.
            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) {
          // Naming is not enabled. Continue
        }
        // Unlike Jetty, all Tomcat threads are daemon threads. We create a
        // blocking non-daemon to stop immediate shutdown
        startDaemonAwaitThread();
      }
      catch (Exception ex) {
        stopSilently();
        destroySilently();
        throw new WebServerException("Unable to start embedded Tomcat", ex);
      }
    }
  }

上面方法逻辑看似多,其实最关键的就一句话。这里核心是你抓大放小,主要关注一句话就可以了:

tomcat.start();

这个start方法执行的流程很有意思。它是类似一个链式调用。

其实你从之前tomcat的组件图就可以猜到,它们组件层级关系很多,每个组件都会触发下一层组件的逻辑。

每个组件都有生命周期,比如init方法-->start()-->destory()之类的。

那么也就说tomcat会以链的方式逐级调用各个模块的init()方法进行初始化, 待各个模块都初始化后, 又会逐级调用各个模块的start()方法启动各个模块。

相关文章
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
846 7
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
1242 1
|
10月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1727 5
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
745 70
|
供应链 JavaScript BI
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
这是一款专为小微企业打造的 SaaS ERP 管理系统,基于 SpringBoot+Vue+ElementUI+UniAPP 技术栈开发,帮助企业轻松上云。系统覆盖进销存、采购、销售、生产、财务、品质、OA 办公及 CRM 等核心功能,业务流程清晰且操作简便。支持二次开发与商用,提供自定义界面、审批流配置及灵活报表设计,助力企业高效管理与数字化转型。
944 2
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
881 0
|
小程序 Java 关系型数据库
weixin117新闻资讯系统设计+springboot(文档+源码)_kaic
本文介绍了一款基于微信小程序的新闻资讯系统,涵盖其开发全过程。该系统采用Java的SSM框架进行后台管理开发,使用MySQL作为本地数据库,并借助微信开发者工具确保稳定性。管理员可通过个人中心、用户管理等功能模块实现高效管理,而用户则能注册登录并查看新闻与视频内容。系统设计注重可行性分析(技术、经济、操作),强调安全性与数据完整性,界面简洁易用,功能全面,极大提升了信息管理效率及用户体验。关键词包括基于微信小程序的新闻资讯系统、SSM框架和MYSQL数据库。
|
小程序 JavaScript Java
基于SpringBoot的智慧停车场微信小程序源码分享
智慧停车场微信小程序主要包含管理端和小程序端。管理端包括停车场管理,公告信息管理,用户信息管理,预定信息管理,用户反馈管理等功能。小程序端包括登录注册,预约停车位,停车导航,停车缴费,用户信息,车辆信息,钱包充值,意见反馈等功能。
1128 5
基于SpringBoot的智慧停车场微信小程序源码分享
|
监控 前端开发 Java
SpringBoot集成Tomcat、DispatcherServlet
通过这些配置,您可以充分利用 Spring Boot 内置的功能,快速构建和优化您的 Web 应用。
1286 21
|
JavaScript Java 测试技术
基于SpringBoot+Vue实现的留守儿童爱心网站设计与实现(计算机毕设项目实战+源码+文档)
博主是一位全网粉丝超过100万的CSDN特邀作者、博客专家,专注于Java、Python、PHP等技术领域。提供SpringBoot、Vue、HTML、Uniapp、PHP、Python、NodeJS、爬虫、数据可视化等技术服务,涵盖免费选题、功能设计、开题报告、论文辅导、答辩PPT等。系统采用SpringBoot后端框架和Vue前端框架,确保高效开发与良好用户体验。所有代码由博主亲自开发,并提供全程录音录屏讲解服务,保障学习效果。欢迎点赞、收藏、关注、评论,获取更多精品案例源码。