> Java > java지도 시간 > 본문

Tomcat의 전반적인 구조에 대한 자세한 설명

零下一度
풀어 주다: 2017-07-20 19:08:40
원래의
1629명이 탐색했습니다.

Tomcat의 전체 구조

Tomcat의 구조는 매우 복잡하지만 Tomcat도 매우 모듈화되어 있습니다. Tomcat의 핵심 모듈을 찾으면 Tomcat의 "7인치"를 파악할 수 있습니다. 다음은 Tomcat의 전체 구조 다이어그램입니다.

그림 1. Tomcat의 전체 구조

위 그림에서 볼 수 있듯이 Tomcat의 핵심은 커넥터와 컨테이너라는 두 가지 구성 요소입니다. 나중에 자세히 소개하겠습니다. 커넥터 구성 요소는 교체될 수 있으며, 이는 서버 디자이너에게 더 많은 선택권을 제공할 수 있습니다. 이 구성 요소는 매우 중요하기 때문에 서버 자체의 설계뿐만 아니라 다양한 애플리케이션 시나리오와도 관련되므로 컨테이너가 이에 대응하도록 선택할 수 있습니다. 여러 커넥터에. 여러 커넥터와 컨테이너가 서비스를 형성합니다. 서비스의 개념은 누구나 잘 알고 있습니다. 서비스를 사용하면 서비스를 외부 세계에 제공할 수 있지만 서비스에도 생명을 불어넣고 제어할 수 있는 환경이 필요합니다. 삶과 죽음의 권한은 서버에게 있습니다. 따라서 전체 Tomcat 수명주기는 서버에 의해 제어됩니다.

서비스를 "결혼"으로 사용하기

Tomcat의 커넥터와 컨테이너를 전체적으로 커플로 비유하면 Boy에 비하면 커넥터는 주로 외부 통신을 담당하고, 컨테이너는 주로 외부 통신을 담당합니다. 주로 내부 처리를 위해 커넥터가 수락한 요청은 소녀와 비교할 수 있습니다. 그렇다면 이 서비스는 남자와 여자를 연결해주는 결혼증명서입니다. 그들을 하나로 묶어 가족을 이루는 것이 바로 봉사입니다. 물론 가족을 이루는 데에는 다른 많은 요소들이 필요합니다.

직접 말하면 서비스는 커넥터와 컨테이너 외부에 추가 레이어를 감싸서 함께 조립하고 외부 세계에 서비스를 제공하는 것입니다. 서비스는 여러 커넥터를 설정할 수 있지만 컨테이너 컨테이너는 하나만 가질 수 있습니다. 이 서비스 인터페이스의 메소드 목록은 다음과 같습니다.

그림 2. 서비스 인터페이스

서비스 인터페이스에 정의된 메소드를 보면 주로 커넥터와 컨테이너를 연결하는 데 사용되며 초기화도 수행함을 알 수 있습니다. 그 아래에 있는 다른 구성요소. 인터페이스는 그 아래에 있는 구성요소의 수명 주기를 제어해야 한다고 규정하지 않습니다. 모든 구성요소의 라이프사이클은 Lifecycle 인터페이스에서 제어됩니다. 여기서는 중요한 디자인 패턴이 사용됩니다.

Tomcat에서 Service 인터페이스의 표준 구현 클래스는 StandardService입니다. 이는 Service 인터페이스를 구현할 뿐만 아니라 Lifecycle 인터페이스도 구현하므로 하위 구성 요소의 수명 주기를 제어할 수 있습니다. StandardService 클래스 구조 다이어그램은 다음과 같습니다.

그림 3. StandardService의 클래스 구조 다이어그램

위 그림에서 볼 수 있듯이 Service 인터페이스의 메소드 구현과 구성 요소 수명 주기를 제어하는 ​​Lifecycle 인터페이스에는 여러 가지 메서드가 있습니다. 이 메서드는 이 서비스 구성 요소뿐만 아니라 Tomcat의 다른 구성 요소에도 이러한 메서드가 있으며 이는 일반적인 디자인 패턴입니다. 나중에 소개할게.

StandardService의 주요 메소드로 구현된 코드를 살펴보겠습니다. 다음은 setContainer 및 addConnector 메소드의 소스 코드입니다.

