In this tutorial we will learn how many different kind of URI patterns can be used with @RequestMapping#value.
Spring uses AntPathMatcher for pattern matching. Following rules are applied:
? matches one character
* matches zero or more characters
** matches zero or more directories in a path
Other than above, regex pattern can also be used with template variables.
Let's go through examples to understand how these rules work.
Examples
Pattern with ?
Each ? matches a single character:
@Controller
@ResponseBody
public class MyController {
@RequestMapping("/car?/s?o?/info")
public String test1(HttpServletRequest request) {
return "from test1(), request uri: " + request.getRequestURI();
}
.............
}
Above handler will map to cars/shop/info, cart/show/info etc:
$ curl http://localhost:8080/cars/shop/info from test1(), request uri: /cars/shop/info
$ curl http://localhost:8080/cart/show/info from test1(), request uri: /cart/show/info
Pattern with *
Each * matches zero or more characters but within a single path segment (path segments are separated by /):
@Controller
@ResponseBody
public class MyController {
.............
@RequestMapping("/c*/s*d/info")
public String test2(HttpServletRequest request) {
return "from test2(), request uri: " + request.getRequestURI();
}
.............
}
Above handler will map to cars/speed/info, cabbie/signalized/info etc:
$ curl http://localhost:8080/cars/speed/info from test2(), request uri: /cars/speed/info
$ curl http://localhost:8080/cabbie/signalized/info from test2(), request uri: /cabbie/signalized/info
Pattern with **
Double wildcards can match anything including forward slashes. They are used to match directories in path.
@Controller
@ResponseBody
public class MyController {
.............
@RequestMapping("/card/**")
public String test3(HttpServletRequest request) {
return "from test3(), request uri: " + request.getRequestURI();
}
.............
}
Above handler will map to /card, /card/about, /card/visa/registration etc:
$ curl http://localhost:8080/card from test3(), request uri: /card
$ curl http://localhost:8080/card/about from test3(), request uri: /card/about
$ curl http://localhost:8080/card/visa/registration from test3(), request uri: /card/visa/registration
Path variables
Spring supports URI template capturing via @PathVariable annotation. A template can have a regex pattern as well.
@Controller
@ResponseBody
public class MyController {
.............
@RequestMapping("/card/{type}/{id:i.*}")
public String test4(@PathVariable String type, @PathVariable String id,
HttpServletRequest request) {
return "from test4(), request uri: " + request.getRequestURI() + "\n" +
"type: " + type + ", id: " + id;
}
.............
}
$ curl http://localhost:8080/card/visa/i2345d from test4(), request uri: /card/visa/i2345d type: visa, id: i2345d
$ curl http://localhost:8080/card/master/i3234 from test4(), request uri: /card/master/i3234 type: master, id: i3234
Selection between multiple matches
When multiple patterns match a request URI, they must be compared to find the best match. This done by finding the most specific match. A score is calculated to find the most specific match. The lowest score wins the comparison. Following rules are applied:
- A single wildcard (*) is counted as one.
- A double wildcard (**) is counted as two.
- URI path without any pattern (e.g. /cars/dealer) has zero count. Also a single '?' is counted as zero (e.g. /car?/d??r has total zero count).
- If one of the handlers has just "/**" (i.e. @RequestMapping("/**")) then it is always matched at end. Also patterns like /employee/** (prefix pattern, the one ending with /**) are matched after the patterns which don't have double wildcards.
- One template variable within a pair of curly braces (e.g: /card/{type}) is counted as one.
- Regex with * inside template variable (e.g. /card/{id:n.*} is also counted as one. Whereas, other regex quantifiers (+ or ?) are not counted at all.
- If multiple patterns have equal score, the longer pattern is chosen. Here length is calculated via
String#length() . In case of Template variables, content between { and } are reduced to a single # (e.g. /card/{var1}/{var2} to /card/#/#) before calculating length.
- At this point, if two patterns have same length, then the pattern with less number of wildcards (*) is selected.
- At this point, if two patterns have same number of wildcards(*), then the pattern with less number of template variables are selected.
Above logic is implemented in AntPathStringMatcher#compare method (this class is nested class of AntPathMatcher ).
Let's see more examples:
@Controller
@ResponseBody
public class MyController {
@RequestMapping("/car?/s?o?/info")//score 0
public String test1(HttpServletRequest request) {
return "from test1(), request uri: " + request.getRequestURI();
}
@RequestMapping("/c*/s*d/info")//score 2, length = 12
public String test2(HttpServletRequest request) {
return "from test2(), request uri: " + request.getRequestURI();
}
@RequestMapping("/card/**")//score 2 but will be used after others because of prefix pattern
public String test3(HttpServletRequest request) {
return "from test3(), request uri: " + request.getRequestURI();
}
@RequestMapping("/card/{type}/{id:i.*}")//2 template variables + 1 wildcard = score 3
public String test4(@PathVariable String type, @PathVariable String id,
HttpServletRequest request) {
return "from test4(), request uri: " + request.getRequestURI() + "\n" +
"type: " + type + ", id: " + id;
}
@RequestMapping("/card/{type}/{id:i.+}")//score 2, length 9 (/card/#/#)
public String test5(@PathVariable String type, @PathVariable String id,
HttpServletRequest request) {
return "from test5(), request uri: " + request.getRequestURI() + "\n" +
"type: " + type + ", id: " + id;
}
}
Request URI: 'card/shod/info':
This URI matches all handlers methods, but test1() will be selected because it has the lowest score:
$ curl http://localhost:8080/care/shod/info from test1(), request uri: /care/shod/info
Request URI 'card/send/info':
It matches all handlers except for test1().
test2() and test5() have the lowest count (2), but test2() will be selected because it is longer in length.
$ curl http://localhost:8080/card/send/info from test2(), request uri: /card/send/info
Let's see other controller:
@Controller
@RequestMapping("/other/")
@ResponseBody
public class MyController2 {
@RequestMapping("/c*/s*d/info")//score 2, length = 12, wildcards=2
public String otherTest1(HttpServletRequest request) {
return "from otherTest1(), request uri: " + request.getRequestURI();
}
@RequestMapping("/card/{type}/info")//score 1, length 12 (/card/#/info), wildcards=0
public String otherTest2(@PathVariable String type, HttpServletRequest request) {
return "from otherTest2(), request uri: " + request.getRequestURI() + "\n" +
"type: " + type;
}
}
In this case the request uri /other/card/send/info matches both methods but the second method will be selected because it has the lowest score:
$ curl http://localhost:8080/other/card/send/info from otherTest2(), request uri: /other/card/send/info type: send
Here is another controller:
@Controller
@RequestMapping("/another/")
@ResponseBody
public class MyController3 {
@RequestMapping("/c*/s*d/info")//score 2, length = 12, wildcards=2
public String anotherTest1(HttpServletRequest request) {
return "from anotherTest1(), request uri: " + request.getRequestURI();
}
@RequestMapping("/card/{type}/inf{id:.+}")//score 2, length 12 (/card/#/info), wildcard = 0
public String anotherTest2(@PathVariable String type, @PathVariable String id,
HttpServletRequest request) {
return "from anotherTest2(), request uri: " + request.getRequestURI()+"\n"+
"type: "+type+", id: "+id;
}
}
In this case the request uri /another/card/send/info matches both methods. Both methods have same scores and same length but last method will be selected because it has the less number of wildcards (*):
$ curl http://localhost:8080/another/card/send/info from anotherTest2(), request uri: /another/card/send/info type: send, id: o
Also check out following tutorials, where URI path pattern is involved:
Example ProjectDependencies and Technologies Used: - spring-webmvc 5.1.1.RELEASE: Spring Web MVC.
- javax.servlet-api 3.0.1 Java Servlet API
- jstl 1.2 javax.servlet:jstl
- JDK 1.8
- Maven 3.5.4
|