关于java:面试官Tomcat-有哪些组成部分讲讲工作原理面试必问

6次阅读

共计 9425 个字符,预计需要花费 24 分钟才能阅读完成。

作者:VectorJin\
起源:juejin.cn/post/6844903473482317837

Tomcat 是什么

开源的 Java Web 应用服务器,实现了 Java EE(Java Platform Enterprise Edition)的部 分技术规范,比方 Java Servlet、Java Server Page、JSTL、Java WebSocket。Java EE 是 Sun 公 司为企业级利用推出的规范平台,定义了一系列用于企业级开发的技术规范,除了上述的之外,还有 EJB、Java Mail、JPA、JTA、JMS 等,而这些都依赖具体容器的实现。

上图比照了 Java EE 容器的实现状况,Tomcat 和 Jetty 都只提供了 Java Web 容器必须的 Servlet 和 JSP 标准,开发者要想实现其余的性能,须要本人依赖其余开源实现。

Glassfish 是由 sun 公司推出,Java EE 最新标准进去之后,首先会在 Glassfish 上进行实 现,所以是钻研 Java EE 最新技术的首选。

最常见的状况是应用 Tomcat 作为 Java Web 服务器,应用 Spring 提供的开箱即用的弱小 的性能,并依赖其余开源库来实现负责的业务性能实现。

另外,Tomcat 系列面试题和答案全副整顿好了,微信搜寻 Java 面试库小程序,能够在线刷题。

Servlet 容器

Tomcat 组成如下图:次要有 Container 和 Connector 以及相干组件形成。

Server:指的就是整个 Tomcat 服 务器,蕴含多组服务,负责管理和 启动各个 Service,同时监听 8005 端口发过来的 shutdown 命令,用 于敞开整个容器;

Service:Tomcat 封装的、对外提 供残缺的、基于组件的 web 服务,蕴含 Connectors、Container 两个 外围组件,以及多个性能组件,各 个 Service 之间是独立的,然而共享 同一 JVM 的资源;

Connector:Tomcat 与内部世界的连接器,监听固定端口接管内部申请,传递给 Container,并 将 Container 解决的后果返回给内部;

Container:Catalina,Servlet 容器,外部有多层容器组成,用于治理 Servlet 生命周期,调用 servlet 相干办法。

Loader:封装了 Java ClassLoader,用于 Container 加载类文件;Realm:Tomcat 中为 web 应用程序提供拜访认证和角色治理的机制;

JMX:Java SE 中定义技术规范,是一个为应用程序、设施、零碎等植入治理性能的框架,通过 JMX 能够近程监控 Tomcat 的运行状态;

Jasper:Tomcat 的 Jsp 解析引擎,用于将 Jsp 转换成 Java 文件,并编译成 class 文件。Session:负责管理和创立 session,以及 Session 的长久化(可自定义),反对 session 的集 群。

Pipeline:在容器中充当管道的作用,管道中能够设置各种 valve(阀门),申请和响应在经由管 道中各个阀门解决,提供了一种灵便可配置的解决申请和响应的机制。

Naming:命名服务,JNDI,Java 命名和目录接口,是一组在 Java 利用中拜访命名和目录服务的 API。命名服务将名称和对象分割起来,使得咱们能够用名称拜访对象,目录服务也是一种命名 服务,对象岂但有名称,还有属性。Tomcat 中能够应用 JNDI 定义数据源、配置信息,用于开发 与部署的拆散。

Container 组成

Engine:Servlet 的顶层容器,蕴含一 个或多个 Host 子容器;Host:虚拟主机,负责 web 利用的部 署和 Context 的创立;Context:Web 利用上下文,蕴含多个 Wrapper,负责 web 配置的解析、管 理所有的 Web 资源;Wrapper:最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创 建、执行和销毁。

生命周期治理Tomcat 为了方便管理组件和容器的生命周期,定义了从创立、启动、到进行、销毁共 12 中状态,tomcat 生命周期治理了外部状态变动的规定管制,组件和容器只需实现相应的生命周期 办法即可实现各生命周期内的操作(initInternal、startInternal、stopInternal、destroyInternal);

