使用场景
网关的路由编排,权限管理,URL白名单等场景下,都可能需要判断给定的URL是否符合匹配某个模式。
URI Template Patterns 模式匹配
Spring 提供了一个很好用的工具类:org.springframework.web.util.UriTemplate,
Representation of a URI template that can be expanded with URI variables via expand(Map), expand(Object[]), or matched to a URL via match(String). This class is designed to be thread-safe and reusable, and allows any number of expand or match calls.
Note: this class uses UriComponentsBuilder internally to expand URI templates, and is merely a shortcut for already prepared URI templates. For more dynamic preparation and extra flexibility, e.g. around URI encoding, consider using UriComponentsBuilder or the higher level DefaultUriBuilderFactory which adds several encoding modes on top of UriComponentsBuilder. See the reference docs for further details.
import org.apache.commons.lang3.text.StrSubstitutor;
import org.springframework.web.util.UriTemplate;
import java.util.Map;
public class UriTemplateTest {
public static void main(String[] args) {
String template = "http://www.demo.com/api/user/{userId}/postId/{postId}";
String targetUri = "http://www.demo.com/user/{userId}/postId/{postId}";
UriTemplate uriTemplate = new UriTemplate(template);
Map<String, String> valueMap = uriTemplate.match("http://www.demo.com/api/user/12345/postId/98012309");
System.out.println(valueMap);
String resultUri = StrSubstitutor.replace(targetUri, valueMap, "{", "}");
System.out.println(resultUri);
}
}
输出结果为:
{userId=12345, postId=98012309}
http://www.demo.com/user/12345/postId/98012309
UriTemplate还可以用来构建URI,示例如下:
UriTemplate template = new UriTemplate("https://example.com/hotels/{hotel}/bookings/{booking}");
Map<String, String> uriVariables = new HashMap<String, String>();
uriVariables.put("booking", "492");
uriVariables.put("hotel", "FourSeason");
System.out.println(template.expand(uriVariables));
将会输出:
https://example.com/hotels/FourSeason/bookings/492
Restful鉴权白名单匹配url
开发的中会遇到这样一种情况,在鉴权或者做权限管理的时候,需要过滤掉白名单,例如:定义有这样一个url:product/detail/{id},需要判断uri是否能通过,用传统的equals方法十分麻烦,Spring提供了一个很好用的类,用于匹配:AntPathMatcher,使用AntPathMatcher就很轻松解决这个问题。
public static void testAntPathMatcher() {
AntPathMatcher antPathMatcher = new AntPathMatcher();
// path路径是否符合pattern的规范
boolean match = antPathMatcher.match("/user/*", "/user/a");
System.out.println("antPathMatcher.match(\"/user/*\", \"/user/a\") result: " + match);
match = antPathMatcher.match("/user/**", "/user/a/b");
System.out.println("antPathMatcher.match(\"/user/**\", \"/user/a/b\") result: " + match);
match = antPathMatcher.match("/user/{id}", "/user/1");
System.out.println(match);
match = antPathMatcher.match("/user/name", "/user/a");
System.out.println(match);
boolean pattern = antPathMatcher.isPattern("user/{id}");
System.out.println(pattern);
// 匹配是不是以path打头的地址
boolean matchStart = antPathMatcher.matchStart("/9user/a", "/user");
System.out.println(matchStart);
// 对路径进行合并 --> /user/a/b
String combine = antPathMatcher.combine("/user", "a/b");
System.out.println("对路径进行合并 --> /user/a/b:" + combine);
// 找出模糊匹配中 通过*或者? 匹配上的那一段配置
String extractPathWithinPattern = antPathMatcher.extractPathWithinPattern("/user/?", "/user/1");
System.out.println(extractPathWithinPattern);
}
注意:
The mapping matches URLs using the following rules:
? matches one character
* matches zero or more characters
** matches zero or more directories in a path
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
? 匹配1个字符
* 匹配0个或多个字符
** 匹配路径中的0个或多个目录
{spring:[a-z]+} 将正则表达式[a-z]+匹配到的值,赋值给名为 spring 的路径变量.(PS:必须是完全匹配才行,在SpringMVC中只有完全匹配才会进入controller层的方法)
符号 *
,虽然可以匹配多个任意的字符,但是,如果你以为 * 可以替代 ** 那就错了, 代表的多个任意字符组成的字符串不能是个 目录 或者说 路径.也就是说,* 并不能拿来替代 **.
示例:
@RequestMapping("/index*")
@ResponseBody
public String index(){
System.out.println("SpaceX");
return "SpaceX";
}
结果:
index true 输出 SpaceX(可以为0字符)
index/ true 输出 SpaceX(可以为"/")
indexa true 输出 SpaceX(可以为1个字符)
indexabc true 输出 SpaceX(可以为多个字符)
index/a false 404错误("/a"是一个路径)
符号 **
0个或多个目录.** 代表的字符串本身不一定要包含 /
示例:
@RequestMapping("/index/**/a")
@ResponseBody
public String index(){
System.out.println();
return "SpaceX";
}
结果:
index/a true 输出 SpaceX(可以为0个目录)
index/x/a true 输出 SpaceX(可以为一个目录)
index/x/z/c/a true 输出 SpaceX(可以为多个目录)
符号 {spring:[a-z]+}
其它的关于 AntPathMatcher 的文章里,对 {spring:[a-z]+} 的匹配大多是只字未提.这里补充下.
示例:
@RequestMapping("/index/{username:[a-b]+}")
@ResponseBody
public String index(@PathVariable("username") String username){
System.out.println(username);
return username;
}
结果:
index/ab true 输出 ab
index/abbaaa true 输出 abbaaa
index/a false 404错误
index/ac false 404错误
(完)