Listing 1. StandardService.SetContainer
public void setContainer(Container container) {
    Container oldContainer = this.container;if ((oldContainer != null) && (oldContainer instanceof Engine))
        ((Engine) oldContainer).setService(null);this.container = container;if ((this.container != null) && (this.container instanceof Engine))
        ((Engine) this.container).setService(this);if (started && (this.container != null) && (this.container instanceof Lifecycle)) {try {
            ((Lifecycle) this.container).start();
        } catch (LifecycleException e) {
            ;
        }
    }synchronized (connectors) {for (int i = 0; i < connectors.length; i++)
            connectors[i].setContainer(this.container);
    }if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {try {
            ((Lifecycle) oldContainer).stop();
        } catch (LifecycleException e) {
            ;
        }
    }
    support.firePropertyChange("container", oldContainer, this.container);
}
로그인 후 복사

이 코드는 매우 간단합니다. 사실, 먼저 현재 이 서비스가 컨테이너와 연결되어 있는지 확인하는 것입니다. 그렇다면 연결을 제거하세요(oldContainer.setService(null)). 이 oldContainer가 시작된 경우 수명 주기를 종료합니다. 그런 다음 새 연결을 교체하고 이 새 컨테이너의 수명 주기를 다시 초기화하고 시작합니다. 마지막으로 관심 있는 이벤트 리스너에게 이 프로세스에 대한 알림이 전달됩니다. 여기서 주목할 만한 점은 컨테이너를 수정할 때 새 컨테이너가 각 커넥터와 연결되어야 한다는 것입니다. 다행히 컨테이너와 커넥터 간에는 양방향 연결이 없습니다. 그렇지 않으면 이 연결을 유지하기가 어렵습니다.

