本来想写完Servlet在将笔记中的Tomcat整理出来,但是发现不说清除Tomcat,Servlet根本没法说清楚,所以,这一篇主要梳理请求在Tomcat中到底发生了什么。

就像我们知道的Tomcat被作为WEB容器使用,当tomcat接收到请求后会发生下列事情:
之前有一篇文章说了大概的Tomcat的运行,但是,那篇文章将Tomcat和Servlet作为一个Web容器来讲了,并不是很详细
Tomcat简述

1.Tomcat流程

下面我试图将Tomcat更加详细的说明出来:
这里写图片描述
Tomcat中的六个容器:

容器作用
Server容器一个StandardServer类实例就表示一个Server容器,server是tomcat的顶级构成容器
Service容器一个StandardService类实例就表示一个Service容器,Tomcat的次顶级容器,Service是这样一个集合:它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。
Engine容器一个StandardEngine类实例就表示一个Engine容器。Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名。当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理
Host容器一个StandardHost类实例就表示一个Host容器,代表一个VirtualHost,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配。每个虚拟主机下都可以部署(deploy)一个或者多个WebApp,每个Web App对应于一个Context,有一个Context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。匹配的方法是“最长匹配”,所以一个path==”“的Context将成为该Host的默认Context。所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配
Context容器一个StandardContext类实例就表示一个Context容器。一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成。Context在创建的时候将根据配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml载入Servlet类。当Context获得请求时,将在自己的映射表(mappingtable)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回
Wrapper容器一个StandardWrapper类实例就表示一个Wrapper容器,Wrapper容器负责管理一个Servlet,包括Servlet的装载、初始化、资源回收。Wrapper是最底层的容器,其不能在添加子容器了。Wrapper是一个接口,其标准实现类是StandardWrapper

各容器的详细信息可阅读此博客

-Connector

 一个Connector将在某个指定端口上侦听客户请求,并将获得的请求交给Engine来处理,从Engine处获得回应并返回客户。
Tomcat有两个典型的Connector:
 - 一个直接侦听来自browser的http请求
 - 一个侦听来自其它WebServer的请求
Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求。
Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。

下面是流程图:

Created with Raphaël 2.1.2 客户端发送请求:http://localhost:8080/wsota/wsota_index.jsp 1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得 2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应 3) Engine获得请求localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机Host 4) Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机) 5) localhost Host获得请求/wsota/wsota_index.jsp,匹配它所拥有的所有Context 6) Host匹配到路径为/wsota的Context(如果匹配不到就把该请求交给路径名为""的Context去处理) 7) path="/wsota"的Context获得请求/wsota_index.jsp,在它的mapping table中寻找对应的servlet 8) Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类 9) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法 10)Context把执行完了之后的HttpServletResponse对象返回给Host 11)Host把HttpServletResponse对象返回给Engine 12)Engine把HttpServletResponse对象返回给Connector 13)Connector把HttpServletResponse对象返回给客户browser

这是一个宏观的路线

2.Tomcat的启动与类的具体实现

一个WEB应用对应一个context容器,也就是servlet运行时的servlet容器。

添加一个web应用时将会创建一个StandardContext容器,并且给这个context容器设置必要的参数,url和path分别代表这个应用在tomcat中的访问路径和这个应用实际的物理路径,这两个参数与tomcat配置中的两个参数是一致的。
其中一个最重要的一个配置是ContextConfig,这个类会负责整个web应用配置的解析工作。

最后将这个context容器加入到父容器host中。

接下来会调用tomcat的start方法启动tomcat。

Tomcat的启动逻辑是基于观察者模式的,所有的容器都会继承Lifecycle接口,它管理着容器的整个生命周期,所有容器的修改和状态改变都会由它通知已经注册的观察者。

Tomcat启动的时序如下:
这里写图片描述


仔细看看基本上就清楚了,这里只记录一下细节:


ContextConfig的init方法

当context容器初始状态设置Init时,添加到context容器的listener将会被调用。ContextConfig继承了LifecycleListener接口,它是在调用Tomcat.addWebapp时被加入到StandardContext容器中的。ContextConfig类会负责整个WEB应用的配置文件的解析工作。

  1. ContextConfig的init方法将会主要完成一下工作:
  2. 创建用于解析XML配置文件的contextDigester对象
  3. 读取默认的context.xml文件,如果存在则解析它
  4. 读取默认的Host配置文件,如果存在则解析它
  5. 读取默认的Context自身的配置文件,如果存在则解析它
  6. 设置Context的DocBase

startInternal方法

ContextConfig的init方法完成后,Context容器会执行startInternal方法,这个方法包括如下几个部分:

  1. 创建读取资源文件的对象
  2. 创建ClassLoader对象
  3. 设置应用的工作目录
  4. 启动相关的辅助类,如logger,realm,resources等
  5. 修改启动状态,通知感兴趣的观察者
  6. 子容器的初始化
  7. 获取ServletContext并设置必要的参数
  8. 初始化“load on startuo”的Servlet

Web应用的初始化

web应用的初始化在14步,下面是该初始化的详细内容

WEB应用的初始化工作是在ContextConfig的configureStart方法中实现的,应用的初始化工作主要是解析web.xml文件,这个文件是一个WEB应用的入口。

  1. Tomcat首先会找globalWebXml,这个文件的搜索路径是engine的工作目录下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。

  2. 接着会找hostWebXml,这个文件可能会在System.getProperty(“catalina.base”)/conf/$ {EngineName}/${HostName}/web.xml.default中。

  3. 接着寻找应用的配置文件examples/WEB-INF/web.xml,web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。

  4. 接下来会讲WebXml对象中的属性设置到context容器中,这里包括创建servlet对象,filter,listerner等,这些在WebXml的configureContext方法中。

以及下面是解析servlet的代码对象:

for (ServletDef servlet : servlets.values()) {  
    Wrapper wrapper = context.createWrapper();  
    String jspFile = servlet.getJspFile();  
    if (jspFile != null) {  
        wrapper.setJspFile(jspFile);  
    }  
    if (servlet.getLoadOnStartup() != null) {  
        wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());  
    }  
    if (servlet.getEnabled() != null) {  
        wrapper.setEnabled(servlet.getEnabled().booleanValue());  
    }  
    wrapper.setName(servlet.getServletName());  
    Map<String,String> params = servlet.getParameterMap();  
    for (Entry<String, String> entry : params.entrySet()) {  
        wrapper.addInitParameter(entry.getKey(), entry.getValue());  
    }  
    wrapper.setRunAs(servlet.getRunAs());  
    Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();  
    for (SecurityRoleRef roleRef : roleRefs) {  
        wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());  
    }  
    wrapper.setServletClass(servlet.getServletClass());  
    MultipartDef multipartdef = servlet.getMultipartDef();  
    if (multipartdef != null) {  
        if (multipartdef.getMaxFileSize() != null &&  
            multipartdef.getMaxRequestSize()!= null &&  
            multipartdef.getFileSizeThreshold() != null) {  
                wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                                      multipartdef.getLocation(),  
                                                      Long.parseLong(multipartdef.getMaxFileSize()),  
                                                  Long.parseLong(multipartdef.getMaxRequestSize()),  
                                                  Integer.parseInt(  
                                                  multipartdef.getFileSizeThreshold())));  
        } else {  
        wrapper.setMultipartConfigElement(new MultipartConfigElement(  
                                              multipartdef.getLocation()));  
        }  
    }  
    if (servlet.getAsyncSupported() != null) {  
        wrapper.setAsyncSupported(  
            servlet.getAsyncSupported().booleanValue());  
    }  
    context.addChild(wrapper);  
}

3.创建Servlet实例

前面完成了servlet的解析工作,并且被包装成了StandardWrapper添加到Context容器中,但是它仍然不能为我们工作,它还没有被实例化。

3.1.创建

如果Servlet的load-on-startup配置项大于0,那么在Context容器启动时就会被实例化。
(这个是在web.xml中进行了配置)
前面提到的在解析配置文件时会读取默认的globalWebXml,在conf下的web.xml文件中定义了一些默认的配置项,其中定义了两个Servlet,分别是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它们的load-on-startup分别是1和3,也就是当tomcat启动时这两个servlet就会被启动。

创建Servlet实例的方式是从Wrapper.loadServlet开始的,loadServlet方法要完成的就是获取servletClass,然后把它交给InstanceManager去创建一个基于servletClass.class的对象。如果这个Servlet配置了jsp-file,那么这个servletClass就是在conf/web.xml中定义的org.apache.jasper.servlet.JspServlet。

3.2.初始化

初始化Servlet在StandardWrapper的initServlet方法中,这个方法很简单,就是调用Servlet的init()方法,同时把包装了StandardWrapper对象的StandardWrapperFacade作为ServletConfig传给Servlet。

如果该Servlet关联的是一个JSP文件,那么前面初始化的就是JspServlet,接下来会模拟一次简单请求,请求调用这个JSP文件,以便编译这个JSP文件为类,并初始化这个类。

这样Servlet对象的初始化就完成了。

3.3.容器默认Servlet

每个servlet容器都有一个默认的servlet,一般都叫做default。

例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)

4.创建HttpServlet

这一篇细细说说请求是怎么交到Servlet手上的。此时,包括Servlet都已经初始化化完毕,一切已经准备就绪。
HttpServletRequest的产生大概在第四步

