新聞中心
前言
你好,我是YourBatman。

- @RequestMapping的URL是支持Ant風(fēng)格的
- @ComponentScan的掃描包路徑是支持Ant風(fēng)格的
- @PropertySource導(dǎo)入資源是支持Ant分隔的(如:classpath:app-*.properties)
- ...
在描述路徑時(shí)有個(gè)常見(jiàn)叫法:Ant風(fēng)格的URL。那么到底什么是Ant風(fēng)格?關(guān)于這個(gè)概念,我特地的谷歌一下、百度一下、bing一下,無(wú)一所獲(沒(méi)有一個(gè)確切的定義),難道這個(gè)盛行的概念真的只能意會(huì)嗎?
直到我在Spring中AntPathMatcher的描述中看到一句話:這是從Apache Ant借用的一個(gè)概念?!澳贻p”的朋友可能從沒(méi)用過(guò)甚至沒(méi)聽(tīng)過(guò)Ant,它是一個(gè)構(gòu)建工具,在2010年之前發(fā)揮著大作用,但之后逐漸被Maven/Gradle取代,現(xiàn)已幾乎銷(xiāo)聲匿跡。雖然Ant“已死”,但Ant風(fēng)格似乎要千古。借助Spring強(qiáng)大的號(hào)召力,該概念似乎已是規(guī)范一樣的存在,大家在不成文的約定著、交流著、書(shū)寫(xiě)著。
那么,既然Ant風(fēng)格貫穿于開(kāi)發(fā)的方方面面,懷著一知半解的態(tài)度使用著實(shí)為不好。今天咱們就深入聊聊,進(jìn)行全方位的講解。
所屬專(zhuān)欄
點(diǎn)撥-Spring技術(shù)棧
本文提綱
版本約定
- JDK:8
- Spring Framework:5.3.x
正文
在Spring 5之前,Spring技術(shù)棧體系內(nèi)幾乎所有的Ant風(fēng)格均由AntPathMatcher提供支持。
PathMatcher路徑匹配器
PathMatcher是抽象接口,該接口抽象出了路徑匹配器的概念,用于對(duì)path路徑進(jìn)行匹配。它提供如下方法:
細(xì)節(jié):PathMatcher所在的包為org.springframework.util.PathMatcher,屬于spring-core核心模塊,表示它可運(yùn)用在任意模塊,not only for web。
- // Since: 1.2
- public interface PathMatcher {
- boolean isPattern(String path);
- boolean match(String pattern, String path);
- boolean matchStart(String pattern, String path);
- String extractPathWithinPattern(String pattern, String path);
- Map
extractUriTemplateVariables(String pattern, String path); - Comparator
getPatternComparator(String path); - String combine(String pattern1, String pattern2);
- }
一個(gè)路徑匹配器提供這些方法在情理之中,這些方法見(jiàn)名知意理解起來(lái)也不難,下面稍作解釋?zhuān)?/p>
- boolean isPattern(String path):判斷path是否是一個(gè)模式字符串(一般含有指定風(fēng)格的特殊通配符就算是模式了)
- boolean match(String pattern, String path):最重要的方法。判斷path和模式pattern是否匹配(注意:二者都是字符串,傳值不要傳反了哈)
- boolean matchStart(String pattern, String path):判斷path是否和模式pattern前綴匹配(前綴匹配:path的前綴匹配上patter了即可,當(dāng)然全部匹配也是可以的)
- String extractPathWithinPattern(String pattern, String path):返回和pattern模式真正匹配上的那部分字符串。舉例:/api/yourbatman/*.html為pattern,/api/yourbatman/form.html為path,那么該方法返回結(jié)果為form.html(注意:返回結(jié)果永遠(yuǎn)不為null,可能是空串)
- Map
- String combine(String pattern1, String pattern2):合并兩個(gè)pattern模式,組合算法由具體實(shí)現(xiàn)自由決定
該接口規(guī)定了作為路徑匹配器一些必要的方法,同時(shí)也開(kāi)放了一些行為策略如getPatternComparator、combine等由實(shí)現(xiàn)類(lèi)自行決定?;蛟S你還覺(jué)得這里某些方法有點(diǎn)抽象,那么下面就開(kāi)始實(shí)戰(zhàn)。
正則表達(dá)式 vs Ant風(fēng)格
正則表達(dá)式(regular expression):描述了一種字符串匹配的模式(pattern),可以用來(lái)檢查一個(gè)串是否含有某種子串、將匹配的子串替換或者從某個(gè)串中取出符合某個(gè)條件的子串等。
正則表達(dá)式是由普通字符(例如字符a到z)以及特殊字符(又稱(chēng)"元字符")組成的文字模式。重點(diǎn)是它的元字符,舉例說(shuō)明:
- ?:匹配前面的子表達(dá)式零次或一次
- *:匹配前面的子表達(dá)式零次或多次
- +:匹配前面的子表達(dá)式一次或多次
- .:匹配除換行符 \n 之外的任何單字符
- ...
正則表達(dá)式幾乎所有編程語(yǔ)言都支持的通用模式,具有普適性(適用于任意字符串的匹配)、功能非常強(qiáng)大等特點(diǎn)。除此之外正則表達(dá)式還有“重”、“難”等特點(diǎn),具有一定上手門(mén)檻、高并發(fā)情況下執(zhí)行效率低也都是它擺脫不了的“特性”。
Ant風(fēng)格(Ant Style):該風(fēng)格源自Apache的Ant項(xiàng)目,若你是個(gè)“老”程序員或許你還用過(guò)Apache Ant,若你是個(gè)小鮮肉也許聞所未聞,畢竟現(xiàn)在是Maven(Gradle)的天下。
Ant風(fēng)格簡(jiǎn)單的講,它是一種精簡(jiǎn)的匹配模式,僅用于匹配路徑or目錄。使用大家熟悉的(這點(diǎn)很關(guān)鍵)的通配符:
看到?jīng)],這才比較符合咱們的習(xí)慣:*代表任意通配符才是正解嘛,而不是像正則一樣代表匹配的數(shù)量來(lái)得讓人“費(fèi)解”。
**直接用于目錄級(jí)別的匹配,可謂對(duì)URL這種字符串非常友好
最佳實(shí)踐場(chǎng)景
正則表達(dá)式具有功能非常強(qiáng)大的特性,從理論上來(lái)講,它可以用于任何場(chǎng)景,但是有些場(chǎng)景它并非最佳實(shí)踐。
舉個(gè)例子:在自定義的登錄過(guò)濾器中,經(jīng)常會(huì)放行一些API接口讓免登錄即可訪問(wèn),這是典型的URL白名單場(chǎng)景,這個(gè)時(shí)候就會(huì)涉及到URL的匹配方式問(wèn)題,一般會(huì)有如下方案:
- 精確匹配:url.equals("/api/v1/yourbatman/adress")。缺點(diǎn):硬編碼式一個(gè)個(gè)羅列,易造成錯(cuò)誤且不好維護(hù)
- 前綴匹配:url.startsWith("/api/v1/yourbatman")。這也算一種匹配模式,可以批量處理某一類(lèi)URL。缺點(diǎn)是:匹配范圍過(guò)大易造成誤傷,或者范圍過(guò)小無(wú)法形成有效匹配,總之就是欠缺靈活度
- 包含匹配:url.contains("/yourbatman")。這個(gè)缺點(diǎn)比較明顯:強(qiáng)依賴(lài)于URL的書(shū)寫(xiě)規(guī)范(如白名單的URL都必須包含指定子串),并且極易造成誤傷
- 正則表達(dá)式匹配:Pattern.compile("正則表達(dá)式")..matcher(url).find()。它的最大優(yōu)點(diǎn)是可以滿(mǎn)足幾乎任意的URL(包括精確、模式等),但最大的缺點(diǎn)是書(shū)寫(xiě)比較復(fù)雜,用時(shí)多少這和coder的水平強(qiáng)相關(guān),另外這對(duì)后期維護(hù)也帶來(lái)了一定挑戰(zhàn)~
經(jīng)常會(huì)聽(tīng)到這樣一句話:“通過(guò)正則表達(dá)式或者Ant風(fēng)格的路徑表達(dá)式來(lái)做URL匹配”。正所謂“殺雞何必用牛刀”,URL相較于普通的字符串具有很強(qiáng)的規(guī)律性:標(biāo)準(zhǔn)的分段式。因此,使用輕量級(jí)Ant風(fēng)格表達(dá)式作為URL的匹配模式更為合適:
1.輕量級(jí)執(zhí)行效率高
2.通配符(模式)符合正常理解,使用門(mén)檻非常低
*和**對(duì)層級(jí)路徑/目錄的支持感覺(jué)就是為此而生的
3.對(duì)于復(fù)雜場(chǎng)景亦可包含正常表達(dá)式來(lái)達(dá)到通用性
總的來(lái)講,所謂為誰(shuí)更好。Ant風(fēng)格和正則表達(dá)式都有它們場(chǎng)景的最佳實(shí)踐:
- Ant風(fēng)格:用于URL/目錄這種標(biāo)準(zhǔn)分段式路徑匹配
- 正則表達(dá)式:用于幾乎沒(méi)規(guī)律(或者規(guī)律性不強(qiáng))的普通字符串匹配
AntPathMatcher:基于Ant風(fēng)格的路徑匹配器
PathMatcher接口并未規(guī)定路徑匹配的具體方式,在Spring的整個(gè)技術(shù)棧里(包括Spring Boot和Cloud)有且僅有一個(gè)實(shí)現(xiàn)類(lèi)AntPathMatcher:基于Ant風(fēng)格的路徑匹配器。它運(yùn)用在Spring技術(shù)棧的方方面面,如:URL路徑匹配、資源目錄匹配等等。
這里有個(gè)有趣的現(xiàn)象:AntPathMatcher是Since:16.07.2003,而其接口PathMatcher是Since:1.2(2005.12)整整晚了2年+才出現(xiàn)。Spring當(dāng)初的設(shè)想是把路徑匹配抽象成為一種模式(也就是PathMatcher)而不限定具體實(shí)現(xiàn),但奈何近20年過(guò)去了AntPathMatcher仍舊為PathMatcher接口的唯一實(shí)現(xiàn)。
說(shuō)明:Spring 5新增了更高效的、設(shè)計(jì)更好的、全新的路徑匹配器PathPattern,但它并未實(shí)現(xiàn)PathMatcher接口而是一套全新“生態(tài)”,用于逐步替換掉AntPathMatcher。關(guān)于此,下篇文章有詳盡分析說(shuō)一千,道一萬(wàn)。了解PathMatcher/AntPathMatcher最為重要的是什么?當(dāng)然是了解它的匹配規(guī)則,做到心里有數(shù)。安排,下面我就通過(guò)代碼示例方式演示其匹配,盡量做到全乎,讓你一文在手全部都有。
代碼演示Path匹配規(guī)則
要什么記憶,有這些代碼示例收藏起來(lái)就足夠了。
在具體代碼示例之前,公示所有示例的公有代碼如下:
- private static final PathMatcher MATCHER = new AntPathMatcher();
使用缺省的 AntPathMatcher實(shí)例,并且API的使用全部面向PathMatcher接口而非具體實(shí)現(xiàn)類(lèi)。
- private static void match(int index, PathMatcher matcher, String pattern, String reqPath) {
- boolean match = matcher.match(pattern, reqPath);
- System.out.println(index + "\tmatch結(jié)果:" + pattern + "\t" + (match ? "【成功】" : "【失敗】") + "\t" + reqPath);
- }
- private static void extractUriTemplateVariables(PathMatcher matcher, String pattern, String reqPath) {
- Map
variablesMap = matcher.extractUriTemplateVariables(pattern, reqPath); - System.out.println("extractUriTemplateVariables結(jié)果:" + variablesMap + "\t" + pattern + "\t" + reqPath);
- }
對(duì)PathMatcher最常用的match方法、extractUriTemplateVariables方法做簡(jiǎn)易封裝,主要為了輸出日志,方便控制臺(tái)里對(duì)應(yīng)著查看。
?:匹配任意單字符
因?yàn)槭瞧ヅ鋯巫址?,所以一般“夾雜”在某個(gè)path片段內(nèi)容中間
- @Test
- public void test1() {
- System.out.println("=======測(cè)試?:匹配任意單個(gè)字符=======");
- String pattern = "/api/your?atman";
- match(MATCHER, pattern, "/api/youratman");
- match(MATCHER, pattern, "/api/yourBatman");
- match(MATCHER, pattern, "/api/yourBatman/address");
- match(MATCHER, pattern, "/api/yourBBBatman");
- }
- =======匹配任意單字符=======
- 1 match結(jié)果:/api/your?atman 【失敗】 /api/youratman
- 2 match結(jié)果:/api/your?atman 【成功】 /api/yourBatman
- 3 match結(jié)果:/api/your?atman 【失敗】 /api/yourBatman/address
- 4 match結(jié)果:/api/your?atman 【失敗】 /api/yourBBBatman
關(guān)注點(diǎn):
- ?表示匹配精確的1個(gè)字符,所以0個(gè)不行(如結(jié)果1)
- 即使?匹配成功,但“多余”部分和pattern并不匹配最終結(jié)果也會(huì)是false(如結(jié)果3,4)
*:匹配任意數(shù)量的字符
因?yàn)槭瞧ヅ淙我鈹?shù)量的字符,所以一般使用*來(lái)代表URL的一個(gè)層級(jí)
- @Test
- public void test2() {
- System.out.println("=======*:匹配任意數(shù)量的字符=======");
- String pattern = "/api/*/yourbatman";
- match(1, MATCHER, pattern, "/api//yourbatman");
- match(2, MATCHER, pattern, "/api/ /yourbatman");
- match(2, MATCHER, pattern, "/api/yourbatman");
- match(3, MATCHER, pattern, "/api/v1v2v3/yourbatman");
- }
- =======*:匹配任意數(shù)量的字符=======
- 1 match結(jié)果:/api/*/yourbatman 【失敗】 /api//yourbatman
- 2 match結(jié)果:/api/*/yourbatman 【成功】 /api/ /yourbatman
- 3 match結(jié)果:/api/*/yourbatman 【失敗】 /api/yourbatman
- 4 match結(jié)果:/api/*/yourbatman 【成功】 /api/v1v2v3/yourbatman
關(guān)注點(diǎn):
- 路徑的//間必須有內(nèi)容(即使是個(gè)空串)才能被*匹配到
- *只能匹配具體某一層的路徑內(nèi)容
**:匹配任意層級(jí)的路徑/目錄
匹配任意層級(jí)的路徑/目錄,這對(duì)URL這種類(lèi)型字符串及其友好。
- @Test
- public void test3() {
- System.out.println("=======**:匹配任意層級(jí)的路徑/目錄=======");
- String pattern = "/api/yourbatman/**";
- match(1, MATCHER, pattern, "/api/yourbatman");
- match(2, MATCHER, pattern, "/api/yourbatman/");
- match(3, MATCHER, pattern, "/api/yourbatman/address");
- match(4, MATCHER, pattern, "/api/yourbatman/a/b/c");
- }
- =======**:匹配任意層級(jí)的路徑/目錄=======
- 1 match結(jié)果:/api/yourbatman/** 【成功】 /api/yourbatman
- 2 match結(jié)果:/api/yourbatman/** 【成功】 /api/yourbatman/
- 3 match結(jié)果:/api/yourbatman/** 【成功】 /api/yourbatman/address
- 4 match結(jié)果:/api/yourbatman/** 【成功】 /api/yourbatman/a/b/c
**其實(shí)不僅可以放在末尾,還可放在中間。
- @Test
- public void test4() {
- System.out.println("=======**:匹配任意層級(jí)的路徑/目錄=======");
- String pattern = "/api/**/yourbatman";
- match(1, MATCHER, pattern, "/api/yourbatman");
- match(2, MATCHER, pattern, "/api//yourbatman");
- match(3, MATCHER, pattern, "/api/a/b/c/yourbatman");
- }
- =======**:匹配任意層級(jí)的路徑/目錄=======
- 1 match結(jié)果:/api/**/yourbatman 【成功】 /api/yourbatman
- 2 match結(jié)果:/api/**/yourbatman 【成功】 /api//yourbatman
- 3 match結(jié)果:/api/**/yourbatman 【成功】 /api/a/b/c/yourbatman
關(guān)注點(diǎn):
- **的匹配“能力”非常的強(qiáng),幾乎可以匹配一切:任意層級(jí)、任意層級(jí)里的任意“東西”
- **在AntPathMatcher里即可使用在路徑中間,也可用在末尾
{pathVariable:正則表達(dá)式(可選)}
該語(yǔ)法的匹配規(guī)則為:將匹配到的path內(nèi)容賦值給pathVariable。
- @Test
- public void test5() {
- System.out.println("======={pathVariable:可選的正則表達(dá)式}=======");
- String pattern = "/api/yourbatman/{age}";
- match(1, MATCHER, pattern, "/api/yourbatman/10");
- match(2, MATCHER, pattern, "/api/yourbatman/Ten");
- // 打印提取到的內(nèi)容
- extractUriTemplateVariables(MATCHER, pattern, "/api/yourbatman/10");
- extractUriTemplateVariables(MATCHER, pattern, "/api/yourbatman/Ten");
- }
- ======={pathVariable:可選的正則表達(dá)式}=======
- 1 match結(jié)果:/api/yourbatman/{age} 【成功】 /api/yourbatman/10
- 2 match結(jié)果:/api/yourbatman/{age} 【成功】 /api/yourbatman/Ten
- extractUriTemplateVariables結(jié)果:{age=10} /api/yourbatman/{age} /api/yourbatman/10
- extractUriTemplateVariables結(jié)果:{age=Ten} /api/yourbatman/{age} /api/yourbatman/Ten
熟不熟悉?一不小心,這不碰到了Spring-Web中@PathVariable的底層原理(之一)么?可能你能察覺(jué)到,age是int類(lèi)型,不應(yīng)該匹配到Ten這個(gè)值呀。這個(gè)時(shí)候我們就可以結(jié)合正則表達(dá)式來(lái)做進(jìn)一步約束啦。
- @Test
- public void test6() {
- System.out.println("======={pathVariable:可選的正則表達(dá)式}=======");
- String pattern = "/api/yourbatman/{age:[0-9]*}";
- match(1, MATCHER, pattern, "/api/yourbatman/10");
- match(2, MATCHER, pattern, "/api/yourbatman/Ten");
- // 打印提取到的內(nèi)容
- extractUriTemplateVariables(MATCHER, pattern, "/api/yourbatman/10");
- extractUriTemplateVariables(MATCHER, pattern, "/api/yourbatman/Ten");
- }
- ======={pathVariable:可選的正則表達(dá)式}=======
- 1 match結(jié)果:/api/yourbatman/{age:[0-9]*} 【成功】 /api/yourbatman/10
- 2 match結(jié)果:/api/yourbatman/{age:[0-9]*} 【失敗】 /api/yourbatman/Ten
- extractUriTemplateVariables結(jié)果:{age=10} /api/yourbatman/{age:[0-9]*} /api/yourbatman/10
- java.lang.IllegalStateException: Pattern "/api/yourbatman/{age:[0-9]*}" is not a match for "/api/yourbatman/Ten"
關(guān)注點(diǎn):
- 該匹配方式可以結(jié)合正則表達(dá)式一起使用對(duì)具體值做約束,但正則表示式是可選的
- 只有匹配成功了,才能調(diào)用extractUriTemplateVariables(...)方法,否則拋出異常
路徑匹配注意事項(xiàng)
請(qǐng)確保模式和路徑都屬于同一種類(lèi)型的路徑才有匹配的意義:要么都是絕對(duì)路徑,要么都是相對(duì)路徑。當(dāng)前,強(qiáng)烈建議是絕對(duì)路徑(以/開(kāi)頭)。
在實(shí)操中,建議在調(diào)用匹配邏輯之前統(tǒng)一對(duì)path路徑進(jìn)行“清理”(如Spring提供的StringUtils#cleanPath方法的做法):使得確保其均以/開(kāi)頭,因?yàn)檫@樣在其上下文中匹配才是有意義的。
其它接口方法
對(duì)于路徑匹配器接口PathMatcher來(lái)講最最最重要的當(dāng)屬match方法。為了雨露均沾,下面對(duì)其它幾個(gè)方法捎帶解釋以及用代碼示例一波。
isPattern()方法
- @Test
- public void test7() {
- System.out.println("=======isPattern方法=======");
- System.out.println(MATCHER.isPattern("/api/yourbatman"));
- System.out.println(MATCHER.isPattern("/api/your?atman"));
- System.out.println(MATCHER.isPattern("/api/*/yourBatman"));
- System.out.println(MATCHER.isPattern("/api/yourBatman/**"));
- }
- false
- true
- true
- true
關(guān)注點(diǎn):
1.只要含有? * ** {xxx}這種特殊字符的字符串都屬于模式
matchStart()方法
它和match方法非常像,區(qū)別為:
- match:要求全路徑完全匹配
- matchStart:模式部分匹配上,然后其它部分(若還有)是空路徑即可
- @Test
- public void test8() {
- System.out.println("=======matchStart方法=======");
- String pattern = "/api/?";
- System.out.println("match方法結(jié)果:" + MATCHER.match(pattern, "/api/y"));
- System.out.println("match方法結(jié)果:" + MATCHER.match(pattern, "/api//"));
- System.out.println("match方法結(jié)果:" + MATCHER.match(pattern, "/api////"));
- System.out.println("matchStart方法結(jié)果:" + MATCHER.matchStart(pattern, "/api//"));
- System.out.println("matchStart方法結(jié)果:" + MATCHER.matchStart(pattern, "/api////"));
- System.out.println("matchStart方法結(jié)果:" + MATCHER.matchStart(pattern, "/api///a/"));
- }
- =======matchStart方法=======
- match方法結(jié)果:true
- match方法結(jié)果:false
- match方法結(jié)果:false
- matchStart方法結(jié)果:true
- matchStart方法結(jié)果:true
- matchStart方法結(jié)果:false
關(guān)注點(diǎn):
1.請(qǐng)對(duì)比結(jié)果,看出和match方法的差異性
matchStart方法的使用場(chǎng)景少之又少,即使在代碼量巨大的Spring體系中,也只有唯一使用處:PathMatchingResourcePatternResolver#doRetrieveMatchingFiles
extractPathWithinPattern()方法
該方法通過(guò)一個(gè)實(shí)際的模式來(lái)確定路徑的哪個(gè)部分是動(dòng)態(tài)匹配的,換句話講:該方法用戶(hù)提取出動(dòng)態(tài)匹配的那部分
說(shuō)明:該方法永遠(yuǎn)不可能返回null
- @Test
- public void test9() {
- System.out.println("=======extractPathWithinPattern方法=======");
- String pattern = "/api/*.html";
- System.out.println("是否匹配成功:" + MATCHER.match(pattern, "/api/yourbatman/address")
- + ",提取結(jié)果:" + MATCHER.extractPathWithinPattern(pattern, "/api/yourbatman/address"));
- System.out.println("是否匹配成功:" + MATCHER.match(pattern, "/api/index.html")
- + ",提取結(jié)果:" + MATCHER.extractPathWithinPattern(pattern, "/api/index.html"));
- }
- =======extractPathWithinPattern方法=======
- 是否匹配成功:false,提起結(jié)果:yourbatman/address
- 是否匹配成功:true,提起結(jié)果:index.html
關(guān)注點(diǎn):
1.該方法和extractUriTemplateVariables()不一樣,即使匹配不成功也能夠返回參與匹配的那部分,有種“重在參與”的趕腳
下面再看個(gè)復(fù)雜點(diǎn)pattern情況(pattern里具有多個(gè)模式)表現(xiàn)如何:
- @Test
- public void test10() {
- System.out.println("=======extractPathWithinPattern方法=======");
- String pattern = "/api/**/yourbatman/*.html/temp";
- System.out.println("是否匹配成功:" + MATCHER.match(pattern, "/api/yourbatman/address")
- + ",提取結(jié)果:" + MATCHER.extractPathWithinPattern(pattern, "/api/yourbatman/address"));
- System.out.println("是否匹配成功:" + MATCHER.match(pattern, "/api/yourbatman/index.html/temp")
- + ",提取結(jié)果:" + MATCHER.extractPathWithinPattern(pattern, "/api/yourbatman/index.html/temp"));
- }
- =======extractPathWithinPattern方法=======
- 是否匹配成功:false,提取結(jié)果:yourbatman/address
- 是否匹配成功:true,提取結(jié)果:yourbatman/index.html/temp
關(guān)注點(diǎn):
- 該方法會(huì)返回所有參與匹配的片段,即使這匹配不成功
- 若有多個(gè)模式(如本例中的**和*),返回的片段不會(huì)出現(xiàn)跳躍現(xiàn)象(只截掉前面的非pattern匹配部分,中間若出現(xiàn)非pattern匹配部分是不動(dòng)的)
getPatternComparator()方法
此方法用于返回一個(gè)Comparator 比較器,用于對(duì)多個(gè)path之間進(jìn)行排序。目的:讓更具體的 path出現(xiàn)在最前面,也就是所謂的精確匹配優(yōu)先原則(也叫最長(zhǎng)匹配規(guī)則(has more characters))。
- @Test
- public void test11() {
- System.out.println("=======getPatternComparator方法=======");
- List
patterns = Arrays.asList( - "/api/**/index.html",
- "/api/yourbatman/*.html",
- "/api/**/*.html",
- "/api/yourbatman/index.html"
- );
- System.out.println("排序前:" + patterns);
- Comparator
patternComparator = MATCHER.getPatternComparator("/api/yourbatman/index.html"); - Collections.sort(patterns, patternComparator);
- System.out.println("排序后:" + patterns);
- }
- =======getPatternComparator方法=======
- 排序前:[/api/**/index.html, /api/yourbatman/*.html, /api/**/*.html, /api/yourbatman/index.html]
- 排序后:[/api/yourbatman/index.html, /api/yourbatman/*.html, /api/**/index.html, /api/**/*.html]
關(guān)注點(diǎn):
- 該方法擁有一個(gè)入?yún)?,作用為:用于判斷是否是精確匹配,也就是用于確定精確值的界限的(根據(jù)此界限值進(jìn)行排序)
- 越精確的匹配在越前面。其中路徑的匹配原則是從左至右(也就是說(shuō)左邊越早出現(xiàn)精確匹配,分值越高)
combine()方法
將兩個(gè)方法“綁定”在一起。PathMatcher接口并未規(guī)定綁定的“規(guī)則”,完全由底層實(shí)現(xiàn)自行決定。如基于Ant風(fēng)格的匹配器的拼接原則如下:
記得@RequestMapping這個(gè)注解吧,它既可以標(biāo)注在類(lèi)上,亦可標(biāo)注在方法上。把Pattern1比作標(biāo)注在類(lèi)上的path(若木有標(biāo)注值就是null嘛),把Pattern2比作標(biāo)注在方法上的path,它倆的結(jié)果不就可以參考上圖了麼。一不小心又加深了點(diǎn)對(duì)@RequestMapping注解的了解有木有。
使用細(xì)節(jié)
AntPathMatcher作為PathMatcher路徑匹配器模式的唯一實(shí)現(xiàn),這里有些使用細(xì)節(jié)可以幫你加深對(duì)AntPathMatcher的了解。
一些默認(rèn)值
默認(rèn)決定了AntPathMatcher的一些缺省行為,了解一下:
- public static final String DEFAULT_PATH_SEPARATOR = "/";
默認(rèn)使用/作為路徑分隔符。若是請(qǐng)求路徑(如http、RPC請(qǐng)求等)或者是Linux的目錄名,一切相安無(wú)事。但若是Windows的目錄地址呢?
說(shuō)明:windows目錄分隔符是\,如C:\ProgramData\Microsoft\Windows\Start Menu\Programs\7-Zip
- private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}");
當(dāng)路徑/目錄里出現(xiàn){xxx:正則表達(dá)式}這種模式的字符串就被認(rèn)定為是VARIABLE_PATTERN
- private static final char[] WILDCARD_CHARS = {'*', '?', '{'};
不解釋
原理
AntPathMatcher采用前綴樹(shù)(trie樹(shù)) 算法對(duì)URL進(jìn)行拆分、匹配。本類(lèi)代碼量還是不少的,整體呈現(xiàn)出比較臃腫的狀態(tài),代碼行數(shù)達(dá)到了近1000行:
或許可以對(duì)它進(jìn)行關(guān)注點(diǎn)的拆分,但這似乎已無(wú)必要。因?yàn)镾pring 5已新增的PathPattern能以更高的運(yùn)行效率、更優(yōu)雅的代碼設(shè)計(jì)來(lái)替代扎根已久的AntPathMatcher,不知這是否能勾起你對(duì)PathPattern興趣呢?
說(shuō)明:這里對(duì)原理實(shí)現(xiàn)點(diǎn)到即止,對(duì)前綴樹(shù)(trie樹(shù)) 感興趣的同學(xué)可專(zhuān)門(mén)研究,至少我也只了解個(gè)大概即止,這里就不班門(mén)弄斧了
應(yīng)用場(chǎng)景
AntPathMatcher作為PathMatcher的唯一實(shí)現(xiàn),所以Spring框架的邏輯處理里隨處可見(jiàn):
說(shuō)明:AntPathMatcher默認(rèn)使用/作為分隔符。你可根據(jù)實(shí)際情況在構(gòu)造時(shí)自行指定分隔符(如windows是\,Lunux是/,包名是.)
在應(yīng)用層面,Ant風(fēng)格的URL、目錄地址、包名地址也是隨處可見(jiàn):
- 掃包:@ComponentScan(basePackages = "cn.yourbatman.**.controller")
- 加載資源:classpath: config/application-*.yaml
- URL映射:@RequestMapping("/api/v1/user/{id}")
- ...
PathMatcher路徑匹配器是spring-core核心包的一個(gè)基礎(chǔ)組件,它帶來(lái)的能力讓Spring框架在路徑/目錄匹配上極具彈性,使用起來(lái)也是非常的方便。
再次強(qiáng)調(diào):PathMatcher它屬于spring-core,所以not only for web.
總結(jié)
Ant風(fēng)格雖然概念源自Apache Ant,但是借由Spring“發(fā)揚(yáng)光大”。在整個(gè)Spring體系內(nèi),涉及到的URL匹配、資源路徑匹配、包名匹配均是支持Ant風(fēng)格的,底層由接口PathMatcher的唯一實(shí)現(xiàn)類(lèi)AntPathMatcher提供實(shí)現(xiàn)。
AntPathMatcher不僅可以匹配Spring的@RequestMapping路徑,也可以用來(lái)匹配各種字符串,包括文件資源路徑、包名等等。由于它所處的模塊是spring-core無(wú)其它多余依賴(lài),因此若有需要(比如自己在寫(xiě)框架時(shí))我們也可以把它當(dāng)做工具來(lái)使用,簡(jiǎn)化開(kāi)發(fā)。
本文轉(zhuǎn)載自微信公眾號(hào)「 BAT的烏托邦」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 BAT的烏托邦公眾號(hào)。
新聞標(biāo)題:AntPathMatcher路徑匹配器,Ant風(fēng)格的URL
路徑分享:http://m.5511xx.com/article/coephcp.html


咨詢(xún)
建站咨詢(xún)