목록 2. StandardService.addConnector
public void addConnector(Connector connector) {synchronized (connectors) {
        connector.setContainer(this.container);
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;if (initialized) {try {
                connector.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }if (started && (connector instanceof Lifecycle)) {try {
                ((Lifecycle) connector).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("connector", null, connector);
    }
}
로그인 후 복사

上面是 addConnector 方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。

最新的 Tomcat6 中 StandardService 也基本没有变化,但是从 Tomcat5 开始 Service、Server 和容器类都继承了 MBeanRegistration 接口,Mbeans 的管理更加合理。

以 Server 为“居”

前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。

Server 要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。

Server 的类结构图如下:

图 4. Server 的类结构图

它的标准实现类 StandardServer 实现了上面这些方法,同时也实现了 Lifecycle、MbeanRegistration 两个接口的所有方法,下面主要看一下 StandardServer 重要的一个方法 addService 的实现:

清单 3. StandardServer.addService
public void addService(Service service) {
    service.setServer(this);synchronized (services) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;if (initialized) {try {
                service.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }if (started && (service instanceof Lifecycle)) {try {
                ((Lifecycle) service).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("service", null, service);
    }
}
로그인 후 복사

从上面第一句就知道了 Service 和 Server 是相互关联的,Server 也是和 Service 管理 Connector 一样管理它,也是将 Service 放在一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生命周期。Tomcat6 中也是没有什么变化的。

组件的生命线“Lifecycle”

前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?

Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中所有组件的生命周期,这个最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat。

下面是 Lifecycle 接口的类结构图:

图 5. Lifecycle 类结构图

除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。

Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的 Start 方法代码如下:

清单 4. StandardServer.Start
public void start() throws LifecycleException {if (started) {
        log.debug(sm.getString("standardServer.start.started"));return;
    }
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;synchronized (services) {for (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)
                ((Lifecycle) services[i]).start();
        }
    }
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
로그인 후 복사

监听的代码会包围 Service 组件的启动过程,就是简单的循环启动所有 Service 组件的 Start 方法,但是所有 Service 必须要实现 Lifecycle 接口,这样做会更加灵活。

Server 的 Stop 方法代码如下:

清单 5. StandardServer.Stop
public void stop() throws LifecycleException {if (!started)return;
    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;for (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)
            ((Lifecycle) services[i]).stop();
    }
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
로그인 후 복사

它所要做的事情也和 Start 方法差不多。

Connector 组件

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

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

图 6. Connector 处理一次请求顺序图

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

下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下 Connector 的主要类图:

图 7. Connector 的主要类图

看一下 HttpConnector 的 Start 方法:

清单 6. HttpConnector.Start
public void start() throws LifecycleException {if (started)throw new LifecycleException
            (sm.getString("httpConnector.alreadyStarted"));
    threadName = "HttpConnector[" + port + "]";
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
    threadStart();while (curProcessors < minProcessors) {if ((maxProcessors > 0) && (curProcessors >= maxProcessors))break;
        HttpProcessor processor = newProcessor();
        recycle(processor);
    }
}
로그인 후 복사

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

清单 7. HttpProcessor.assign
synchronized void assign(Socket socket) {while (available) {try {
            wait();
        } catch (InterruptedException e) {
        }
    }this.socket = socket;
    available = true;
    notifyAll();if ((debug >= 1) && (socket != null))
        log(" An incoming request is being assigned");
}
로그인 후 복사

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

Run 方法代码如下:

清单 8. HttpProcessor.Run
public void run() { while (!stopped) { 
        Socket socket = await(); if (socket == null) continue; try { 
            process(socket); 
        } catch (Throwable t) { 
            log("process.invoke", t); 
        } 
        connector.recycle(this); 
    } synchronized (threadSync) { 
        threadSync.notifyAll(); 
    } 
}
로그인 후 복사

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

清单 9. HttpProcessor.process
private void process(Socket socket) {boolean ok = true;boolean finishResponse = true;
    SocketInputStream input = null;
    OutputStream output = null;try {
        input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
    } catch (Exception e) {
        log("process.create", e);
        ok = false;
    }
    keepAlive = true;while (!stopped && ok && keepAlive) {
        finishResponse = true;try {
            request.setStream(input);
            request.setResponse(response);
            output = socket.getOutputStream();
            response.setStream(output);
            response.setRequest(request);
            ((HttpServletResponse) response.getResponse())
                .setHeader("Server", SERVER_INFO);
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }try {if (ok) {
                parseConnection(socket);
                parseRequest(input, output);if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
                    parseHeaders(input);if (http11) {
                    ackRequest(output);if (connector.isChunkingAllowed())
                        response.setAllowChunking(true);
                }
            }
        。。。。。。try {
            ((HttpServletResponse) response).setHeader
                ("Date", FastHttpDateFormat.getCurrentDate());if (ok) {
                connector.getContainer().invoke(request, response);
            }
            。。。。。。
        }try {
            shutdownInput(input);
            socket.close();
        } catch (IOException e) {
            ;
        } catch (Throwable e) {
            log("process.invoke", e);
        }
    socket = null;
}
로그인 후 복사

当 Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。

Servlet 容器“Container”

Container 是容器的父接口,所有子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一个 Servlet class 对应一个 Wrapper,如果有多个 Servlet 就可以定义多个 Wrapper,如果有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 通常就是对应下面这个配置:

清单 10. Server.xml
<Contextpath="/library"docBase="D:\projects\library\deploy\target\library.war"reloadable="true"/>
로그인 후 복사
로그인 후 복사

容器的总体设计

Context 还可以定义在父容器 Host 中,Host 不是必须的,但是要运行 war 程序,就必须要 Host,因为 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,如果要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 代表一个完整的 Servlet 引擎。

那么这些容器是如何协同工作的呢?先看一下它们之间的关系图:

图 8. 四个容器的关系图

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

图 9. Engine 和 Host 处理请求的时序图

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

清单 11. Server.xml
<Engine defaultHost="localhost" name="Catalina">
 <Valve className="org.apache.catalina.valves.RequestDumperValve"/>………<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"xmlNamespaceAware="false" xmlValidation="false">
 <Valve className="org.apache.catalina.valves.FastCommonAccessLogValve"directory="logs"  prefix="localhost_access_log." suffix=".txt"pattern="common" resolveHosts="false"/>     
    …………</Host></Engine>
로그인 후 복사

StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下执行。

前面是 Engine 和 Host 容器的请求过程,下面看 Context 和 Wrapper 容器时如何处理请求的。下面是处理请求的时序图:

图 10. Context 和 wrapper 的处理请求时序图

从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保存了当前请求正在处理的 Host、Context 和 wrapper。

Engine 容器

Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下:

图 11. Engine 接口的类结构

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

清单 12. StandardEngine. addChild
public void addChild(Container child) {if (!(child instanceof Host))throw new IllegalArgumentException
            (sm.getString("standardEngine.notHost"));super.addChild(child);
} 
public void setParent(Container container) {throw new IllegalArgumentException
        (sm.getString("standardEngine.notParent"));
}
로그인 후 복사

它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。

Host 容器

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

下面是和 Host 相关的类关联图:

图 12. Host 相关的类图

从上图中可以看出除了所有容器都继承的 ContainerBase 外,StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每个 web application。

Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法,Host 可以调用这些方法完成应用的部署等。

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 方法开始的,这个方法的代码片段如下:

清单 13. 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 属性,如下面配置:

清单 14. Server.xml
<Contextpath="/library"docBase="D:\projects\library\deploy\target\library.war"reloadable="true"/>
로그인 후 복사
로그인 후 복사

当这个 reloadable 设为 true 时,war 被修改后 Tomcat 会自动的重新加载这个应用。如何做到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码如下:

清单 15. StandardContext. backgroundProcess
public void backgroundProcess() {if (!started) return;
    count = (count + 1) % managerChecksFrequency;if ((getManager() != null) && (count == 0)) {try {
            getManager().backgroundProcess();
        } catch ( Exception x ) {
            log.warn("Unable to perform background process on manager",x);
        }
    }if (getLoader() != null) {if (reloadable && (getLoader().modified())) {try {
                Thread.currentThread().setContextClassLoader
                    (StandardContext.class.getClassLoader());
                reload();
            } finally {if (getLoader() != null) {
                    Thread.currentThread().setContextClassLoader
                        (getLoader().getClassLoader());
                }
            }
        }if (getLoader() instanceof WebappLoader) {
            ((WebappLoader) getLoader()).closeJARs(false);
        }
    }
}
로그인 후 복사

它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完成 Context 的一次重新加载。可以看出执行 reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个 backgroundProcess 方法是怎么被调用的呢?

这个方法是在 ContainerBase 类中定义的内部类 ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用所有容器的 backgroundProcess 方法,因为所有容器都会继承 ContainerBase 类,所以所有容器都能够在 backgroundProcess 方法中定义周期执行的事件。

Wrapper 容器

Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。

下面看一下非常重要的一个方法 loadServlet,代码片段如下:

清单 16. StandardWrapper.loadServlet
public synchronized Servlet loadServlet() throws ServletException {
    ………
    Servlet servlet;try {
        ………
        ClassLoader classLoader = loader.getClassLoader();
        ………
        Class classClass = null;
        ………
        servlet = (Servlet) classClass.newInstance();if ((servlet instanceof ContainerServlet) &&(isContainerProvidedServlet(actualClass) ||((Context)getParent()).getPrivileged() )) {
                ((ContainerServlet) servlet).setWrapper(this);
        }
        classLoadTime=(int) (System.currentTimeMillis() -t1);try {
            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);if( System.getSecurityManager() != null) {
                Class[] classType = new Class[]{ServletConfig.class};
                Object[] args = new Object[]{((ServletConfig)facade)};
                SecurityUtil.doAsPrivilege("init",servlet,classType,args);
            } else {
                servlet.init(facade);
            }if ((loadOnStartup >= 0) && (jspFile != null)) {
                ………if( System.getSecurityManager() != null) {
                    Class[] classType = new Class[]{ServletRequest.class,
                        ServletResponse.class};
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service",servlet,classType,args);
                } else {
                    servlet.service(req, res);
                }
            }
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);
            ………         return servlet;
}
로그인 후 복사

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

图 13. ServletConfig 与 StandardWrapperFacade、StandardWrapper 的关系

Servlet 可以获得的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 可以通过 ServletConfig 拿到有限的容器的信息。

当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 所有的 filter。

Tomcat 中其它组件

Tomcat 还有其它重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。 

위 내용은 Tomcat의 전반적인 구조에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!