步骤过程详情
1AbstractEndpoint类及其子类来处理。AbstractEndpoint这个抽象类中有一个抽象内部类Acceptor,这个Acceptor的实现类是AbstractEndpoint的三个子类的内部类Acceptor来实现的。
1我们的请求就是被这Acceptor监听到并且接收的。这个类其实是一个线程类,因为AbstractEndpoint.Acceptor实现了Runnable接口。
1AprEndpoint接收请求的过程。就是用一个接收器接收请求,过程中会使用套接字。但是好像并不是有的请求都会用这个Acceptor来接收。
1当接收请求完毕,经过一系列的处理后就会由AprEndpoint的内部类SocketProcessor来将请求传给ProtocolHandler来处理。这个SocketProcessor也是一个线程类。它有一行代码将套接字传给了第二步来处理。
2在/AbstractConnectionHandler接收到第一步传来的套接字以后,对套接字进行处理wapper就是套接字包装类的对象,这里还是理解为套接字,套接字在这里传给了第③步的Processor接口的实例。state = processor.process(wrapper);
3第二步完成后,就会交给Processor接口的实现类来处理。在这里将会创建请求和响应,但不是我们熟悉的HttpServletRequest或HttpServletResponse类型或其子类型。而是org.apache.coyote.Request 和 org.apache.coyote.Response类型的
3它将会在Connector中来进行处理,我说的是处理而不是类型转换是因为org.apache.coyote.Request 和HttpServletRequest并不是父子关系的类,总之,HttpServletRequest的请求是由Connector来创建,在CoyoteAdapter中处理成HttpServletRequest.
3这个方法就是请求对象、响应对象和套接字进行信息交互的地方,也就是真真正正将套接字中的信息转化为请求信息,还要把响应信息写到套接字中。
4第三步完成之后交给CoyoteAdapter来处理CoyotoAdapter是将请求传入Server容器的切入点。Adapter. This represents the entry point in a coyote-based servlet container.
4(具体怎么处理成HttpServletRequest看代码)CoyoteAdapter中有一个service()方法。这个方法持有一个Connector的引用。这个Connector又持有一个Service容器的引用,而Service容器有持有一个Container(Container的实现类有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根据这些引用将请求传递到Server容器中了。
5如果上面的请求传递到的Container是StandaradEngine,那么就会Engine就会调用它持有的StandardPipeline对象来处理请求。StandardPipeline就相当于一条管道,这条管道中的有许多阀门,这些阀门会对请求进行处理,并且控制它下一步往哪里传递。StandardEngine的管道使用的阀门是StandardEngineValve。
6容器继续逐层传递和StandardEngine一样,StandardHost、StandardContext、StandardWrapper这几个容器都拥有自己的一条管道StandardPipeline来处理的请求。但是需要注意的是他们使用的阀门是不一样的。StandardHost则会使用StandardHostValve,其他的同理。
7当最后一个StandardWrapperVale处理完请求后,此时请求已经到达了最底层的容器了。StandardWrapper就是最底层的容器,它不允许再有子容器。其实每一个StandardWrapper代表一个Servlet,因为每一个StandardWrapper都会持有一个Servlet实例的引用。
//CoyoteAdapter的service(...)方法
/**
     * Service method.
     */
    @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {
     //下面这两行代码就是将请求处理后转化为HttpServletRequest的
        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);
      //如果请求为null就让Connector来创建。
        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);
      //省略部分代码
        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;
        boolean postParseSuccess = false;

        try {
            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container 在这里将请求传到Server容器中。
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

              //省略部分代码

            }

    }    /**

5.Tomcat核心类

Tomcat的体系结构:

这里写图片描述
一个Connector+一个Container构成一个Service,Service就是对外提供服务的组件,有了Service组件Tomcat就可以对外提供服务了。
那么这些个组件到底是干嘛用的呢?
Connector是一个连接器,主要负责接收请求并把请求交给Container
Container就是一个容器,主要装的是具体处理请求的组件。

Service主要是为了关联Container与Connector,一个单独的Container或者一个单独的Connector都不能完整处理一个请求,只有两个结合在一起才能完成一个请求的处理。

5.1 Tomcat的两大组件

1. Connecter组件

一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。
Tomcat中有两个经典的Connector,一个直接侦听来自Browser的HTTP请求,另外一个来自其他的WebServer请求。Cotote HTTP/1.1 Connector在端口8080处侦听来自客户Browser的HTTP请求,Coyote JK2 Connector在端口8009处侦听其他Web Server的Servlet/JSP请求。
Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。

2. Container组件

主容器,包含Tomcat的各个容器。
Container是容器的父接口,由四个容器组成,分别是Engine,Host,Context,Wrapper。其中Engine包含Host,Host包含Context,Content包含Wrapper,一个Servlet class对应一个Wrapper

6.总结

这篇文章是整理多篇文章所得,并不能保证完全正确,如有错误,感谢指正。

附件:
server.xml的配置:server.xml

参考:
文章大部分是网络资料,以及少量的自我梳理,以下是参考的链接:
http://www.cnblogs.com/GooPolaris/p/8111837.html

http://blog.csdn.net/res_cue/article/details/21756357

https://www.cnblogs.com/gy19920604/p/5377766.html

http://blog.csdn.net/simba1949/article/details/79242746

https://www.cnblogs.com/zhouyuqin/p/5143121.html

https://www.cnblogs.com/softidea/p/5384395.html

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