比方执行初始化操作时,会判断以后状态是否 New,如果不是则抛出生命周期异样;是的 话则设置以后状态为 Initializing,并执行 initInternal 办法,由子类实现,办法执行胜利则设置当 前状态为 Initialized,执行失败则设置为 Failed 状态;

Tomcat 的生命周期治理引入了事件机制,在组件或容器的生命周期状态发生变化时会通 知事件监听器,监听器通过判断事件的类型来进行相应的操作。事件监听器的增加能够在 server.xml 文件中进行配置;

Tomcat 各类容器的配置过程就是通过增加 listener 的形式来进行的,从而达到配置逻辑与 容器的解耦。如 EngineConfig、HostConfig、ContextConfig。EngineConfig:次要打印启动和进行日志 HostConfig:次要解决部署利用,解析利用 META-INF/context.xml 并创立利用的 Context ContextConfig:次要解析并合并 web.xml,扫描利用的各类 web 资源 (filter、servlet、listener)

Tomcat 的启动过程

启动从 Tomcat 提供的 start.sh 脚本开始,shell 脚本会调用 Bootstrap 的 main 办法,理论 调用了 Catalina 相应的 load、start 办法。

load 办法会通过 Digester 进行 config/server.xml 的解析,在解析的过程中会依据 xml 中的关系 和配置信息来创立容器,并设置相干的属性。接着 Catalina 会调用 StandardServer 的 init 和 start 办法进行容器的初始化和启动。

依照 xml 的配置关系,server 的子元素是 service,service 的子元素是顶层容器 Engine,每层容器有持有本人的子容器,而这些元素都实现了生命周期治理 的各个办法,因而就很容易的实现整个容器的启动、敞开等生命周期的治理。

StandardServer 实现 init 和 start 办法调用后,会始终监听来自 8005 端口(可配置),如果接管 到 shutdown 命令,则会退出循环监听,执行后续的 stop 和 destroy 办法,实现 Tomcat 容器的 敞开。同时也会调用 JVM 的 Runtime.getRuntime()﴿.addShutdownHook 办法,在虚拟机意外退 出的时候来敞开容器。

所有容器都是继承自 ContainerBase,基类中封装了容器中的反复工作,负责启动容器相干的组 件 Loader、Logger、Manager、Cluster、Pipeline,启动子容器(线程池并发启动子容器,通过 线程池 submit 多个线程,调用后返回 Future 对象,线程外部启动子容器,接着调用 Future 对象 的 get 办法来期待执行后果)。

List<Future<Void>> results = new ArrayList<Future<Void>>();
for (int i = 0; i < children.length; i++) {results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result:results) {
    try {result.get();
    } catch (Exception e) {log.error(sm.getString("containerBase.threadedStartFailed"),e);
        fail = true;
    }
}

Web 利用的部署形式 注:catalina.home:装置目录;catalina.base:工作目录; 默认值 user.dir

  • Server.xml 配置 Host 元素,指定 appBase 属性,默认 $catalina.base/webapps/
  • Server.xml 配置 Context 元素,指定 docBase,元素,指定 web 利用的门路
  • 自定义配置:在 $catalina.base/EngineName/HostName/XXX.xml 配置 Context 元素

HostConfig 监听了 StandardHost 容器的事件,在 start 办法中解析上述配置文件:

  • 扫描 appbase 门路下的所有文件夹和 war 包,解析各个利用的 META-INF/context.xml,并 创立 StandardContext,并将 Context 退出到 Host 的子容器中。
  • 解析 $catalina.base/EngineName/HostName/ 下的所有 Context 配置,找到相应 web 应 用的地位,解析各个利用的 META-INF/context.xml,并创立 StandardContext,并将 Context 退出到 Host 的子容器中。

注:

  • HostConfig 并没有理论解析 Context.xml,而是在 ContextConfig 中进行的。
  • HostConfig 中会定期检查 watched 资源文件(context.xml 配置文件)

