用户您好!请先登录!

Tomcat系统架构概述

Tomcat系统架构概述

Tomcat 是一个 Web 应用服务器,它是对 HTTP 和 Servlet 规范的实现,简单来说它做了这几件事:处理 HTTP 协议、执行 Servlet 和处理网络 I/O。

Spring 框架就是对 Servlet 的封装,Spring 应用本身就是一个 Servlet,而 Servlet 容器是管理和运行 Servlet 的。

分享:详细讲解Tomcat之系统架构

Servlet 接口和 Servlet 容器这一整套规范叫作 Servlet 规范。Tomcat 和 Jetty 都按照 Servlet 规范的要求实现了 Servlet 容器。

Servlet 容器工作流程:

当客户请求某个资源时,HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,然后调用 Servlet 容器的 service 方法,Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器会把响应发送给客户端。

下图是Tomcat作为实现Servlet规范的工作流程:

分享:详细讲解Tomcat之系统架构

Servlet 规范提供了两种扩展机制:Filter和Listener。

  • Filter 是干预过程的,它是过程的一部分,是基于过程行为的。
  • Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致。

Tomcat系统架构

顶层架构:

让面试官颤抖的Tomcat系统架构系列

 

Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。

Service主要包含两个部分:Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下:

让面试官颤抖的Tomcat系统架构系列

一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接。

多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了!所以整个 Tomcat 的生命周期由 Server 控制。

  • Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;
  • Server掌管着整个Tomcat的生死大权;
  • Service 是对外提供服务的;
  • Connector用于接受请求并将请求封装成Request和Response来具体处理;
  • Container用于封装和管理Servlet,以及具体处理request请求;

Tomcat 要实现 2 个核心功能:

  • 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  • 加载和管理 Servlet,以及具体处理 Request 请求。

因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

分享:详细讲解Tomcat之系统架构

1. 连接器

连接器需要完成 3 个高内聚的功能:

  • 网络通信。
  • 应用层协议解析。
  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。

因此 Tomcat 的设计者设计了 3 个组件来实现这 3 个功能,分别是 EndPoint、Processor 和 Adapter。

Endpoint 和 Processor 放在一起抽象成了 ProtocolHandler 组件,连接器用 ProtocolHandler 来处理网络连接和应用层协议。

分享:详细讲解Tomcat之系统架构

EndPoint 是一个接口,它的抽象实现类 AbstractEndpoint 里面定义了两个内部类:Acceptor 和 SocketProcessor。其中 Acceptor 用于监听 Socket 连接请求。SocketProcessor 用于处理接收到的 Socket 请求。

EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。

分享:详细讲解Tomcat之系统架构

2. 容器

Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。

分享:详细讲解Tomcat之系统架构

Context 表示一个 Web 应用程序;Wrapper 表示一个 Servlet,一个 Web 应用程序中可能会有多个 Servlet;Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;Engine 表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。

请求定位 Servlet 的过程:Tomcat 会创建一个 Service 组件和一个 Engine 容器组件,在 Engine 容器下创建两个 Host 子容器,在每个 Host 容器下创建两个 Context 子容器。由于一个 Web 应用通常有多个 Servlet,Tomcat 还会在每个 Context 容器里创建多个 Wrapper 子容器。

分享:详细讲解Tomcat之系统架构

每一个容器都有一个 Pipeline 对象。

分享:详细讲解Tomcat之系统架构

3. 一个请求在 Tomcat 中流转的过程

分享:详细讲解Tomcat之系统架构

4. startup.sh 启动 tomcat 的过程

分享:详细讲解Tomcat之系统架构

 

服务器模型

服务器模型(或 I/O 模型),描述的是 TCP 连接的处理方式,以及 Socket 读写时线程的状态。Java 里常用的是 BIO 和 NIO,分别对应同步阻塞和同步非阻塞两种模型,Tomcat 中的 Connector 组件就是对这两种的封装实现。

BIO – 阻塞式

Tomcat 实现了一个一连接一线程的简单服务器模型,内部采用线程池做了优化,设计如下:

一线互联网大牛带你解析 Tomcat 架构概述
  • 当 Acceptor 接收到一个 TCP 连接时,线程池分配一个线程进行处理;
  • 线程调用 read() 方法读取 Socket 输入流中的字节,此时线程阻塞(Block),直到收到客户端发送的数据;
  • 收到数据后,进行解码、业务处理、编码,最后把响应发送到客户端,关闭连接。

由此可以看出,合理的分配线程池大小可以一定程度上提高系统的并发能力。

NIO – 非阻塞

BIO 的缺点在于不管当前连接有没有数据传输,它始终阻塞占用线程池内的一个线程,而 NIO 的处理方式是若通道无数据可读取,此时线程不阻塞直接返回,可用于处理其他连接,提高了线程利用率。那怎么知道什么时候处理数据的读写呢?当通道可读或可写时,内核会通知用户程序进行处理。

