获取Spring中@PathVariable注解里带点的完整参数


背景

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/hehehttp://127.0.0.1:8080/param/hehe.hehe都返回hehe

如果访问http://127.0.0.1:8080/param/hehe.hehe.hehe,它会返回hehe.hehe

所以会发现它把最后一个小数点后面的字符给截掉了,那如果我们想要获取完整的字符串,该怎么办呢?

探索

  1. 参数怎么来的

入口在InvocableHandlerMethod.invokeForRequest,如下:
获取Spring中@PathVariable注解里带点的完整参数

是根据PathVariableMethodArgumentResolver.resolveName得来的,如下:
获取Spring中@PathVariable注解里带点的完整参数
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

什么时候放进attributes里的?,在RequestMappingInfoHandlerMapping.handleMatch,如下:
获取Spring中@PathVariable注解里带点的完整参数

  1. 参数怎么解析的
程序定义的:/param/{param1} -> /param/{param1}.* 接口传过来的:/param/hehe.hehe  以/分割,第一个字符串param里没有参数,所以会跳过,直接看第二个字符串: {param1}.* -> pattern=(.*)Q.E.* hehe.hehe  param1=hehe 

我们定义的是/param/{param1},怎么就变成了/param/{param1}.*?在PatternsRequestCondition.getMatchingPattern
获取Spring中@PathVariable注解里带点的完整参数
可以看到如果useSuffixPatternMatch为true,并且指定的url里没有.,会在后缀自动增加.*

  1. 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(); 	} } 

解决

  1. 修改WebConfiggetRequestMappingHandlerMapping
@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;     } } 
  1. 增加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);     } } 
  1. 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

  1. 修改参数
@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; 			} 		} 	} 
发表评论

相关文章