ContextConfig 解析 context.xml 程序:

  • 先解析全局的配置 config/context.xml
  • 而后解析 Host 的默认配置 EngineName/HostName/context.xml.default
  • 最初解析利用的 META-INF/context.xml

ContextConfig 解析 web.xml 程序:

  • 先解析全局的配置 config/web.xml
  • 而后解析 Host 的默认配置 EngineName/HostName/web.xml.default 接着解析利用的 MEB-INF/web.xml
  • 扫描利用 WEB-INF/lib/ 下的 jar 文件,申请处理过程解析其中的 META-INF/web-fragment.xml 最初合并 xml 封装成 WebXml,并设置 Context

注:

  • 扫描 web 利用和 jar 中的注解 (Filter、Listener、Servlet) 就是上述步骤中进行的。
  • 容器的定期执行:backgroundProcess,由 ContainerBase 来实现的,并且只有在顶层容器 中才会开启线程。(backgroundProcessorDelay=10 标记位来管制)

Servlet 生命周期

Servlet 是用 Java 编写的服务器端程序。其次要性能在于交互式地浏览和批改数据,生成动静 Web 内容。

  1. 申请达到 server 端,server 依据 url 映射到相应的 Servlet
  2. 判断 Servlet 实例是否存在,不存在则加载和实例化 Servlet 并调用 init 办法
  3. Server 别离创立 Request 和 Response 对象,调用 Servlet 实例的 service 办法(service 办法 外部会依据 http 申请办法类型调用相应的 doXXX 办法)
  4. doXXX 办法内为业务逻辑实现,从 Request 对象获取申请参数,处理完毕之后将后果通过 response 对象返回给调用方
  5. 当 Server 不再须要 Servlet 时(个别当 Server 敞开时),Server 调用 Servlet 的 destroy() 方 法。

load on startup

当值为 0 或者大于 0 时,示意容器在利用启动时就加载这个 servlet; 当是一个正数时或者没有指定时,则批示容器在该 servlet 被抉择时才加载; 负数的值越小,启动该 servlet 的优先级越高;

single thread model

每次拜访 servlet,新建 servlet 实体对象,但并不能保障线程平安,同时 tomcat 会限度 servlet 的实例数目 最佳实际:不要应用该模型,servlet 中不要有全局变量

申请处理过程

  1. 依据 server.xml 配置的指定的 connector 以及端口监听 http、或者 ajp 申请
  2. 申请到来时建设连贯, 解析申请参数, 创立 Request 和 Response 对象, 调用顶层容器 pipeline 的 invoke 办法
  3. 容器之间层层调用, 最终调用业务 servlet 的 service 办法
  4. Connector 将 response 流中的数据写到 socket 中

Pipeline 与 Valve

Pipeline 能够了解为事实中的管道,Valve 为管道中的阀门,Request 和 Response 对象在管道中 通过各个阀门的解决和管制。

每个容器的管道中都有一个必不可少的 basic valve, 其余的都是可选的,basic valve 在管道中最 后调用, 同时负责调用子容器的第一个 valve。

Valve 中次要的三个办法:setNext、getNext、invoke;valve 之间的关系是单向链式构造, 自身 invoke 办法中会调用下一个 valve 的 invoke 办法。

各层容器对应的 basic valve 别离是 StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。

JSP 引擎

JSP 生命周期

  • 编译阶段:servlet 容器编译 servlet 源文 件, 生成 servlet 类
  • 初始化阶段: 加载与 JSP 对应的 servlet 类, 创立其实例, 并调用它的初始化办法
  • 执行阶段: 调用与 JSP 对应的 servlet 实例的 服务办法
  • 销毁阶段: 调用与 JSP 对应的 servlet 实例的 销毁办法, 而后销毁 servlet 实例

JSP 元素 代码片段:<% 代码片段 %> JSP 申明:<%! declaration; [declaration;]+ … %> JSP 表达式:<%= 表达式 %> JSP 正文:<%– 正文 –%> JSP 指令:<%@ directive attribute=“value”%> JSP 行为:<jsp:action_name attribute=“value”/> HTML 元素:html/head/body/div/p/… JSP 隐式对象:request、response、out、session、application、config、pageContext、page、Exception

JSP 元素阐明

代码片段: 蕴含任意量的 Java 语句、变量、办法或表达式; JSP 申明: 一个申明语句能够申明一个或多个变量、办法, 供前面的 Java 代码应用; JSP 表达式: 输入 Java 表达式的值,String 模式; JSP 正文: 为代码作正文以及将某段代码正文掉 JSP 指令: 用来设置与整个 JSP 页面相干的属性, <%@ page … %> 定义页面的依赖属性, 比方 language、contentType、errorPage、isErrorPage、import、isThreadSafe、session 等等 <%@ include … %> 蕴含其余的 JSP 文件、HTML 文件或文本文件, 是该 JSP 文件的一部分, 会 被同时编译执行 <%@ taglib … %> 引入标签库的定义, 能够是自定义标签 JSP 行为:jsp:include、jsp:useBean、jsp:setProperty、jsp:getProperty、jsp:forward

Jsp 解析过程

  • 代码片段: 在_jspService()办法内间接输入
  • JSP 申明: 在 servlet 类中进行输入
  • JSP 表达式: 在_jspService()办法内间接输入
  • JSP 正文: 间接疏忽, 不输入
  • JSP 指令: 依据不同指令进行辨别,include: 对引入的文件进行解析;page 相干的属性会做为 JSP 的属性, 影响的是解析和申请解决时的行为
  • JSP 行为: 不同的行为有不同的解决形式,jsp:useBean 为例, 会从 pageContext 依据 scope 的 类别获取 bean 对象, 如果没有会创立 bean, 同时存到相应 scope 的 pageContext 中
  • HTML: 在_jspService()办法内间接输入
  • JSP 隐式对象: 在_jspService()办法会进行申明, 只能在办法中应用;

Connector

Http:HTTP 是超文本传输协定, 是客户端浏览器或其余程序与 Web 服务器之间的应用层通信协 议 AJP:Apache JServ 协定 (AJP) 是一种二进制协定, 专门代理从 Web 服务器到位于后端的利用 程序服务器的入站申请 阻塞 IO

非阻塞 IO

IO 多路复用

阻塞与非阻塞的区别在于进行读操作和写操作的零碎调用时,如果此时内核态没有数据可读或者没有缓冲空间可写时,是否阻塞。

IO 多路复用的益处在于可同时监听多个 socket 的可读和可写事件,这样就能使得利用能够同时监听多个 socket,开释了利用线程资源。

Tomcat 各类 Connector 比照

Connector 的实现模式有三种,别离是 BIO、NIO、APR,能够在 server.xml 中指定。

  • JIO:用 java.io 编写的 TCP 模块,阻塞 IO
  • NIO:用 java.nio 编写的 TCP 模块,非阻塞 IO,(IO 多路复用)
  • APR:全称 Apache Portable Runtime,应用 JNI 的形式来进行读取文件以及进行网络传输

Apache Portable Runtime 是一个高度可移植的库,它是 Apache HTTP Server 2.x 的外围。APR 具备许多用处,包含拜访高级 IO 性能(如 sendfile,epoll 和 OpenSSL),操作系统级性能(随机数生成,零碎状态等)和本地过程解决(共享内存,NT 管道和 Unix 套接字)。

表格中字段含意阐明:

  • Support Polling:是否反对基于 IO 多路复用的 socket 事件轮询
  • Polling Size:轮询的最大连接数
  • Wait for next Request:在期待下一个申请时,解决线程是否开释,BIO 是没有开释的,所以在 keep-alive=true 的状况下解决的并发连接数无限
  • Read Request Headers:因为 request header 数据较少,能够由容器提前解析结束,不须要阻塞
  • Read Request Body:读取 request body 的数据是利用业务逻辑的事件,同时 Servlet 的限度,是须要阻塞读取的
  • Write Response:跟读取 request body 的逻辑相似,同样须要阻塞写

NIO 解决相干类

