Tomcat源碼分析(五)–容器處理連接之servlet的映射 – JAVA編程語言程序開發技術文章

 本文所要解決的問題:一個http請求過來,容器是怎麼知道選擇哪個具體servlet?
     我們知道,一個Context容器表示一個web應用,一個Wrapper容器表示一個servlet,所以上面的問題可以轉換為怎麼由Context容器選擇servlet,答案是映射器。映射器是實現瞭Mapper接口的類,作用就是根據請求連接(主要是協議和路徑)來選擇下一個容器,可以看做是一個哈希表,根據關鍵字段來選擇具體的值,Mapper接口的定義為:
[java] 
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方法,在StandardContext容器有一句這樣的代碼:
[java] 
wrapper = (Wrapper) context.map(request, true); 
這句代碼表示容器會調用map方法來映射請求到具體的wrapper上,意思就是說,根據連接請求request來選擇wrapper。上面的map會調用父類ContainerBase的map方法來找到具體的映射器,至於這個映射器和容器是怎麼關聯上的,具體請參考 Tomcat源碼分析(三)–連接器是如何與容器關聯的?這篇文章,大致原理是一樣的。StandardContext容器有一個標準的映射器實現類StandardContextMapper,所以最終會調用到映射器StandardContextMapper的map方法,這個方法是選擇servlet的關鍵(省略瞭一些代碼):
[java] 
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:
[java] 
public String findServletMapping(String pattern) { 
    synchronized (servletMappings) { 
        return ((String) servletMappings.get(pattern)); 
    } 

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

作者:haitao111313

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。