NIO 的编程比较复杂,常用的是 Reactor 模式,它描述了一个利用多路复用 I/O,基于事件驱动的服务器处理模型, (这里) 基于 Doug Lea 的 Scalable IO in Java 对 Reactor 进行了实现。Tomcat 的设计略有不同,其设计如下:

一线互联网大牛带你解析 Tomcat 架构概述
  • Acceptor 以阻塞模式接收 TCP 连接,然后将连接注册到 Poller 上;
  • Poller 以非阻塞模式处理 SSL 握手和 HTTP 请求头的读取;
  • BlockPoller 模拟阻塞处理 HTTP 请求体的读取和发送响应。

值得注意的是,两类 Poller 都只是负责事件的通知,I/O 操作都是由线程池中的线程完成。那么,ServerSocketChannel 为什么阻塞?为什么要模拟阻塞处理请求体和 Servlet 响应?相关的讨论可参考:

  • Why Tomcat’s Non-Blocking Connector is using a blocking socket?
  • Getting my head around NIO ‘simulated’ blocking (trying to)

Servlet API 的实现

Servlet 规范描述了容器如何加载和运行 Servlet,如和将请求映射到用户配置的 Servlet, 如何处理请求和响应等相关问题。Tomcat 主要实现了以下 API:

  • ServletConfig :Servlet 名字和初始化参数;
  • ServletContext :定义了 Web 应用程序,Servlet 运行的上下文;
  • ServletRequest :封装客户端请求;
  • ServletResponse :封装服务端响应;
  • FilterChain :请求过滤调用链;
  • FilterConfig :过滤配置对象;
  • RequestDispatcher :转发请求,将请求转发给 JSP 或另一个 Servlet 处理。

其他如 Servlet、Filter、GenericServlet、HttpServlet 接口或类则由用户程序来实现,更多详细的介绍请参考规范内容。

Tomcat启动及应用启动流程

1. Tomcat自身的启动脚本

  • tomcat的启动和关闭分别是通过执行bin目录下的startup.sh和shutdown.sh来实现的,而startup.sh和shutdown.sh里面会执行catalina.sh来完成实际的启动和关闭。在catalina.sh里面会获取或设置启动相关的环境变量,然后配置启动的各种参数,如下为启动脚本:可以看到是执行Bootstrap类。
  • Bootstrap类在加载时,会初始化类加载器体系,主要是commonLoader,catalinaLoader,sharedLoader这三个类加载器(默认为同一个),并使用catalinaLoader来通过放射的方式加载和创建org.apache.catalina.startup.Catalina实现,最后在main方法中,根据脚本执行参数来执行启动或停止。其中启动start时,依次调用Catalina的load和start:
    • load为加载解析conf/server.xml文件并创建StandardServer,StanderService,StandardHost对象实例,即只要server.xml中存在这些节点,则会创建对应的Container接口的实现类对象实例;
    • start为从StanderServer开始整个Catalina容器的启动。

2. Tomcat容器启动流程

server.xml文件解析

tomcat中对xml配置文件的解析是通过Digester来完成的。xml文件的解析一般有Dom4j和SAX两种方式组成,其中Dom4j为在内存中构建一棵Dom树来解析的,而SAX则是读取输入流,然后在读到某个xml节点时,触发相应的节点事件并回调相应的处理方法来实现的。而Digester在SAX的基础上,进行了封装,使得更加方便使用,目前Digester已经是一个Apache commons子项目:Digester。具体的解析规则包括:

  • 简单节点解析规则定义
  • 复杂节点解析规则定义
  • HostRuleSet
  • LifecycleListenerRule

LifeCycleListener事件监听机制完成各子容器的配置解析和创建

  • 在Host节点对应的StanderHost类的生命周期监听器LifeCycleListener列表,添加了一个org.apache.catalina.startup.HostConfig监听器实现。
  • 在Catalina体系的核心组件的生命周期是通过LifeCycle来管理的,即初始化,启动,停止都对应相关的状态和listener事件产生,并交给相应的监听器Listener处理。对于核心组件Host,Context的启动,分别是交给org.apache.catalina.startup包的HostConfig和ContextConfig来完成的。

webapp目录下ServletContext应用加载

  • HostConfig底层调用deployWAR方法来遍历webapp目录,并解压war包,解析META-INF/context.xml文件,创建StandardContext对象,然后加到StandardHost的children,即子容器列表。之后在StandardHost的startInternal中遍历已经填充好的children,调用StandardContext的start方法,从而对每个Context进行启动,初始化机制也是跟StandardHost一样,使用listener事件监听机制来完成实际创建,具体为交给ContextConfig。
  • ContextConfig与HostConfig类似,不过是在StandardContext的startInternal方法,触发ContextConfig的lifecycleEvent方法
  • ContextConfig底层调用configureStart来启动应用,核心方法为webConfig。
  • webConfig是tomcat非常重要的一个方法,主要是根据servlet规范,解析web.xml,以及合并包含的jar包里面的web-fragment.xml文件到web.xml,获取注解信息等等工作,完成对应java servlet的ServletContext的创建,代表一个应用的配置和启动。

3. Tomcat在SringBoot 中启动

3.1 SpringBoot的Main方法

用过SpringBoot的人都知道,首先要写一个main方法来启动

@SpringBootApplication
public class TomcatdebugApplication {
 public static void main(String[] args) {
 SpringApplication.run(TomcatdebugApplication.class, args);
 }
}

我们直接点击run方法的源码,跟踪下来,发下最终 的run方法是调用ConfigurableApplicationContext方法,源码如下:

public ConfigurableApplicationContext run(String... args) {
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 //设置系统属性『java.awt.headless』,为true则启用headless模式支持
 configureHeadlessProperty();
 //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,
 //找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
 //之后逐个调用其started()方法,广播SpringBoot要开始执行了
 SpringApplicationRunListeners listeners = getRunListeners(args);
 //发布应用开始启动事件
 listeners.starting();
 try {
 //初始化参数
 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
 //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
 //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
 configureIgnoreBeanInfo(environment);
 //打印banner
 Banner printedBanner = printBanner(environment);
 //创建应用上下文
 context = createApplicationContext();
 //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器
 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
 new Class[] { ConfigurableApplicationContext.class }, context);
 //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
 //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
 //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
 //这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
 //刷新上下文
 refreshContext(context);
 //再一次刷新上下文,其实是空方法,可能是为了后续扩展。
 afterRefresh(context, applicationArguments);
 stopWatch.stop();
 if (this.logStartupInfo) {
 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
 }
 //发布应用已经启动的事件
 listeners.started(context);
 //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
 //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
 callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
 handleRunFailure(context, ex, exceptionReporters, listeners);
 throw new IllegalStateException(ex);
 }
 try {
 //应用已经启动完成的监听事件
 listeners.running(context);
 }
 catch (Throwable ex) {
 handleRunFailure(context, ex, exceptionReporters, null);
 throw new IllegalStateException(ex);
 }
 return context;
 }

其实这个方法我们可以简单的总结下步骤为:

  1. 配置属性
  2. 获取监听器,发布应用开始启动事件
  3. 初始化输入参数
  4. 配置环境,输出banner
  5. 创建上下文
  6. 预处理上下文
  7. 刷新上下文
  8. 再刷新上下文
  9. 发布应用已经启动事件
  10. 发布应用启动完成事件

而启动Tomcat就是在第7步中“刷新上下文”,要分析tomcat内容的话,只需要关注两个内容即可,上下文是如何创建的,上下文是如何刷新的,分别对应的方法就是createApplicationContext() 和refreshContext(context),接下来我们来看看这两个方法做了什么。

protected ConfigurableApplicationContext createApplicationContext() {
 Class<?> contextClass = this.applicationContextClass;
 if (contextClass == null) {
 try {
 switch (this.webApplicationType) {
 case SERVLET:
 contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
 break;
 case REACTIVE:
 contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
 break;
 default:
 contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
 }
 }
 catch (ClassNotFoundException ex) {
 throw new IllegalStateException(
 "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
 ex);
 }
 }
 return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
 }

这里就是根据我们的webApplicationType 来判断创建哪种类型的Servlet,代码中分别对应着Web类型(SERVLET),响应式Web类型(REACTIVE),非Web类型(default),我们建立的是Web类型,所以肯定实例化

DEFAULT_SERVLET_WEB_CONTEXT_CLASS指定的类,也就是AnnotationConfigServletWebServerApplicationContext类,这个类继承的是ServletWebServerApplicationContext,这就是我们真正的主角,而这个类最终是继承了AbstractApplicationContext,了解完创建上下文的情况后,我们再来看看刷新上下文,相关代码如下:

//类:SpringApplication.java
private void refreshContext(ConfigurableApplicationContext context) {
 //直接调用刷新方法
 refresh(context);
 if (this.registerShutdownHook) {
 try {
 context.registerShutdownHook();
 }
 catch (AccessControlException ex) {
 // Not allowed in some environments.
 }
 }
 }
//类:SpringApplication.java
protected void refresh(ApplicationContext applicationContext) {
 Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
 ((AbstractApplicationContext) applicationContext).refresh();
 }

这里还是直接传递调用本类的refresh(context)方法,最后是强转成父类AbstractApplicationContext调用其refresh()方法,该代码如下:

// 类:AbstractApplicationContext 
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 {
 // 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()
 onRefresh();
 // Check for listener beans and register them.
 registerListeners();
 // Instantiate all remaining (non-lazy-init) singletons.
 finishBeanFactoryInitialization(beanFactory);
 // Last step: publish corresponding event.
 finishRefresh();
 }
 catch (BeansException ex) {
 if (logger.isWarnEnabled()) {
 logger.warn("Exception encountered during context initialization - " +
 "cancelling refresh attempt: " + ex);
 }
 // Destroy already created singletons to avoid dangling resources.
 destroyBeans();
 // Reset 'active' flag.
 cancelRefresh(ex);
 // Propagate exception to caller.
 throw ex;
 }
 finally {
 // Reset common introspection caches in Spring's core, since we
 // might not ever need metadata for singleton beans anymore...
 resetCommonCaches();
 }
 }
 }

这里我们看到onRefresh()方法是调用其子类的实现,根据我们上文的分析,我们这里的子类是ServletWebServerApplicationContext。

//类:ServletWebServerApplicationContext
protected void onRefresh() {
 super.onRefresh();
 try {
 createWebServer();
 }
 catch (Throwable ex) {
 throw new ApplicationContextException("Unable to start web server", ex);
 }
 }
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();
 }

到这里,其实庐山真面目已经出来了,createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,我们就来看看这个工厂的真面目。

3.2 走进Tomcat内部

工厂类是一个接口,各个具体服务的实现是由各个子类来实现的,所以我们就去看看TomcatServletWebServerFactory.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);
 }

根据上面的代码,我们发现其主要做了两件事情,第一件事就是把Connnctor(我们称之为连接器)对象添加到Tomcat中,第二件事就是configureEngine。

我们再看看Tomcat类的源码:

//部分源码,其余部分省略。
public class Tomcat {
//设置连接器
 public void setConnector(Connector connector) {
 Service service = getService();
 boolean found = false;
 for (Connector serviceConnector : service.findConnectors()) {
 if (connector == serviceConnector) {
 found = true;
 }
 }
 if (!found) {
 service.addConnector(connector);
 }
 }
 //获取service
 public Service getService() {
 return getServer().findServices()[0];
 }
 //设置Host容器
 public void setHost(Host host) {
 Engine engine = getEngine();
 boolean found = false;
 for (Container engineHost : engine.findChildren()) {
 if (engineHost == host) {
 found = true;
 }
 }
 if (!found) {
 engine.addChild(host);
 }
 }
 //获取Engine容器
 public Engine getEngine() {
 Service service = getServer().findServices()[0];
 if (service.getContainer() != null) {
 return service.getContainer();
 }
 Engine engine = new StandardEngine();
 engine.setName( "Tomcat" );
 engine.setDefaultHost(hostname);
 engine.setRealm(createDefaultRealm());
 service.setContainer(engine);
 return engine;
 }
 //获取server
 public Server getServer() {
 if (server != null) {
 return server;
 }
 System.setProperty("catalina.useNaming", "false");
 server = new StandardServer();
 initBaseDir();
 // Set configuration source
 ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
 server.setPort( -1 );
 Service service = new StandardService();
 service.setName("Tomcat");
 server.addService(service);
 return server;
 }
 //添加Context容器
 public Context addContext(Host host, String contextPath, String contextName,
 String dir) {
 silence(host, contextName);
 Context ctx = createContext(host, contextPath);
 ctx.setName(contextName);
 ctx.setPath(contextPath);
 ctx.setDocBase(dir);
 ctx.addLifecycleListener(new FixContextListener());
 if (host == null) {
 getHost().addChild(ctx);
 } else {
 host.addChild(ctx);
 }
 //添加Wrapper容器
 public static Wrapper addServlet(Context ctx,
 String servletName,
 Servlet servlet) {
 // will do class for name and set init params
 Wrapper sw = new ExistingStandardWrapper(servlet);
 sw.setName(servletName);
 ctx.addChild(sw);
 return sw;
 }
}

阅读Tomcat的getServer()我们可以知道,Tomcat的最顶层是Server,Server就是Tomcat的实例,一个Tomcat一个Server;通过getEngine()我们可以了解到Server下面是Service,而且是多个,一个Service代表我们部署的一个应用,而且我们还可以知道,Engine容器,一个service只有一个;根据父子关系,我们看setHost()源码可以知道,host容器有多个;同理,我们发现addContext()源码下,Context也是多个;addServlet()表明Wrapper容器也是多个,而且这段代码也暗示了,其实Wrapper和Servlet是一层意思。另外我们根据setConnector源码可以知道,连接器(Connector)是设置在service下的,而且是可以设置多个连接器(Connector)。

至此,Tomcat在SpringBoot启动就一目了然了。

行走的code
行走的code

要发表评论,您必须先登录