Spring MVC 之 URL PathMatcher

SpringBoot 前缀匹配规则配置:

spring:
  mvc:
    pathmatch:
      use-suffix-pattern: true
      use-registered-suffix-pattern: true

目前基本所有的官方商城项目都是使用的spring mvc 框架,使用@RequestMapping来定义具体的url,例如:

示例1

1

2

3

4

5

6

7

8

@RequestMapping(value = "/shoppingcart",method = RequestMethod.GET)

public String showShoppingCartPage(@LoginMember MemberDetails memberDetails,HttpServletRequest request,HttpServletResponse response,Model model){

    //xxxxxxx

    //xxxxxxx

    //......

    return "shoppingcart.shoppingcart";

     

}

以Gucci商城举例,购物车链接是 https://www.gucci.cn/zh/shoppingcart 但是如果把url改为 https://www.gucci.cn/zh/shoppingcart.htm 也是可以访问的,

甚至是 .abcdefg 后缀都可以访问的。正常来说我们在代码当中只是配置了 "/shoppingcart",那么访问"/shoppingcart.htm" 应该直接到404页面的,而不是

到正常的购物车页面。而造成这种现象的具体原因如下:

RequestMappingInfoHandlerMapping 在处理http请求的时候, 如果请求url 有后缀,如果找不到精确匹配的那个@RequestMapping方法。

那么,就把后缀去掉,然后.* 去匹配,这样,一般都可以匹配。 比如有一个@RequestMapping("/shoppingcart"), 那么精确匹配的情况下, 只

会匹配/shoppingcart请求。 但如果我前端发来一个 /shoppingcart.abcdef 这样的请求, 又没有配置 @RequestMapping("/shoppingcartabcdef")

这样映射的情况下, 那么@RequestMapping("/shoppingcart") 就会生效。

处理链是这样的:

at org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getMatchingPattern(PatternsRequestCondition.java:254)
at org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getMatchingPatterns(PatternsRequestCondition.java:230)
at org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getMatchingCondition(PatternsRequestCondition.java:210)
at org.springframework.web.servlet.mvc.method.RequestMappingInfo.getMatchingCondition(RequestMappingInfo.java:214)
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfoHandlerMapping.java:79)
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfoHandlerMapping.java:56)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.addMatchingMappings(AbstractHandlerMethodMapping.java:358)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:328)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:299)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:57)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299)
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1104)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:916)

具体来说是AbstractHandlerMethodMapping的getHandlerInternal方法

示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

@Override

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {

    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

    if (logger.isDebugEnabled()) {

        logger.debug("Looking up handler method for path " + lookupPath);

    }

    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //主要是这个方法去匹配

    if (logger.isDebugEnabled()) {

        if (handlerMethod != null) {

            logger.debug("Returning handler method [" + handlerMethod + "]");

        }

        else {

            logger.debug("Did not find handler method for [" + lookupPath + "]");

        }

    }

    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);

}

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {

    List<Match> matches = new ArrayList<Match>();

    List<T> directPathMatches = this.urlMap.get(lookupPath);//精确匹配

    if (directPathMatches != null) {

        addMatchingMappings(directPathMatches, matches, request);

    }

    if (matches.isEmpty()) {

        // No choice but to go through all mappings...

        addMatchingMappings(this.handlerMethods.keySet(), matches, request);//不能精确匹配,就会执行这里遍历所有映射

    }

    if (!matches.isEmpty()) {

        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));

        Collections.sort(matches, comparator);

        if (logger.isTraceEnabled()) {

            logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);

        }

        Match bestMatch = matches.get(0);

        if (matches.size() > 1) {

            Match secondBestMatch = matches.get(1);

            if (comparator.compare(bestMatch, secondBestMatch) == 0) {

                Method m1 = bestMatch.handlerMethod.getMethod();

                Method m2 = secondBestMatch.handlerMethod.getMethod();

                throw new IllegalStateException(

                            "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +

                            m1 + ", " + m2 + "}");

            }

        }

        handleMatch(bestMatch.mapping, lookupPath, request);

        return bestMatch.handlerMethod;

    }

    else {

        return handleNoMatch(handlerMethods.keySet(), lookupPath, request);

    }

}

//没有精确匹配的情况下执行到PatternsRequestCondition类的 getMatchingPattern 方法,关键就在这个方法

private String getMatchingPattern(String pattern, String lookupPath) {

    if (pattern.equals(lookupPath)) {

        return pattern;

    }

    if (this.useSuffixPatternMatch) { //这里如果没有特殊设置的默认是true

        if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {

            for (String extension : this.fileExtensions) {

                if (this.pathMatcher.match(pattern + extension, lookupPath)) {

                    return pattern + extension;

                }

            }

        }

        else {//执行到else这里,如果你的请求带后缀,如*.htm 就会匹配到

            boolean hasSuffix = pattern.indexOf('.') != -1;

            if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {

                return pattern + ".*";

            }

        }

    }

    if (this.pathMatcher.match(pattern, lookupPath)) {

            return pattern;

    }

    if (this.useTrailingSlashMatch) {

        if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {

            return pattern +"/";

        }

    }

    return null;

}

如果想要精确匹配的话,需要在spring mvc的配置文件中设置,以5.3.2.X 以上版本来说需要在 springmvc-annotation-driven.xml配置文件当中添加如下代码,将后缀模式设置为false

<mvc:path-matching suffix-pattern="false" />

同时其他的一些规则,比如忽略url大小写,可以自定义规则,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//继承自AntPathMatcher路径匹配

public class CaseInsensitivePathMatcher extends AntPathMatcher {

    protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) {

        return super.doMatch(pattern.toLowerCase(), path.toLowerCase(), fullMatch, uriTemplateVariables);

    }

}

//同时在 springmvc-annotation-driven.xml当中添加如下配置,

<bean id="caseInsensitivePathMatcher" class="com.web.interceptor.CaseInsensitivePathMatcher"/>

<mvc:annotation-driven>

        <mvc:path-matching path-matcher="caseInsensitivePathMatcher"/>

</mvc:annotation-driven>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值