Acceptor 线程负责接管连贯,调用 accept 办法阻塞接管建设的连贯,并对 socket 进行封装成 PollerEvent,指定注册的事件为 op_read,并放入到 EventQueue 队列中,PollerEvent 的 run 办法逻辑的是将 Selector 注册到 socket 的指定事件;

Poller 线程从 EventQueue 获取 PollerEvent,并执行 PollerEvent 的 run 办法,调用 Selector 的 select 办法,如果有可读的 Socket 则创立 Http11NioProcessor,放入到线程池中执行;

CoyoteAdapter 是 Connector 到 Container 的适配器,Http11NioProcessor 调用其提供的 service 办法,外部创立 Request 和 Response 对象,并调用最顶层容器的 Pipeline 中的第一个 Valve 的 invoke 办法

Mapper 次要解决 http url 到 servlet 的映射规定的解析,对外提供 map 办法

NIO Connector 主要参数

Comet

Comet 是一种用于 web 的推送技术,能使服务器实时地将更新的信息传送到客户端,而无须客户端发出请求 在 WebSocket 进去之前,如果不应用 comet,只能通过浏览器端轮询 Server 来模仿实现服务器端推送。Comet 反对 servlet 异步解决 IO,当连贯上数据可读时触发事件,并异步写数据(阻塞)

Tomcat 要实现 Comet,只需继承 HttpServlet 同时,实现 CometProcessor 接口

  • Begin:新的申请连贯接入调用,可进行与 Request 和 Response 相干的对象初始化操作,并保留 response 对象,用于后续写入数据
  • Read:申请连贯有数据可读时调用
  • End:当数据可用时,如果读取到文件完结或者 response 被敞开时则被调用
  • Error:在连贯上产生异样时调用,数据读取异样、连贯断开、解决异样、socket 超时

Note:

  • Read:在 post 申请有数据,但在 begin 事件中没有解决,则会调用 read,如果 read 没有读取数据,在会触发 Error 回调,敞开 socket
  • End:当 socket 超时,并且 response 被敞开时也会调用;server 被敞开时调用
  • Error:除了 socket 超时不会敞开 socket,其余都会敞开 socket
  • End 和 Error 工夫触发时应敞开以后 comet 会话,即调用 CometEvent 的 close 办法 Note:在事件触发时要做好线程平安的操作

异步 Servlet

传统流程:

  • 首先,Servlet 接管到申请之后,request 数据解析;
  • 接着,调用业务接口的某些办法,以实现业务解决;
  • 最初,依据解决的后果提交响应,Servlet 线程完结

异步解决流程:

  • 客户端发送一个申请
  • Servlet 容器调配一个线程来解决容器中的一个 servlet
  • servlet 调用 request.startAsync(),保留 AsyncContext, 而后返回
  • 任何形式存在的容器线程都将退出,然而 response 依然放弃凋谢
  • 业务线程应用保留的 AsyncContext 来实现响应(线程池)
  • 客户端收到响应

Servlet 线程将申请转交给一个异步线程来执行业务解决,线程自身返回至容器,此时 Servlet 还没有生成响应数据,异步线程解决完业务当前,能够间接生成响应数据(异步线程领有 ServletRequest 和 ServletResponse 对象的援用)

为什么 web 利用中反对异步?

推出异步,次要是针对那些比拟耗时的申请:比方一次迟缓的数据库查问,一次内部 REST API 调用, 或者是其余一些 I / O 密集型操作。这种耗时的申请会很快的耗光 Servlet 容器的线程池,继而影响可扩展性。

Note:从客户端的角度来看,request 依然像任何其余的 HTTP 的 request-response 交互一样,只是消耗了更长的工夫而已

异步事件监听

  • onStartAsync:Request 调用 startAsync 办法时触发
  • onComplete:syncContext 调用 complete 办法时触发
  • onError:解决申请的过程出现异常时触发
  • onTimeout:socket 超时触发

Note : onError/ onTimeout 触发后,会紧接着回调 onComplete onComplete 执行后,就不可再操作 request 和 response

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0