优秀的编程知识分享平台

网站首页 > 技术文章 正文

Tomcat 系统架构分析②(tomcat架构图)

nanyue 2024-09-01 20:40:06 技术文章 15 ℃

承接Tomcat 系统架构分析①

2. Connector 组件
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。

由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:

Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外Coyote 也定义自己的 Request 和 Response 对象。
下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下Connector 的主要类图:

看一下 HttpConnector 的 Start 方法:

threadStart() 执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在 HttpProcessor 的 assign 方法中,这个方法是代码如下 :


创建 HttpProcessor 对象是会把 available 设为 false,所以当请求到来时不会进入 while 循环,将请求的 socket 赋给当期处理的socket,并将 available 设为 true,当 available 设为 true 是HttpProcessor 的 run 方法将被激活,接下去将会处理这次请求。Run 方法代码如下:

解析 socket 的过程在 process 方法中, process 方法的代码片段如下

当Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。
3. Servlet 容器“Container”,Container 是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是: Engine、 Host、Context、Wrapper,这四个组件不是平行的,而是父子关系, Engine 包含 Host,Host 包含Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 通常就是对应下面这个配置:
3.1. Server.xml
<Context path="/library docBase="D:\projects\library\deploy\target\library.war"reloadable="true"/>

3.2. 容器的总体设计
Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。
那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:

当 Connector 接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet处理。下面是这个过程的时序图:

这里看到了 Valve 是不是很熟悉,没错 Valve 的设计在其他框架中也有用的,同样 Pipeline 的原理也基本是相似的,它是一个管道,Engine 和 Host 都会执行这个 Pipeline,您可以在这个管道上增加任意的 Valve, Tomcat 会挨个执行这些 Valve,而且四个组件都会有自己的一套 Valve 集合。您怎么才能定义自己的 Valve 呢?在 server.xml 文件中可以添加,如给 Engine 和 Host 增加一个 Valve如下

StandardEngineValve 和 StandardHostValve 是 Engine 和 Host的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下执行。前面是 Engine 和 Host 容器的请求过程,下面看 Context 和Wrapper 容器时如何处理请求的。下面是处理请求的时序图:

从 Tomcat5 开始,子容器的路由放在了 request 中, request 中保存了当前请求正在处理的 Host、 Context 和 wrapper。
3.3. Engine 容器,Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下

它的标准实现类是 StandardEngine,这个类注意一点就是 Engine没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也只能是 Host 类型的,代码如下

它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。
3.4. Host 容器,Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。

从上图中可以看出除了所有容器都继承的 ContainerBase 外,StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个 web
application。Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法, Host 可以调用这些方法完成应用的部署等
3.5. Context 容器,Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat可以没有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 实例, Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 以前是通过一个 Mapper 类来管理的, Tomcat5 以后这个功能被移到了 request中,在前面的时序图中就可以发现获取子容器都是通过 request 来分配的。Context 准备 Servlet 的运行环境是在 Start 方法开始的,这个方法的代码片段如下:
1). StandardContext.start
public synchronized void start() throws LifecycleException {
………
if( !initialized ) {
try {
init();
} catch( Exception ex ) {
throw new LifecycleException("Error initializaing ", ex);
}
}
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
setAvailable(false);
setConfigured(false);
boolean ok = true;
File configBase = getConfigBase();
if (configBase != null) {
if (getConfigFile() == null) {
File file = new File(configBase, getDefaultConfigFile());
setConfigFile(file.getPath());
try {
File appBaseFile = new File(getAppBase());
if (!appBaseFile.isAbsolute()) {
appBaseFile = new File(engineBase(), getAppBase());}
String appBase = appBaseFile.getCanonicalPath();
String basePath =
(new File(getBasePath())).getCanonicalPath();
if (!basePath.startsWith(appBase)) {
Server server = ServerFactory.getServer();
((StandardServer) server).storeContext(this);}
} catch (Exception e) {
log.warn("Error storing config file", e);}
} else {
try {
String canConfigFile = (new
File(getConfigFile())).getCanonicalPath();
if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {
File file = new File(configBase, getDefaultConfigFile());
if (copy(new File(canConfigFile), file)) {
setConfigFile(file.getPath());}}
} catch (Exception e) {
log.warn("Error setting config file", e);}}}
………
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Lifecycle)
((Lifecycle) children[i]).start();}
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
………}
它主要是设置各种资源属性和管理组件,还有非常重要的就是启动子容器和 Pipeline。我们知道 Context 的配置文件中有个 reloadable 属性,如下面配置:
2). Server.xml<Context path="/library" docBase="D:\projects\library\deploy\target\library.war"reloadable="true"/> 当这个 reloadable 设为 true 时, war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下

它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完成 Context 的一次重新加载。可以看出执行reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个backgroundProcess 方法是怎么被调用的呢?这个方法是在 ContainerBase 类中定义的内部类ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用所有容器的 backgroundProcess 方法,因为所有容器都会继承
ContainerBase 类,所以所有容器都能够在 backgroundProcess 方法中定义周期执行的事件

3.6. Wrapper 容器,Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的Servlet 的装载、初始化、执行以及资源回收。 Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。Wrapper 的实现类是 StandardWrapper, StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出StandardWrapper 将直接和 Servlet 的各种信息打交道。下面看一下非常重要的一个方法 loadServlet,代码片段如下:
1). StandardWrapper.loadServlet

它基本上描述了对 Servlet 的操作,当装载了 Servlet 后就会调用Servlet 的 init 方法,同时会传一个 StandardWrapperFacade 对象 给 Servlet,这个对象包装了 StandardWrapper, ServletConfig 与它们的关系图如下:

Servlet 可以获得的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的filter。

Tags:

最近发表
标签列表