背景
spring-boot的版本是2.1.4.RELEASE,spring的版本是5.1.6.RELEASE
一个例子如下:
@Configuration @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class) @SuppressWarnings("unchecked") public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new RequestMappingHandlerMapping(); } } @RestController public class ParamController { @GetMapping(value = "/param/{param1}") public String param(@PathVariable("param1") String param1) { return param1; } } @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
启动一下,访问http://127.0.0.1:8080/param/hehe
和http://127.0.0.1:8080/param/hehe.hehe
都返回hehe
如果访问http://127.0.0.1:8080/param/hehe.hehe.hehe
,它会返回hehe.hehe
所以会发现它把最后一个小数点后面的字符给截掉了,那如果我们想要获取完整的字符串,该怎么办呢?
探索
- 参数怎么来的
入口在InvocableHandlerMethod.invokeForRequest
,如下:
是根据PathVariableMethodArgumentResolver.resolveName
得来的,如下:
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
什么时候放进attributes
里的?,在RequestMappingInfoHandlerMapping.handleMatch
,如下:
- 参数怎么解析的
程序定义的:/param/{param1} -> /param/{param1}.* 接口传过来的:/param/hehe.hehe 以/分割,第一个字符串param里没有参数,所以会跳过,直接看第二个字符串: {param1}.* -> pattern=(.*)/Q./E.* hehe.hehe param1=hehe
我们定义的是/param/{param1}
,怎么就变成了/param/{param1}.*
?在PatternsRequestCondition.getMatchingPattern
可以看到如果useSuffixPatternMatch为true,并且指定的url里没有.,会在后缀自动增加.*
- useSuffixPatternMatch是在哪里设置的?
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware { private boolean useSuffixPatternMatch = true; @Override public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); // 这里设置了 this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet(); } }
解决
- 修改
WebConfig
的getRequestMappingHandlerMapping
@Configuration @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class) @SuppressWarnings("unchecked") public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping(); requestMappingHandlerMapping.setUseSuffixPatternMatch(false); return requestMappingHandlerMapping; } }
- 增加
configurePathMatch
方法
@Configuration @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class) @SuppressWarnings("unchecked") public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new RequestMappingHandlerMapping(); } @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); } }
- url中增加点
a. 中间的参数是不受影响的
@RestController public class ParamController { @GetMapping(value = "/param/{param1}/{param2}") public String param(@PathVariable("param1") String param1, @PathVariable("param2") String param2) { return param1 + " " + param2; } }
访问http://127.0.0.1:8080/param/hehe.hehe/hehe.hee
返回hehe.hehe hehe
b. 增加点
@RestController public class ParamController { //@GetMapping(value = "/param/{param1}") //public String param(@PathVariable("param1") String param1) { // return param1; //} @GetMapping(value = "/param/{param1}.{param2}") public String param(@PathVariable("param1") String param1, @PathVariable("param2") String param2) { return param1 + " " + param2; } }
访问http://127.0.0.1:8080/param/hehe.hehe
返回hehe hehe
注意第一个方法和第二个方法不要同时出现,如果同时出现的话,则会访问第一个方法
@RestController public class ParamController { @GetMapping(value = "/param/{param1}") public String param(@PathVariable("param1") String param1) { return param1; } @GetMapping(value = "/param/{param1}.{param2}") public String param(@PathVariable("param1") String param1, @PathVariable("param2") String param2) { return param1 + " " + param2; } }
访问http://127.0.0.1:8080/param/hehe.heh
返回hehe
- 修改参数
@RestController public class ParamController { @GetMapping(value = "/param/{param1:.+}") public String param(@PathVariable("param1") String param1) { return param1; } }
访问http://127.0.0.1:8080/param/hehe.hehe
,返回hehe.hehe
原因如下:
/param/{param1:.+} -> /param/{param1:.+} /param/hehe.hehe {param1:.+} -> pattern=(.+) hehe.hehe param1=hehe.hehe
得到pattern以及提取参数的类 AntPathStringMatcher protected static class AntPathStringMatcher { private static final Pattern GLOB_PATTERN = Pattern.compile("//?|//*|//{((?://{[^/]+?//}|[^/{}]|////[{}])+?)//}"); private static final String DEFAULT_VARIABLE_PATTERN = "(.*)"; private final Pattern pattern; private final List<String> variableNames = new LinkedList<>(); public AntPathStringMatcher(String pattern) { this(pattern, true); } public AntPathStringMatcher(String pattern, boolean caseSensitive) { StringBuilder patternBuilder = new StringBuilder(); Matcher matcher = GLOB_PATTERN.matcher(pattern); int end = 0; while (matcher.find()) { patternBuilder.append(quote(pattern, end, matcher.start())); String match = matcher.group(); if ("?".equals(match)) { patternBuilder.append('.'); } else if ("*".equals(match)) { patternBuilder.append(".*"); } else if (match.startsWith("{") && match.endsWith("}")) { int colonIdx = match.indexOf(':'); if (colonIdx == -1) { patternBuilder.append(DEFAULT_VARIABLE_PATTERN); this.variableNames.add(matcher.group(1)); } else { String variablePattern = match.substring(colonIdx + 1, match.length() - 1); patternBuilder.append('('); patternBuilder.append(variablePattern); patternBuilder.append(')'); String variableName = match.substring(1, colonIdx); this.variableNames.add(variableName); } } end = matcher.end(); } patternBuilder.append(quote(pattern, end, pattern.length())); this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) : Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); } private String quote(String s, int start, int end) { if (start == end) { return ""; } return Pattern.quote(s.substring(start, end)); } /** * Main entry point. * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. */ public boolean matchStrings(String str, @Nullable Map<String, String> uriTemplateVariables) { Matcher matcher = this.pattern.matcher(str); if (matcher.matches()) { if (uriTemplateVariables != null) { // SPR-8455 if (this.variableNames.size() != matcher.groupCount()) { throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + this.pattern + " does not match the number of URI template variables it defines, " + "which can occur if capturing groups are used in a URI template regex. " + "Use non-capturing groups instead."); } for (int i = 1; i <= matcher.groupCount(); i++) { String name = this.variableNames.get(i - 1); String value = matcher.group(i); uriTemplateVariables.put(name, value); } } return true; } else { return false; } } }