Servlet中/和/*的区别详解

目录
  • 本文提纲
  • 版本约定
  • ✍正文
  • 点拨“市面上”的错误答案
  • 1、/用于Servlet,/*用于Filter
  • 2、/不会匹配.jsp请求,而/*可以匹配到.jsp请求
  • 3、/*匹配范围比/大
  • 4、/匹配所有url(路径+后缀),/*只匹配路径型
  • Servlet四种匹配方式
    • 1. 精确匹配
    • 2. 路径匹配
    • 3. 后缀名匹配
    • 4. 缺省匹配
  • URL匹配注意事项
  • 匹配顺序
  • /和/*的区别
  • DispatcherServlet不拦截.jsp请求根因分析
  • ✍总结

本文提纲

版本约定

  • JDK:8
  • Servlet:4.x
  • tomcat:9.x

✍正文

什么样的答案终身难忘?学生时代关于记忆经常能听见两种论调:

  • 死记硬背:见效快,但也忘得快,且一般不会灵活运用(指标不治本)
  • 理解性记忆:见效慢,但记忆持久且会灵活运用(治标又治本)

如果是你,你愿意pick哪种?

正所谓授人以鱼不如授人以渔,后者方能形成永久记忆。不谋而合,本文将采用后种讲述方式,帮你记忆持久化。

关于/和/*的区别这个问题,依稀记得2015年我自学那会就能把它俩搞得明明白白,并且通过理解形成了“永久记忆”,所以至那会其就从来没有犯过迷糊,难道我就这么重视基础么(md,又在吹牛。。。)

点拨“市面上”的错误答案

如果用谷歌百度一下关键字:/和/*的区别,搜索出来的答案不客气的说,基本全错!!! 错误的姿势基本还一模一样,原因你懂的。

各种错误case,且听我娓娓道来。搜集了下有如下4种主流答案,一一点拨。

环境说明:使用原生Servlet,war包方式部署至外置Tomcat作为服务器,端口号8080,context-path为:appcontext

1、/用于Servlet,/*用于Filter

反例:

@WebFilter(urlPatterns = {"/*"})
public class FakeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FakeServlet收到请求:" + req.getRequestURI());
    }
}

启动服务器,浏览器访问:http://localhost:8080/appcontext/api/demo1,控制台输出:

FakeServlet收到请求:/appcontext/api/demo1

一般来讲/确实用于Servlet,/*用于Filter,但并不代表这是正确的。

说明:Filter路径模式使用/无效

2、/不会匹配.jsp请求,而/*可以匹配到.jsp请求

这个结论表面上看没有问题,但是往深了想一步,是否能够推导出这个结论:“/不会匹配.html请求,而/*可以匹配到.html请求”。试试看:

@WebServlet(urlPatterns = {"/"})
public class FakeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FakeServlet收到请求:" + req.getRequestURI());
    }
}

@WebFilter(urlPatterns = {"/*"})
public class FakeFilter extends HttpFilter {

    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        System.out.println("FakeFilter收到请求:" + req.getRequestURI());
        super.doFilter(req, res, chain);
    }
}

启动服务器,浏览器访问:http://localhost:8080/appcontext/api/demo1.jsp,控制台输出:

FakeFilter收到请求:/appcontext/api/demo1.jsp

servlet并未匹配上,似乎符合此结论:/不会匹配.jsp请求,而/*可以。

浏览器再访问:http://localhost:8080/appcontext/api/demo1.html,控制台输出:

FakeFilter收到请求:/appcontext/api/demo1.html

FakeServlet收到请求:/appcontext/api/demo1.html

Filter和Servlet都匹配成功,破功了吧!

所以说,局限于该回答本身没有问题,而问题在于.jsp后缀是一种特殊的请求,拿特殊案例当做通用结论肯定是站不住脚的。

3、/*匹配范围比/大

通过本文下面的讲解你就会知道:/属于最大的的匹配范围,而/*恰好是范围和/一样了而已,但/*的优先级比/高,并不是它的匹配范围比/大。

4、/匹配所有url(路径+后缀),/*只匹配路径型

用一句话反驳:/*也能匹配上/api/demo1.html这种后缀型url(其实上面已经给出示例了)

这4个结论搜索排名非常靠前,不知误导了多少小朋友呀。与其每次将信将疑,倒不如花点时间写代码自己做个试验来得靠谱。我一向推崇的代码多动手,人云亦云不如自己来上一发。

带着这几个❌结论,接下来开始发大招啦:从根本上带你理解Servlet规范的URL匹配机制,从而理解到//*的区别,授之以渔让你终身难忘

Servlet的urlPatterns路径映射

说明:本文所指的Servlet是广义的(规范),所以也包含Filter的urlPatterns

Servlet/Filter是服务端的一段小程序,用于处理Http请求。每个Servlet可以映射1个or多个路径,在xml时代这么写(url-pattern标签可写多个):

<servlet-mapping>
	<servlet-name>Demo1Servlet</servlet-name>
	<url-pattern>/api/demo1</url-pattern>
	<url-pattern>/api/demo2</url-pattern>
</servlet-mapping>

@WebServlet注解方式这么写:

@WebServlet(urlPatterns = {"/api/demo1", "/api/demo2"})
public class Demo1Servlet extends HttpServlet { ... }

此时,该Servlet就能处理这两种 URL了。

问题来了,如果希望本Servlet处理某一类请求,该怎么破呢?

一类请求显然是无法一一枚举出来的,这时就需要用到Servlet的模式匹配了。urlPatterns除了写字面量的字符串,还支持pattern模式的字符串(从该属性的命名你应该也能看出来)。

接下来聚焦于Servlet的匹配方式展开详细讲解,这是本文的核心内容。

Servlet四种匹配方式

在Servlet规范中一共约定了四种匹配方式,无一例外,每种方式都非常重要和常用,下面逐一介绍。

1. 精确匹配

顾名思义,urlPatterns是个无通配符的精确字符串,如:

@WebServlet(urlPatterns = {"/api/demo1", "/api/demo2"}) // 精确匹配
public class UrlPatternDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.printf("收到请求:%s ServletPath:%s PathInfo:%s\n", req.getRequestURI(), req.getServletPath(), req.getPathInfo());
    }
}

打印里输出servletPath和pathInfo信息,让日志更具对比性

浏览器访问http://localhost:8080/appcontext/api/demo1/api/demo2均能收到该请求,控制台分别打印:

收到请求:/appcontext/api/demo1 ServletPath:/api/demo1 PathInfo:null
收到请求:/appcontext/api/demo2 ServletPath:/api/demo2 PathInfo:null

2. 路径匹配

pattern规则:以/开头,且以/*结尾。如:

@WebServlet(urlPatterns = {"/api/*", "/*"}) // 路径匹配
public class UrlPatternDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	// 同上
    }
}

浏览器访问http://localhost:8080/appcontext/api/demo1,控制台输出(匹配的/api/*):

收到请求:/appcontext/api/demo1 ServletPath:/api PathInfo:/demo1

访问http://localhost:8080/appcontext/apiapi/demo1,控制台输出(匹配的/*

收到请求:/appcontext/apiapi/demo1 ServletPath: PathInfo:/apiapi/demo1

关注点:当匹配上/*模式时,ServletPath的值为空串,但PathInfo的值更为“丰富”了。

3. 后缀名匹配

patten规则:以*.开头(注意是开头,所以/api/*.jsp这么写是非法的)。如:

@WebServlet(urlPatterns = {"*.jsp", "*.*"}) // 后缀名匹配
public class UrlPatternDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	// 同上
    }
}

访问http://localhost:8080/appcontext/api/demo1,结果404,因为没有后缀嘛;
访问http://localhost:8080/appcontext/api/demo1.jsp,控制台输出(匹配*.jsp):

收到请求:/appcontext/api/demo1.jsp ServletPath:/api/demo1.jsp PathInfo:null

访问http://localhost:8080/appcontext/api/demo1.servlet,结果404,因为urlPatterns里没有匹配.servlet后缀的模式;
访问http://localhost:8080/appcontext/api/demo1.,结果404,原因同上
访问http://localhost:8080/appcontext/api/demo1.*,控制台打印(匹配*.*):

收到请求:/appcontext/api/demo1.* ServletPath:/api/demo1.* PathInfo:null

发现没,这种匹配方式还蛮“特殊”的,需要注意这两点:

  • 该模式以*.开头,后面的均是常量,即使是*也是常量。比如*.*匹配的后缀必须是.*而不能是其它
  • 该匹配方式下,pathInfo永远是null,servletPath永远是“全部”

4. 缺省匹配

pattern规则:固定值/。如:

想一想,这不就是我们熟悉的DispatcherServlet的匹配路径么?

@WebServlet(urlPatterns = "/") // 缺省匹配
public class UrlPatternDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	// 同上
    }
}

这个时候匹配任意路径

访问http://localhost:8080/appcontext,控制台打印:

收到请求:/appcontext/ ServletPath:/ PathInfo:null

访问http://localhost:8080/appcontext/api/demo1,控制台打印:

收到请求:/appcontext/api/demo1 ServletPath:/api/demo1 PathInfo:null

访问http://localhost:8080/appcontext/api/demo1.html,控制台打印:

收到请求:/appcontext/api/demo1.html ServletPath:/api/demo1.html PathInfo:null

此匹配规则下,pathInfo永远是null,servletPath永远是“全部”。

关于pathInfo:pathInfo只有当Servlet是路径匹配时,才有值。其它情况永远为null

URL匹配注意事项

Servlet对URL的匹配既不是Ant风格,也不是Regex。特殊符号只有单个的*,且使用位置有强约束,切忌想当然的随意拼凑。

举例两种典型的错误理解,应该能帮助到你:

  • /api/*.jsp:该urlPatterns是非法的,启动时会报错“IllegalArgumentException: servlet映射中的[/api/*.jsp]无效”。原因为:

    • 若当路径匹配,/*后面不能再有任何东西
    • 若当后缀名匹配,*.必须是最前面
  • /api/*/demo:这个urlPatterns是合法的。只不过它属于精确匹配,也就是说别看它中间有*,仍旧有且仅能匹配/api/*/demo这个请求路径

匹配顺序

有时候一个URL会被多个urlPatterns所匹配,这时谁优先呢?

Servlet同样遵循“国际惯例”:越精确越优先,越模糊越靠后。站在pattern模式的角度换句话讲就是:范围越小越优先,范围越大越靠后。

因此Servlet四种匹配方式顺序按范围从小到大(优先级从高到底)排序为:精确匹配 > 路径匹配 > 后缀名匹配 > 缺省匹配

/和/*的区别

终于,来到了今天的主菜。

从上至下的阅读到这里,再看这个问题,是不是觉得答案已经浮出水面?那么,最后我还是来总结一下它俩的异同点:

相同点

绝大部分场景下具有相同的表现:匹配所有

不同点

就是由于它们的相同点(如此相似),所以才让我们难以区分。

关于/

  • servlet中特殊的匹配模式(用在Filter中无效),
  • 因为是缺省匹配代表匹配所有路径,所以只可能存在一个实例(若存在多个就覆盖)
  • 优先级最低(兜底),这是和/*的最大区别。它不会覆盖任何其它的url-pattern,只会覆盖Servlet容器(如Tomcat)内建的DefaultServlet

关于/*

  • 属于4中匹配模式中的路径匹配,可用于Servlet和Filter
  • 优先级很高(仅次于精确匹配)。所以它会覆盖所有的后缀名匹配,从而很容易引起404问题,所以这种模式的“伤害性”是非常强的,一般有且仅用在Filter上

DispatcherServlet不拦截.jsp请求根因分析

/只能用于Servlet上,/*一般只用于Filter上。

大家熟悉的Spring MVC的DispatcherServlet的匹配路径默认就是/,它会拦截各种各样的请求,诸如下面这种请求都会拦截:

  • /api/demo1
  • /html/demo1.html
  • /static/main.js

但是,它不会拦截/api/demo1.jsp这种以.jsp结尾的请求。据此现象就出现了:/不拦.jsp请求而/*拦截(/*的范围比/大)这种“错误”言论。

下面告诉你此现象的根因:Servlet容器(如Tomcat)内置有专门匹配.jsp这种请求的Servlet处理器,如下图所示:

后缀名匹配优先级高于缺省匹配,所以.jsp结尾的请求不会被DispatcherServlet所“截胡”而是交给了JspServlet处理。

有了这波分析后,就问你,是不是就不用死记答案了?是不是就终身难忘啦?

✍总结

Servlet的urlPatterns匹配方式是学习Java Web的重要一环,也是深入理解Spring MVC原理的大门,毕竟Spring MVC依旧是做业务开发的首选,而且还会持续很久、很久。

本文对Servlet的匹配方式做了全覆盖讲解,包括:

四种匹配方式匹配顺序(优先级)Servlet和Filter匹配的区别模式匹配中//*区别的根本原因

通过本文希望能让你不再被Servlet的模式匹配所困扰,更不要被一些似可非可的结论所迷惑,摇摆不定时大不了编码验证一下嘛。

本文通过授人以渔的方式道出//*的区别,期待能成为你的永久记忆,我做到了吗?

到此这篇关于Servlet中/和/*的区别详解的文章就介绍到这了,更多相关Servlet中/和/*的区别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JSP中Servlet的Request与Response的用法与区别

    JSP中Servlet的Request与Response的用法与区别 简介:Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象.和代表响应的response对象.request和response对象即然代表请求和响应,那我们要获取客户机提交过来的数据,只需要找request对象就行了.要向客户机输出数据,只需要找response对象就行了. 一,Request Request代表请求对象,其中封装了对请求中具有请求行.请求头.实体内容的操作的方法

  • 详谈Servlet和Filter的区别以及两者在Struts2和Springmvc中的应用

    在javaweb开发中,Servlet和Filter是很重要的两个概念,我们平时进行javaweb开发的时候,会经常和Servlet和Filter打交道,但我们真的了解Servlet和Filter吗? 一.基本概念 Servlet: Servlet 是在WEB服务器上运行的程序.这个词是在 Java applet的环境中创造的,Java applet 是一种当作单独文件跟网页一起发送的小程序,它通常用于在客户端运行,结果得到为用户进行运算或者根据用户互作用定位图形等服务. 服务器上需要一些程序,

  • Servlet和Filter之间的区别与联系

    filter是一个可以复用的代码片段,可以用来转换HTTP请求.响应和头信息.Filter不像Servlet,它不能产生一个请求或者响应,它只是修改对某一资源的请求,或者修改从某一的响应. 最近使用插装的时候,改用cookie对计算机进行识别,加入了过滤,仔细研究了一下servlet和filter,区别主要是: 过滤器的生命周期一般都要经过下面三个阶段: servlet的特点是: 初始化 当容器第一次加载该过滤器时,init() 方法将被调用.该类在这个方法中包含了一个指向 Filter Con

  • 浅谈SpringMVC的拦截器(Interceptor)和Servlet 的过滤器(Filter)的区别与联系 及SpringMVC 的配置文件

    1.过滤器: 依赖于servlet容器.在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次.使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据. 比如:在过滤器中修改字符编码:在过滤器中修改 HttpServletRequest的一些参数,包括:过滤低俗文字.危险字符等 关于过滤器的一些用法可以参考我写过的这些文章: 继承HttpServletRequestWrapper以实现在Filter中修改HttpServletRequest的参

  • jsp和servlet的区别探讨

    答案一: 首先你先要弄懂什么是servlet,servlet是在服务器端执行的java程序,只不过它有专门的一套规则(就是我们平常所说的api):jsp说得简单点就是用另一套简单的规则写的servlet程序,它可以写java代码,还可以写html代码,JavaScript,css等等--,但是到服务器端首先会被转成servlet程序然后就按照servlet的执行顺序执行了. 答案二: 以下的是从网上找的: JSP和SERVLET到底在应用上有什么区别,很多人搞不清楚.我来胡扯几句吧.简单的说,S

  • Servlet中/和/*的区别详解

    目录 本文提纲 版本约定 ✍正文 点拨"市面上"的错误答案 1./用于Servlet,/*用于Filter 2./不会匹配.jsp请求,而/*可以匹配到.jsp请求 3./*匹配范围比/大 4./匹配所有url(路径+后缀),/*只匹配路径型 Servlet四种匹配方式 1. 精确匹配 2. 路径匹配 3. 后缀名匹配 4. 缺省匹配 URL匹配注意事项 匹配顺序 /和/*的区别 DispatcherServlet不拦截.jsp请求根因分析 ✍总结 本文提纲 版本约定 JDK:8 Se

  • JavaWeb Servlet中Filter过滤器的详解

    JavaWeb Servlet中Filter过滤器的详解 1.简述 Filter过滤器,对web服务器所有web资源进行过滤,从而实现一些特殊的功能(权限访问控制.过滤敏感词汇.压缩响应信息).Filter能够对Servlet容器的请求和响应进行检查和修改,其本身不能生成请求request和响应response,只提供过滤作用(Servlet被调用之前检查Request对象修改其相关信息,Servlet被调用后检查Response修改其相关信息),Filter对象常驻服务器. 2.Lifecyc

  • C语言中const和C++中的const 区别详解

    C语言中const和C++中的const 区别详解 C++的const和C语言的#define都可以用来定义常量,二者是有区别的,const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有类型安全检查. 而C语言中的const与C++也有很大的不同,在C语言中用const修饰的变量仍是一个变量,表示这个变量是只读的,不可显示地更改,而在C++中用const修饰过后,就变成常量了.例如下面的代码: const int n=10; int a[n];

  • 从go语言中找&和*区别详解

    *和&的区别 :& 是取地址符号 , 即取得某个变量的地址 , 如 ; &a*是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 . 从代码中验证 : 先构建一个Rect类型 : 1. &是取地址符号, 取到Rect类型对象的地址 2. *可以表示一个变量是指针类型(r是一个指针变量): 3.*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值 4.查看这个指针变量的地址 , 基本数据类型直

  • C语言中 & 和 &&的区别详解

    这是c语言的基本语法,但是在学习的过程中也总是搞混.所以记录一下,也和大家分享一下. &:按照位与操作,例如:0010&1101,结果为0000 &是java中的位逻辑运算:       eg: 2&3=2: 分析如下: 2的二进制为10 :3的二进制为11 : 逻辑&之后为10 &&:短路与,表示如果两个条件都成立则执行之后的逻辑: 例如:if(a==0&&b==0),意思就是if a为0并且b为0的时候,进行下一步操作. || 短

  • javaScript中"=="和"==="的区别详解

    区别: ==, 两边值类型不同的时候,要先进行类型转换,再比较. ==,不做类型转换,类型不同的一定不等. 下面分别说明: 先说 "===",这个比较简单.下面的规则用来判断两个值是否===相等: 1.如果类型不同,就不相等 2.如果两个都是数值,并且是同一个值,那么[相等]:(!例外)的是,如果其中至少一个是NaN,那么[不相等].(判断一个值是否是NaN,只能用isNaN()来判断) 3.如果两个都是字符串,每个位置的字符都一样,那么相等:否则不相等 . 4.如果两个值都是true

  • C字符串与C++中string的区别详解

    在C++中则把字符串封装成了一种数据类型string,可以直接声明变量并进行赋值等字符串操作.以下是C字符串和C++中string的区别:  C字符串 string对象(C++) 所需的头文件名称  <string>或<string.h> <string>或<string.h> 需要头文件 原因 为了使用字符串函数 为了使用string类 声明 方式 char name[20]; string name; 初始化方式 char name[20]="

  • MyBatis中#{}和${}的区别详解

    最近在用mybatis,之前用过ibatis,总体来说差不多,不过还是遇到了不少问题,再次记录下. 先给大家介绍下MyBatis中#{}和${}的区别,具体介绍如下: 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 2. $将传入的数据直接显示生成在sql中.

  • Mybatis中#{}与${}的区别详解

    前言 在开发中使用Mybatis经常使用到#{}与${},依旧有很多开发者对二者的使用不是很清晰,正所谓好记性不如烂笔头,特此总结一下. 在mybatis中动态 sql 是其主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在执行操作之前 mybatis 会对其进行动态解析.mybatis 提供了两种支持动态 sql 的语法:#{} 以及 $ { }, 其最大的区别则是前者方式能够很大程度防止sql注入(安全),后者方式无法防止Sql注入 .什么??不懂什么是Sql注入?额...

  • mysql中#{}和${}的区别详解

    #{}会将传入的数据当成一个字符串,会对自动传入的数据加一个双引号 order by #{userId}   这里假如userId = 111,那么解析成sql时会变成 order by "111"这里如果userId = idStr,那么解析成sql时会变成 order by "idStr" ${}会将传入的数据直接显示生成在sql中 order by #{userId}  这里假如userId = 111,那么解析成sql时会变成 order by 111这里如

随机推荐