/和/*的区别记不住?我的答案保你终身难忘

2024-01-23 10:10

本文主要是介绍/和/*的区别记不住?我的答案保你终身难忘,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

授人以鱼不如授人以渔
本文已被https://yourbatman.cn收录;程序员专用网盘https://wangpan.yourbatman.cn;公号后台回复“专栏列表”获取全部小而美的原创技术专栏

✍前言

你好,我是方同学(YourBatman)

A哥 -> 方同学。是的,中文昵称改了。自知道行不深无以用“哥”字称呼,虽已毕业多年,同学二字寄寓心态一直积极、热情、年轻

这次的标题🐂吹得有点大,倍感压力。不过没关系,毕竟吹牛不用上睡,也不犯法。在信息大爆炸的时代,连技术圈的标题党也不少啦:

  • 30分钟教你手撸一个ORM框架。其实就一个反射注解拼接字符串
  • 5分钟教你玩转Docker。额,5分钟后包就业吗?
  • 玩转亿级流量高并发缓存方案。全国(乃至全球)能达如此流量级别的屈指可数,你确定?

我标榜自己从不标题党,是的这次也不例外。本文将分析//*的区别这个老生常谈的问题,看别的博文总是看了忘忘了看,本文不同的是,关于此问题这一篇文章就够了,它将成为你的永久记忆(一不小心又吹牛了🥶)
在这里插入图片描述

所属专栏

  • 点拨-Servlet

本文提纲

在这里插入图片描述

版本约定

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

✍正文

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

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

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

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

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

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

如果用谷歌百度一下关键字:/和/*的区别,搜索出来的答案不客气的说,基本全错!!! 错误的姿势基本还一模一样,原因你懂的。
在这里插入图片描述
各种错误case,且听我娓娓道来。搜集了下有如下4种主流答案,一一点拨。

环境说明:使用原生Servlet,war包方式部署至外置Tomcat作为服务器,端口号8080,context-path为:appcontext
在这里插入图片描述

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

反例:

@WebFilter(urlPatterns = {"/*"})
public class FakeServlet extends HttpServlet {@Overrideprotected 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("FakeServlet收到请求:" + req.getRequestURI());}
}@WebFilter(urlPatterns = {"/*"})
public class FakeFilter extends HttpFilter {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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

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

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

4. 缺省匹配

pattern规则:固定值/。如:

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

@WebServlet(urlPatterns = "/") // 缺省匹配
public class UrlPatternDemoServlet extends HttpServlet {@Overrideprotected 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的模式匹配所困扰,更不要被一些似可非可的结论所迷惑,摇摆不定时大不了编码验证一下嘛。

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

相关源代码已上传,公号后台回复数字100获取

推荐阅读

  • Cors跨域(四):解决方案对决JSONP vs CORS
  • YourBatman 2020年感悟关键词:科比、裁员、管理层、活着
  • 2个周末,历时100+小时,YourBatman新版Blog正式上线
  • Spring5新宠:PathPattern,AntPathMatcher:那我走?
  • 最好的IDEA debug长文?看完我佛了
    在这里插入图片描述
System.out.println("写得可还行?收藏再看吧,点个赞吧,分享下吧");
echo("关注【BAT的乌托邦】 https://yourbatman.cn");
console.log("私聊YourBatman:fsx1056342982");

在这里插入图片描述

System.out.println("写得可还行?收藏再看吧,点个赞吧,分享下吧");
echo("关注【BAT的乌托邦】 https://yourbatman.cn");
console.log("私聊YourBatman:fsx1056342982");

我是YourBatman:一个早在2013年就已毕业的大龄程序员。网瘾失足、清考、延期毕业、房产中介、送外卖、销售…是我不可抹灭的标签。现在是一个纯粹技术工。

  • 2013.08-2014.07宁夏银川中介公司卖二手房1年,毕业后第1份工作
  • 2014.07-2015.05荆州/武汉/北京,从事炸鸡排、卖保险、直销、送外卖工作,这是第2,3,4,5份工作
  • 2015.08开始从事Java开发,做过兼职,闯过外包,呆过大厂!现为我司基础架构团队负责人
  • Java架构师、Spring开源贡献者,CSDN 2019博客之星Top 10,博客专家,领域建模专家。热衷写代码,有代码洁癖;重视基础和基建,相信效率为王
  • 现在写纯粹技术专栏(公号后台回复专栏列表获取全部),不哗众取宠。如果你也有共鸣,可加我好友(fsx1056342982)一起进步哈(备注:java)

这篇关于/和/*的区别记不住?我的答案保你终身难忘的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/636065

相关文章

分辨率三兄弟LPI、DPI 和 PPI有什么区别? 搞清分辨率的那些事儿

《分辨率三兄弟LPI、DPI和PPI有什么区别?搞清分辨率的那些事儿》分辨率这个东西,真的是让人又爱又恨,为了搞清楚它,我可是翻阅了不少资料,最后发现“小7的背包”的解释最让我茅塞顿开,于是,我... 在谈到分辨率时,我们经常会遇到三个相似的缩写:PPI、DPI 和 LPI。虽然它们看起来差不多,但实际应用

GORM中Model和Table的区别及使用

《GORM中Model和Table的区别及使用》Model和Table是两种与数据库表交互的核心方法,但它们的用途和行为存在著差异,本文主要介绍了GORM中Model和Table的区别及使用,具有一... 目录1. Model 的作用与特点1.1 核心用途1.2 行为特点1.3 示例China编程代码2. Tab

Nginx指令add_header和proxy_set_header的区别及说明

《Nginx指令add_header和proxy_set_header的区别及说明》:本文主要介绍Nginx指令add_header和proxy_set_header的区别及说明,具有很好的参考价... 目录Nginx指令add_header和proxy_set_header区别如何理解反向代理?proxy

Java中&和&&以及|和||的区别、应用场景和代码示例

《Java中&和&&以及|和||的区别、应用场景和代码示例》:本文主要介绍Java中的逻辑运算符&、&&、|和||的区别,包括它们在布尔和整数类型上的应用,文中通过代码介绍的非常详细,需要的朋友可... 目录前言1. & 和 &&代码示例2. | 和 ||代码示例3. 为什么要使用 & 和 | 而不是总是使

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

Spring中@RestController和@Controller的使用及区别

《Spring中@RestController和@Controller的使用及区别》:本文主要介绍Spring中@RestController和@Controller的使用及区别,具有很好的参考价... 目录Spring中@RestController和@Controller使用及区别1. 基本定义2. 使

Qt 中 isHidden 和 isVisible 的区别与使用小结

《Qt中isHidden和isVisible的区别与使用小结》Qt中的isHidden()和isVisible()方法都用于查询组件显示或隐藏状态,然而,它们有很大的区别,了解它们对于正确操... 目录1. 基础概念2. 区别清见3. 实际案例4. 注意事项5. 总结1. 基础概念Qt 中的 isHidd

Spring、Spring Boot、Spring Cloud 的区别与联系分析

《Spring、SpringBoot、SpringCloud的区别与联系分析》Spring、SpringBoot和SpringCloud是Java开发中常用的框架,分别针对企业级应用开发、快速开... 目录1. Spring 框架2. Spring Boot3. Spring Cloud总结1. Sprin

Java中的runnable 和 callable 区别解析

《Java中的runnable和callable区别解析》Runnable接口用于定义不需要返回结果的任务,而Callable接口可以返回结果并抛出异常,通常与Future结合使用,Runnab... 目录1. Runnable接口1.1 Runnable的定义1.2 Runnable的特点1.3 使用Ru

Java中Runnable和Callable的区别和联系及使用场景

《Java中Runnable和Callable的区别和联系及使用场景》Java多线程有两个重要的接口,Runnable和Callable,分别提供一个run方法和call方法,二者是有较大差异的,本文... 目录一、Runnable使用场景二、Callable的使用场景三、关于Future和FutureTa