码迷,mamicode.com
首页 > 其他好文 > 详细

How Tomcat Works 5

时间:2015-04-19 11:36:37      阅读:185      评论:0      收藏:0      [点我收藏+]

标签:tomcat   web.xml   servlet   

上一节简单介绍了tomcat中的流水线,当connector收到一条消息的时候,将socket交给processor来处理,processor构造出来request和response对象并解析http请求,然后processor调用container的的invoke方法来处理这两个对象。invoke方法是父类ContainerBase中的方法,主要是调用该Container对应的Pipeline处理请求。Tomcat中StandardEngine对应的pipeline是StandardPipeline,其中的basicValve是StandardEngineValve,每个basicValve都是该容器中最后被调用的valve。它其中的一个任务便是调用自己下层组件的invoke方法,继续流水线的处理,直到最后的的wrappervalve。值得提一点的是一个servlet对应一个wrapper。流程图如下:

技术分享

本节重点介绍的是tomcat处理一个请求的大体过程。

在tomcat的外层web.xlm会配置一个默认的serverlet——org.apache.catalina.servlets.DefaultServlet当根据mapping未匹配到需要处理的servlet的时候,会使用该servlet处理请求,DefaultServlet在tomcat启动的时候便会被加载。

假设我们访问的地址为http://localhost:8080/myapp/primi myapp是我自己写的一个app,非常简单的结构如下:

技术分享

在WEB-INF下只有一个web.xml,配置了servlet的映射关系

  <servlet>
    <servlet-name>PrimitiveServlet</servlet-name>
    <servlet-class>PrimitiveServlet</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>2</param-value>
    </init-param>
  </servlet>

  <!-- Define the Manager Servlet Mapping -->
  
  <servlet-mapping>
    <servlet-name>PrimitiveServlet</servlet-name>
    <url-pattern>/primi</url-pattern>
  </servlet-mapping>

那么下面就来看看tomcat是如何一步步找到这个servlet并处理我们的请求的。

技术分享

connector将收到的请求交给Engine来处理,一个在Engine的标准valve中会调用下面的方法来匹配到对应的host

// Select the Host to be used for this Request
        StandardEngine engine = (StandardEngine) getContainer();
        Host host = (Host) engine.map(request, true);
        if (host == null) {
            ((HttpServletResponse) response.getResponse()).sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost",
                              request.getRequest().getServerName()));
            return;
        }

        // Ask this Host to process this request
        host.invoke(request, response);
可以看到通过engine的map方法找到对应的host,其中在tomcat4版本中匹配url的过程是通过Mapper类完成的,这里会匹配到标识为localhost的Host容器,也就是所有对localhost的访问都会由localhost容器处理,获取到对应的Host后调用它的invoke方法,接下来的处理过程类似上面。

// Select the Context to be used for this Request
        StandardHost host = (StandardHost) getContainer();
        Context context = (Context) host.map(request, true);
        if (context == null) {
            ((HttpServletResponse) response.getResponse()).sendError
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                 sm.getString("standardHost.noContext"));
            return;
        }
Context对应的就是webapps目录下一个个的应用程序,由Host匹配合适Context的方法在StandardHostMapper类中:

public Container map(Request request, boolean update) {
        // Has this request already been mapped?
        if (update && (request.getContext() != null))
            return (request.getContext());

        // Perform mapping on our request URI
        String uri = ((HttpRequest) request).getDecodedRequestURI();
        Context context = host.map(uri);

        // Update the request (if requested) and return the selected Context
        if (update) {
            request.setContext(context);
            if (context != null)
                ((HttpRequest) request).setContextPath(context.getPath());
            else
                ((HttpRequest) request).setContextPath(null);
        }
        return (context);

    }
获取到uri,该例子中为/myapp/primi。由于我们在server.xml中配置了localhost下有一个名字为myapp的Context,所以按照从后往前按照反斜杠/截取的过程,最终会匹配到mayapp这个context,同样类似上面的处理过程,context的invoke方法会被调用。也就是请求和响应消息被传递给名字为myapp的context来处理。

StandardContextValve中队消息的处理代码如下:

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);
上面的主要逻辑是在一个应用上下文中匹配出对该消息处理的servlet,这里是一个Wrapper。由于我们设置了servletmapping,因此会匹配到StandardWrapper[PrimitiveServlet]这个wrapper。最后会由StandardWrapperValve处理该消息,主要逻辑如下:

servlet = wrapper.allocate();
上面是非常重要的一个步骤,就是获取一个对应的Servlet实例,allocate的主要逻辑如下:

if (!singleThreadModel) {

            // Load and initialize our instance if necessary
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            instance = loadServlet();
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }

            if (!singleThreadModel) {
                if (debug >= 2)
                    log("  Returning non-STM instance");
                countAllocated++;
                return (instance);
            }

        }
该方法中会判断一个Servlet是否实现了单线程的模式,如果不是单线程的模式每次都会返回相同的实例,也就是只有一个实例存在。生成servlet的方法由loadServlet使用类加载器加载对应的Servelet,加载过程我们前面几节已经涉及到了,这里不再赘述(针对servlet的init和service等方法的调用是在loadServlet方法中完成的)。如果是单线程模式会将特定数量的实例放到对象池中,每次从池中获取可用的对象,如果池中没有可用的实例,此时会阻塞,可以看出单线程模式会有性能问题。

到此针对一个请求寻找到合适的servlet处理的过程大概就如上面所讲。


How Tomcat Works 5

标签:tomcat   web.xml   servlet   

原文地址:http://blog.csdn.net/tangyongzhe/article/details/45115441

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!