当前位置: 首页 > 工具软件 > Pattern Web > 使用案例 >

web.xml中的url-pattern 写法小结(附源码分析)

萧建木
2023-12-01

前言

Servlet和filter是J2EE开发中常用的技术,使用方便,配置简单.但url-pattern可能有点迷糊.这里总结其中url-pattern中的映射规则.

servlet容器对url的匹配过程

当一个请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url,比如我访问的是 http://localhost/test/aaa.html,我的应用上下文是test,容器会将http://localhost/test去掉, 剩下的/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的(按照servlet-mapping在web.xml中声明的先后顺序),而且当有一个servlet匹配成功以后,就不会去理会剩下 的servlet了(filter不同,将符合条件都进行过滤)。

源码分析

有兴趣的其实可以研究一下源码, 就不需要来背这里总结写好的结论了, 建议大家多看看源码.这里参考的源码是`apache-tomcat-9.0.0.M6-src`.

想要了解url-pattern的大致配置必须了解org.apache.tomcat.util.http.mapper.Mapper这个类.

这个类上的源码注释:Mapper, which implements the servlet API mapping rules (which are derived from the HTTP rules). 意思也就是说 “Mapper是一个衍生自HTTP规则并实现了servlet API映射规则的类”。

tomcat在启动的时候会扫描web.xml文件, org.apache.tomcat.util.descriptor.web.WebXml这个类是扫描web.xml文件的, 然后得到的servlet的映射数据servletMappings(HashMap), map的key: url-pattern value: servlet-name
然后会调用Context(实现类为StandardContext)的addServletMapping方法。 这个方法会调用本文上面提到的Mapper的addWrapper方法.
这个方法进行if-else判断
1. 以 /* 结尾的。 path.endsWith(“/*”)
2. 以 *. 开头的。 path.startsWith(“*.”)
3. 是否是 /, path.equals(“/”)
4. 以上3种之外的。

各种对应的处理完成之后,会存入context的各种wrapper中。这里的context是ContextVersion,这是一个定义在Mapper内部的静态类。
它有4种wrapper。 defaultWrapper,exactWrapper, wildcardWrappers,extensionWrappers。

这里的Wrapper概念:

Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。

回过头来看mapper的addWrapper方法:
1. 我们看到 /* 对应的Servlet会被丢到wildcardWrappers中
2. *. 会被丢到extensionWrappers中
3. / 会被丢到defaultWrapper中
4. 其他的映射都被丢到exactWrappers中

用户请求过来的时候会调用mapper的internalMapWrapper方法, 方法体中代码的作者已经下好了注释, 用户请求这里进行URL匹配的时候是有优先级的.

// Rule 1 -- Exact Match
        Wrapper[] exactWrappers = contextVersion.exactWrappers;
        internalMapExactWrapper(exactWrappers, path, mappingData);

        // Rule 2 -- Prefix Match
        boolean checkJspWelcomeFiles = false;
        Wrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
        if (mappingData.wrapper == null) {
            internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
                                       path, mappingData);
            .....
        }

        ....// Rule 3 -- Extension Match
        Wrapper[] extensionWrappers = contextVersion.extensionWrappers;
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                    true);
        }

        // Rule 4 -- Welcome resources processing for servlets
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    ...// Rule 4a -- Welcome resources processing for exact macth
                    internalMapExactWrapper(exactWrappers, path, mappingData);

                    // Rule 4b -- Welcome resources processing for prefix match
                    if (mappingData.wrapper == null) {
                        internalMapWildcardWrapper
                            (wildcardWrappers, contextVersion.nesting,
                             path, mappingData);
                    }

                    // Rule 4c -- Welcome resources processing
                    //            for physical folder
                    if (mappingData.wrapper == null
                        && contextVersion.resources != null) {
                        Object file = null;
                        String pathStr = path.toString();
                        try {
                            file = contextVersion.resources.lookup(pathStr);
                        } catch(NamingException nex) {
                            // Swallow not found, since this is normal
                        }
                        if (file != null && !(file instanceof DirContext) ) {
                            internalMapExtensionWrapper(extensionWrappers, path,
                                                        mappingData, true);
                            if (mappingData.wrapper == null
                                && contextVersion.defaultWrapper != null) {
                                mappingData.wrapper =
                                    contextVersion.defaultWrapper.object;
                                mappingData.requestPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.wrapperPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.requestPath.setString(pathStr);
                                mappingData.wrapperPath.setString(pathStr);
                            }
                        }
                    }
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }

        }

        /* welcome file processing - take 2
         * Now that we have looked for welcome files with a physical
         * backing, now look for an extension mapping listed
         * but may not have a physical backing to it. This is for
         * the case of index.jsf, index.do, etc.
         * A watered down version of rule 4
         */
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    path.setOffset(pathOffset);
                    path.setEnd(pathEnd);
                    path.append(contextVersion.welcomeResources[i], 0,
                                contextVersion.welcomeResources[i].length());
                    path.setOffset(servletPath);
                    internalMapExtensionWrapper(extensionWrappers, path,
                                                mappingData, false);
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }
        }


        // Rule 7 -- Default servlet
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            if (contextVersion.defaultWrapper != null) {
                mappingData.wrapper = contextVersion.defaultWrapper.object;
                mappingData.requestPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.wrapperPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
            }
            ...
        }

