《How tomcat works》读书笔记_容器_总结

以Bootstrap2为例重新捋一遍

Connector连接器

the default connector class diagram

HttpConnector connector = new HttpConnector();

新建一个HttpConnector实例,并调用setContainer方法为连接器设置容器。

连接器由两个类组成:HttpConnector和HttpProcessor。

HttpConnector

HttpConnector类主要功能是创建一个服务器套接字来接受HTTP请求,同时初始化HttpProcessor。

initialize方法:创建一个java.net.ServerSocket实例,这是Java的一个服务器套接字。注意这里只是返回了一个ServerSocket实例,并没有执行这个实例的accept方法。

start方法:启动HttpConnector,由于HttpConnector运行在一个单独的线程上,所以start方法调用threadStart方法来启动这个线程,执行HttpConnector的run方法。之后,再初始化一个HttpProcessor实例的线程池。

run方法:在run方法中调用ServerSocket的accept方法启动服务器套接字,等待HTTP请求。如果获取到一个HTTP请求,那么就从HttpProcessor线程池中获取一个HttpProcessor实现,然后将这个socket请求传递给HttpProcessor进行处理。也就是,在HttpConnector调用HttpProcessor的assign方法后,不必等待其处理完成,直接回到accept方法等待下一个请求的到来。

HttpProcessor

HttpProcessor也实现了java.lang.Runnable接口,每一个HttpProcessor实例都是一个单独的线程。在HttpConnector中维护了一个HttpProcessor的线程池,默认有5个HttpProcessor线程在等待。在HttpConnector触发之前,它们都在阻塞状态。HttpConnector接收到一个请求时,就会从线程池中获取一个HttpProcessor实例,调用HttpProcessor的assign方法将其唤醒,处理assign方法传递给它的socket请求。

assign方法:在HttpConnector线程中调用HttpProcessor的assign方法。用另一个线程来唤醒当前线程,调用notifyAll方法将当前线程从wait中唤醒,并将socket请求赋给一个成员变量socket。

run方法:被唤醒后,run方法直接从其await方法处接着向下执行,调用process方法对socket请求进一步进行处理。然后调用HttpConnector的recycle方法回收这个HttpProcessor实例到线程池进行重用。需要注意的是,这里的HttpConnector实例是由在创建HttpProcessor时由其构造函数传入的。

process方法:process方法主要是解析HTTP请求并生成request和response对象。然后调用HttpConnector的getContainer方法获取关联到这个连接器的容器,然后调用容器的invoke方法进一步进行处理,主要是生成返回给客户端的内容。最后做一些清理的工作。

总结:

Good!这样我们就从连接器过渡到了容器,连接器在解析HTTP请求并生成相应的request和response对象之后就完成了它的使命,接下来的工作就交给容器来完成了。
那么,接下来我们总结下容器的工作原理以及处理过程,将这些内容串成一条线便于大家理解。
另外,连接器的主要的类还是HttpConnector,一个HttpConnector可以多个HttpProcessor,HttpProcessor类主要是方便HttpConnector可以一次处理多个HTTP请求。HttpProcessor对外是不可以见,所以连接器一应的设置都在HttpConnector上,这也是为什么 HttpProcessor的构造方法中要传递一个HttpConnector实例。

容器

这里写图片描述
如上面类图所示,每个容器最顶层的接口都是Container,有四种容器也就有四种不的接口,每一个接口都继承了Container接口。这就是整个容器的框架结构

容器

每个容器内部都单独维护了一个管道

private SimplePipeline pipeline = new SimplePipeline(this);

在容器的构造方法中会为其各自的管道设置基本Value

public SimpleWrapper() {
    pipeline.setBasic(new SimpleWrapperValve());
}

容器,顾名思义它只是一个存放东西的地方。它不作任何处理只起一个容纳作用。Egine封装多个Host;Host封装多个Context;Context封装多个Wrapper;Wrapper作为最底层容器封装最基本的servlet。
不同的容器除了有相同继承自Container接口的方法外,还有一些各自的方法,这里我们只以Context和Wrapper为例。

Wrapper

Wrapper中区别于其它容器最重要的两个方法是:allocate和load。

  • load方法:Wrapper容器中会关联一个Loader组件,load方法使用这个组件加载并初始化一个servlet.
  • allocate方法:分配一个servlet实例,确保这个servlet已经初始化完成,可以执行它的service方法。

Wrapper容器主要功能是提供一个加载并成功初始化的Servlet。将连接器生成的Request和Response对象传递给这个Servlet的service方法,由Servlet处理并最终返回结果给客户端。

Context

Context容器中比较重要的方法是map方法,map方法会根据request请求对象在Context容器中查找并返回一个Wrapper容器。这里需要一个Mapper的组件,每个Context容器会关联一个或以上的Mapper,一个Mapper对应一种协议。request请求对象中的url和可以处理这个url的Wrapper容器事先会被注册到这个Mapper中。

容器和管道

这里写图片描述

在Tomcat中管道是一个比较重要的类。一个管道可以多个Value以及一个必须的基本Value,Value可以任意添加,只要实现了Value接口的就可以。在管道的invoke方法中会依次调用每一个Value中的invoke方法,在最后调用它的基本Value的invoke方法。在前面的程序中,Value中的invoke方法主要是处理HTTP请求的Request对象,比较重要的是基本Value的invoke方法。基本Value的invoke方法会获取一个子容器并调用它的invoke方法,或是当前容器是Wrapper的话会获取一个servlet实例并调用它的service方法。

每个容器都有持有一个管道组件,

private SimplePipeline pipeline = new SimplePipeline(this);

管道实例以当前容器来构造。

容器中的invoke方法是一个比较重要的方法,容器的所有操作都是由这个方法开始的。

public void invoke(Request request, Response response)
        throws IOException, ServletException {
    pipeline.invoke(request, response);
}

上面是容器的invoke方法,Context和Wrapper容器的invoke方法完全是一模一样的。所以可以看出实际工作还是交给了管道来作。容器主要还是用于查找子容器或是如果是底层容器的话加载初始化Servlet。

另外,仔细观察会发现,所有容器与管道一样都实现了org.apache.catalina.Pipeline这个管道的接口。一般地,如果两个类同时实现了一个相同的接口,并且一个类持有了另一个类,这里的容器持有的管道类,那么这个容器也就相当于管道的一个门面类(设计模式)。那么,通过这个容器就可以执行它所持有管道上的操作,如新增删除Value等。

管道和基本Value

容器的工作会交给管道来完成,而管道的大部分工作会在其基本Value中完成。
基本Value中,会调用容器中的一些方法来完成工作,如果是上层容器,通过容器找到可以响应这个请求的下级容器,并调用下级容器的invoke方法

        Context context = (Context) getContainer();
        // Select the Wrapper to be used for this Request
        Wrapper wrapper = null;
        try {
            wrapper = (Wrapper) context.map(request, true);
        } catch (IllegalArgumentException e) {
            badRequest(requestURI,
                    (HttpServletResponse) response.getResponse());
            return;
        }
        if (wrapper == null) {
            notFound(requestURI, (HttpServletResponse) response.getResponse());
            return;
        }
        // Ask this Wrapper to process this Request
        response.setContext(context);
        wrapper.invoke(request, response);

这里需要注意的是:下级容器调用的invoke方法是接口Container中的而不是接口Pipeline中的。

如果是底层容器,如Wrapper。这时基本Value会调用Wrapper的load和allocate方法来生成一个初始化好的Servlet并调用Servlet的service方法。

        try {
            servlet = wrapper.allocate();
            if (hres != null && hreq != null) {
                servlet.service(hreq, hres);
            } else {
                servlet.service(sreq, sres);
            }
        } catch (ServletException e) {
        }

总结

综上所述,我们可以看出,所有容器都由invoke方法和管道串联起来的。

首先,连接器会关联一个容器,它不需要了解这个容器是具体是什么类型,是上层还底层。它只需要获取到这个容器调用它的invoke方法就可以了。

if (ok) {
   connector.getContainer().invoke(request, response);
}

getContainer方法会获取一个Container实例,java的向上转型,这个Container可以是一个Context,也可以是一个Wrapper。

其次,容器中的invoke方法没有做任何处理,直接调用了管道的invoke方法。

public void invoke(Request request, Response response)
        throws IOException, ServletException {
    pipeline.invoke(request, response);
}

上面已经说过,所有容器中的invoke方法长得都一样。
管道中会依次请求Value1,Value2,。。。对request和response对象进行处理,直至基本Value。

最后,在基本Value中,获取到经过Value1,Value2,。。。等等处理的request和response对象。基本Value在不同的容器中有不同的实现:在Context容器中,需要根据请求的url来返回一个子容器Wrapper;在Wrapper容器中,调用wrapper容器的load和allocate方法返回一个初始化完成的servlet,并调用这个servlet的service方法对请求进行处理,将结果返回给客户端。

最后的最后,整个过程就是:

  1. 连接器接收到一个请求,调用容器的invoke方法将这个请求传递给容器;
  2. 容器的invoke方法中直接调用其管道的invoke方法;
  3. 管道的invoke方法依次请求注册到管道内的Value1,Value2,。。。等等的invoke方法,直到其基本Value;
  4. 在基本Value的invoke方法中,如果当前容器还有子容器,直接调用这个容器的invoke方法,重新执行2~4步。如果当前容器没有子容器(如,Wrapper),则加载初始化一个servlet,并调用它的serice方法完成整个请求。

连接器和容器

连接器并不知道容器的细节,不论设置给连接器的容器是什么类型,它只调用容器的invoke方法就可以了。由于每个容器都必然实现了Container接口,也就是说每个容器必然都有一个invoke方法。这样就把连接器和容器之间的相互依赖就降到了最低,提供了程序的鲁棒性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值