本系列转载自 http://blog.csdn.net/haitao111313/article/category/1179996 本文所要解决的问题:一个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也是根据映射器来选择它们的下一级容器的。