1.概述

在本文中,我们将讨论使用Spring MVC 时的一个常见问题。

当使用带有@RequestMapping的路径变量(@PathVariable) 来映射URI末尾时,如果路径变量包含点,

我们获取的路径变量的内容会在最后一个点处被截断。下面我们就来研究一下原因以及解决方法。

2. Spring MVC的后缀模式匹配

Spring MVC由于解析路径变量的方式而导致这种通常不想要的行为。

Spring MVC 默认执行“.*”后缀匹配,所以一个映射到“/person”的控制器隐式地映射到“/person.*”。

这让请求通过URL路径(比如/person.pdf/person.xml)来获取资源很简单。

具体地说,Spring认为最后一个点后面的任何东西都是文件扩展名,例如 .pdf或 .xml。

让我们看一个使用路径变量的示例,然后使用不同的可能值来分析结果:

# curl http://localhost:8080/dpv/link/ripjava
firstValue: link , secondValue: ripjava%   

# curl http://localhost:8080/dpv/link.com/ripjava.java
firstValue: link.com , secondValue: ripjava%  

# curl http://localhost:8080/dpv/link.com/ripjava.java.com
firstValue: link.com , secondValue: ripjava.java

测试可以发现,第一个变量不受影响,但第二个的最后一个点之后的内容总是被截断。

3. 解决方法

3.1 使用正则表达式

一种方法是通过添加正则表达式映射来修改我们的@PathVariable定义。

这样的话,任何的点(包括最后一个点)都将被视为我们参数的一部分:

@RequestMapping(value = "/dpv2/{firstValue}/{secondValue:.+}", method = RequestMethod.GET)
@ResponseBody
public String dotPathVariable2(@PathVariable("firstValue") String firstValue,
                               @PathVariable("secondValue") String secondValue) {
  return String.format("firstValue: %s , secondValue: %s", firstValue, secondValue);
}

我们来测试一下:

# curl http://localhost:8080/dpv2/link/ripjava
firstValue: link , secondValue: ripjava%                                                         
# curl http://localhost:8080/dpv2/link.com/ripjava.java.com
firstValue: link.com , secondValue: ripjava.java.com%                                                                   
# curl http://localhost:8080/dpv2/link.com/ripjava.java.com
firstValue: link.com , secondValue: ripjava.java.com%

3.2 在@PathVariable的末尾添加斜杠

避免此问题的另一种方法是在@PathVariable的末尾添加斜杠。这将包含我们的第二个变量,以保护它免受Spring的默认行为的影响:

@RequestMapping(value = "/dpv3/{firstValue}/{secondValue}/", method = RequestMethod.GET)

需要注意的是,我们需要在每个URI末尾添加斜杠。

我们来测试一下:

# curl http://localhost:8080/dpv3/link.com/ripjava.java/
firstValue: link.com , secondValue: ripjava.java%                                       

# curl http://localhost:8080/dpv3/link.com/ripjava.java
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 404 Not Found</title>
</head>
<body><h2>HTTP ERROR 404 Not Found</h2>
<table>
<tr><th>URI:</th><td>/dpv3/link.com/ripjava.java</td></tr>
<tr><th>STATUS:</th><td>404</td></tr>
<tr><th>MESSAGE:</th><td>Not Found</td></tr>
<tr><th>SERVLET:</th><td>mvc</td></tr>
</table>
<hr><a href="http://eclipse.org/jetty">Powered by Jetty:// 9.4.24.v20191120</a><hr/>
</body>
</html>

3.3 全局修改

上面的两个解决方法适用于单个的请求映射。

如果我们需要更改全局的行为,可以通过在应用程序上下文中声明我们自己的DefaultAnnotationHandlerMapping bean并将其useDefaultSuffixPattern 属性设置为false来自定义Spring MVC配置:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.ripjava.spring.mvc.model"})
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
                .setUseSuffixPatternMatch(false);
    }
}

需要注意的是,这种方法会影响所有URL。

我们重新测试一下我们在第2节中的URL:

# curl http://localhost:8080/dpv/link/ripjava
firstValue: link , secondValue: ripjava%                                                                                
# curl http://localhost:8080/dpv/link.com/ripjava.java
firstValue: link.com , secondValue: ripjava.java%                                                                        
# curl http://localhost:8080/dpv/link.com/ripjava.java.com
firstValue: link.com , secondValue: ripjava.java.com%

4. 结论

在本文中,我们探索了在Spring MVC中使用@PathVariable@RequestMapping时解决路径变量映射URL末尾时点被截取的问题以及原因。

与往常一样,可以在GitHub上找到示例实现。