1. 程式人生 > >Tomcat原始碼分析(五)--容器處理連線之servlet的對映

Tomcat原始碼分析(五)--容器處理連線之servlet的對映

    本文所要解決的問題:一個http請求過來,容器是怎麼知道選擇哪個具體servlet?

     我們知道,一個Context容器表示一個web應用,一個Wrapper容器表示一個servlet,所以上面的問題可以轉換為怎麼由Context容器選擇servlet,答案是對映器。對映器是實現了Mapper介面的類,作用就是根據請求連線(主要是協議和路徑)來選擇下一個容器,可以看做是一個雜湊表,根據關鍵欄位來選擇具體的值,Mapper介面的定義為:

public interface Mapper {
    public Container getContainer();//返回與該對映器相關聯的容器
    public void setContainer(Container container);
    public String getProtocol();//返回與該對映器處理的協議
    public void setProtocol(String protocol);
    public Container map(Request request, boolean update); //對映函式,實現該函式
}

Tomcat原始碼分析(四)--容器處理連結之責任鏈模式中已經知道,請求連線到達StandardContext容器的invoke方法,最終會到達StandardContextValue閥的invoke方法裡面,在這個invoke方法中有一句這樣的程式碼

 wrapper = (Wrapper) context.map(request, true);
這句程式碼表示容器會呼叫map方法來對映請求到具體的wrapper上,意思就是說,根據連線請求request來選擇wrapper。上面的map會呼叫父類ContainerBase的map方法來找到具體的對映器,至於這個對映器和容器是怎麼關聯上的,具體請參考
Tomcat原始碼分析(三)--聯結器是如何與容器關聯的?
這篇文章,大致原理是一樣的。StandardContext容器有一個標準的對映器實現類StandardContextMapper,所以最終會呼叫到對映器StandardContextMapper的map方法,這個方法是選擇servlet的關鍵(省略了一些程式碼):
 public Container map(Request request, boolean update) {
  // Identify the context-relative URI to be mapped
        String contextPath =
            ((HttpServletRequest) request.getRequest()).getContextPath();
        String requestURI = ((HttpRequest) request).getDecodedRequestURI();
        String relativeURI = requestURI.substring(contextPath.length());
        // Apply the standard request URI mapping rules from the specification
        Wrapper wrapper = null;
        String servletPath = relativeURI;
        String pathInfo = null;
        String name = null;

        // Rule 1 -- Exact Match
        if (wrapper == null) {
            if (debug >= 2)
                context.log("  Trying exact match");
            if (!(relativeURI.equals("/")))
                name = context.findServletMapping(relativeURI);
            if (name != null)
                wrapper = (Wrapper) context.findChild(name);
            if (wrapper != null) {
                servletPath = relativeURI;
                pathInfo = null;
            }
        }

        // Rule 2 -- Prefix Match
        if (wrapper == null) {
            if (debug >= 2)
                context.log("  Trying prefix match");
            servletPath = relativeURI;
            while (true) {
                name = context.findServletMapping(servletPath + "/*");
                if (name != null)
                    wrapper = (Wrapper) context.findChild(name);
                if (wrapper != null) {
                    pathInfo = relativeURI.substring(servletPath.length());
                    if (pathInfo.length() == 0)
                        pathInfo = null;
                    break;
                }
                int slash = servletPath.lastIndexOf('/');
                if (slash < 0)
                    break;
                servletPath = servletPath.substring(0, slash);
            }
        }

        // Rule 3 -- Extension Match
        if (wrapper == null) {
            if (debug >= 2)
                context.log("  Trying extension match");
            int slash = relativeURI.lastIndexOf('/');
            if (slash >= 0) {
                String last = relativeURI.substring(slash);
                int period = last.lastIndexOf('.');
                if (period >= 0) {
                    String pattern = "*" + last.substring(period);
                    name = context.findServletMapping(pattern);
                    if (name != null)
                        wrapper = (Wrapper) context.findChild(name);
                    if (wrapper != null) {
                        servletPath = relativeURI;
                        pathInfo = null;
                    }
                }
            }
        }

        // Rule 4 -- Default Match
        if (wrapper == null) {
            if (debug >= 2)
                context.log("  Trying default match");
            name = context.findServletMapping("/");
            if (name != null)
                wrapper = (Wrapper) context.findChild(name);
            if (wrapper != null) {
                servletPath = relativeURI;
                pathInfo = null;
            }
        }
}

程式碼很長,但是很容易看懂,就是分4中匹配模式(完全匹配,字首匹配,擴充套件匹配,預設匹配)來選擇wrapper,關鍵程式碼就是name = context.findServletMapping和wrapper = (Wrapper) context.findChild(name);這裡面context都是StandardContext。context.findServletMapping是根據匹配模式來找到servlet名字,context.findChild是根據servlet名字找到具體的wrapper。findServletMapping方法很簡單,就是在一個HashMap裡面得到servlet名字,程式碼如下,其中servletMappings是一個HashMap:
    public String findServletMapping(String pattern) {
        synchronized (servletMappings) {
            return ((String) servletMappings.get(pattern));
        }
    }

findChild方法跟findServletMapping方法一樣,也是在一個HashMap找wrapper容器。至此,已經能夠回答一開始的問題了,StandardContext容器根據對映器來選擇wrapper。當然,容器Engine和Host也是根據對映器來選擇它們的下一級容器的。