规则1:精确匹配,使用contextVersion的exactWrappers
规则2:前缀匹配,使用contextVersion的wildcardWrappers
规则3:扩展名匹配,使用contextVersion的extensionWrappers
规则4:使用资源文件来处理servlet,使用contextVersion的welcomeResources属性,这个属性是个字符串数组(如默认首页index.jsp)
规则7:使用默认的servlet,使用contextVersion的defaultWrapper

匹配规则和顺序如下:

精确路径匹配
比如servletA 的url-pattern为 /test,servletB的url-pattern为 /* ,这个时候,如果我访问的url为 http://localhost/test ,这个时候容器就会先进行精确路径匹配,发现/test正好被servletA精确匹配,那么就去调用servletA,也不会去理会其他的 servlet了
最长路径匹配
例子:servletA的url-pattern为 /test/*,而servletB的url-pattern为 /test/a/*,此时访问 http://localhost/test/a时,容器会选择路径最长的servlet来匹配,也就是这里的servletB
扩展匹配
如果url最后一段包含扩展(如 *.do),容器将会根据扩展选择合适的servlet。

注意:如果前面三条规则都没有找到一个servlet,容器会根据url选择对应的请求资源。如果应用定义了一个default servlet,则容器会将请求丢给default servlet(什么是default servlet?后面会讲)。

url-pattern详解

在web.xml文件中,以下语法用于定义映射:

  1. 以”/’开头和以”/*”结尾的是用来做路径映射的。
  2. 以前缀”*.”开头的是用来做扩展映射的。
  3. “/” 是用来定义default servlet映射的。
  4. 剩下的都是用来定义详细映射的。比如: /aa/bb/cc.action

注意事项
1. 容器会首先查找精确路径匹配,如果找不到,再查找目录匹配,如果也找不到,就查找扩展名匹配
2. 如果一个请求匹配多个“目录匹配”,容器会选择最长的匹配(也就是更为精确的路径)
3. 定义”/*.action”这样一个看起来很正常的匹配会报错?因为这个匹配即属于路径映射,也属于扩展映射,导致容器无法判断

自定义路径映射

我想定义一个除了一种情况的所有url-pattern,比如除了 *.jsp的所有情况
似乎找不到一种 all but ×××的写法
但似乎可以用下面这种方法:

<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>com.test.LoginFilter</filter-class>
    <init-param>
        <param-name>UrlRegx</param-name>
        <param-value><!--你的正则表达式--></param-value>
    </init-param>
</filter>

自己定义一个规则,在后台进行二次过滤

什么是default servlet ?

  1. web.xml中如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
  2. 凡是在web.xml文件中找不到匹配的元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求
  3. 在tomcat的安装目录\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。(\conf\web.xml文件所有发布到tomcat的web应用所共享的)
     <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
           <param-name>debug</param-name>
           <param-value>0</param-value>
        </init-param>
        <init-param>
           <param-name>listings</param-name>
           <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
       <servlet-name>default</servlet-name>
       <url-pattern>/</url-pattern>
    </servlet-mapping>

4 . 当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet,由DefaultServlet类寻找,当寻找到了请求的html或图片时,则返回其资源文件,如果没有寻找到则报出404错误。

The default servlet is the servlet which serves static resources as
well as serves the directory listings (if directory listings are
enabled).
见官网http://tomcat.apache.org/tomcat-6.0-doc/default-servlet.html

5 . 如果在web应用的web.xml文件中的中配置了”/”,如:

    <servlet>
      <servlet-name>ServletDemo3</servlet-name>
      <servlet-class>edu.servlet.ServletDemo3</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>ServletDemo3</servlet-name>
      <url-pattern>/</url-pattern>
    </servlet-mapping>

则当请求的url和上面其他的均不匹配时,则会交给ServletDemo3.Java处理,而不在交给DefaultServlet.java处理,也就是说,当请求web应用中的静态资源等时,则全部进入了ServletDemo3.java,而不会正常返回页面资源。

参考文章: http://www.cnblogs.com/fangjian0423/p/servletContainer-tomcat-urlPattern.html

 类似资料: