Spring 3 RequestMapping:获取路径值

156

在解析过requestMapping@PathVariable值后,是否有一种方法可以获取完整的路径值?

也就是说: /{id}/{restOfTheUrl} 应该能够将 /1/dir1/dir2/file.html 解析为 id=1restOfTheUrl=/dir1/dir2/file.html

欢迎提出任何想法。

11个回答

215

URL中未匹配的部分会以请求属性的形式暴露出来,该属性名为HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE:

@RequestMapping("/{id}/**")
public void foo(@PathVariable("id") int id, HttpServletRequest request) {
    String restOfTheUrl = new AntPathMatcher().extractPathWithinPattern(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),request.getRequestURI());
    ...
}

72
不,属性HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE包含完整的匹配路径。 - uthark
12
乌萨克是正确的。restOfTheUrl中的值将是整个路径,而不仅仅是由**捕获的剩余部分。 - dcstraw
4
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE是可选的,某些实现可能为NULL或""。request.getRequestURI()返回相同的值,并且不是可选的。 - nidalpres
2
这个解决方案已经不再可行且不可靠。 - DolphinJava
@DolphinJava 更新了解决方案,使其与最新的Spring版本兼容。 - Amith Kumar

59

刚刚发现了与我的问题对应的问题。使用HandlerMapping常量,我能够编写一个小实用程序来解决这个问题:

/**
 * Extract path from a controller mapping. /controllerUrl/** => return matched **
 * @param request incoming request.
 * @return extracted path
 */
public static String extractPathFromPattern(final HttpServletRequest request){


    String path = (String) request.getAttribute(
            HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);

    AntPathMatcher apm = new AntPathMatcher();
    String finalPath = apm.extractPathWithinPattern(bestMatchPattern, path);

    return finalPath;

}

24

这里已经有一段时间了,但仍然发布。对于某些人可能会有用。

@RequestMapping( "/{id}/**" )
public void foo( @PathVariable String id, HttpServletRequest request ) {
    String urlTail = new AntPathMatcher()
            .extractPathWithinPattern( "/{id}/**", request.getRequestURI() );
}

3
这段代码存在的问题是它没有处理Servlet前缀和映射前缀。 - gavenkoa

18
Fabien Kruba的优秀回答的基础上,我认为如果可以通过注释将URL的**部分作为参数传递给控制器方法,方式类似于@RequestParam@PathVariable,而不总是使用需要显式要求HttpServletRequest的实用程序方法,那将是很好的。因此,以下是如何实现的示例。希望对某些人有用。

创建注释以及参数解析器:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WildcardParam {

    class Resolver implements HandlerMethodArgumentResolver {

        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.getParameterAnnotation(WildcardParam.class) != null;
        }

        @Override
        public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
            HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
            return request == null ? null : new AntPathMatcher().extractPathWithinPattern(
                    (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE),
                    (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
        }

    }

}

注册方法参数解析器:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new WildcardParam.Resolver());
    }

}

使用控制器处理方法中的注释,可以轻松访问URL中的**部分:

@RestController
public class SomeController {

    @GetMapping("/**")
    public void someHandlerMethod(@WildcardParam String wildcardParam) {
        // use wildcardParam here...
    }

}

12

你需要使用内置的 pathMatcher

@RequestMapping("/{id}/**")
public void test(HttpServletRequest request, @PathVariable long id) throws Exception {
    ResourceUrlProvider urlProvider = (ResourceUrlProvider) request
            .getAttribute(ResourceUrlProvider.class.getCanonicalName());
    String restOfUrl = urlProvider.getPathMatcher().extractPathWithinPattern(
            String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)),
            String.valueOf(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)));

2
确认这个在最新版本的Spring Boot中可以工作。 - Dave Bauman
1
同时确认该方法适用于Spring Boot 2.2.4 RELEASE版本。 - tom_mai78101

6

我已经使用了Tuckey URLRewriteFilter来处理包含'/'字符的路径元素,因为我认为Spring 3 MVC尚不支持它们。

http://www.tuckey.org/

你可以将此过滤器添加到你的应用程序中,并提供一个XML配置文件。在该文件中,你可以提供重写规则,使用这些规则可以将包含'/'字符的路径元素转换为请求参数,Spring MVC可以使用@RequestParam正确处理这些参数。

WEB-INF/web.xml:

<filter>
  <filter-name>UrlRewriteFilter</filter-name>
  <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<!-- map to /* -->

WEB-INF/urlrewrite.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite
    PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
    "http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
  <rule>
    <from>^/(.*)/(.*)$</from>
    <to last="true">/$1?restOfTheUrl=$2</to>
</urlrewrite>

控制器方法:

@RequestMapping("/{id}")
public void handler(@PathVariable("id") int id, @RequestParam("restOfTheUrl") String pathToFile) {
  ...
}

4

是的,restOfTheUrl并不仅返回所需值,但我们可以通过使用UriTemplate匹配来获取该值。

我已经解决了这个问题,这里是解决方案:

@RequestMapping("/{id}/**")
public void foo(@PathVariable("id") int id, HttpServletRequest request) {
String restOfTheUrl = (String) request.getAttribute(
    HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    /*We can use UriTemplate to map the restOfTheUrl*/
    UriTemplate template = new UriTemplate("/{id}/{value}");        
    boolean isTemplateMatched = template.matches(restOfTheUrl);
    if(isTemplateMatched) {
        Map<String, String> matchTemplate = new HashMap<String, String>();
        matchTemplate = template.match(restOfTheUrl);
        String value = matchTemplate.get("value");
       /*variable `value` will contain the required detail.*/
    }
}

1
这是我做的方法。您可以看到我如何将requestedURI转换为文件系统路径(这就是SO问题所涉及的内容)。奖励:还有如何使用文件响应的方式。
@RequestMapping(value = "/file/{userId}/**", method = RequestMethod.GET)
public void serveFile(@PathVariable("userId") long userId, HttpServletRequest request, HttpServletResponse response) {
    assert request != null;
    assert response != null;

    // requestURL:  http://192.168.1.3:8080/file/54/documents/tutorial.pdf
    // requestURI:  /file/54/documents/tutorial.pdf
    // servletPath: /file/54/documents/tutorial.pdf
    // logger.debug("requestURL: " + request.getRequestURL());
    // logger.debug("requestURI: " + request.getRequestURI());
    // logger.debug("servletPath: " + request.getServletPath());

    String requestURI = request.getRequestURI();
    String relativePath = requestURI.replaceFirst("^/file/", "");

    Path path = Paths.get("/user_files").resolve(relativePath);
    try {
        InputStream is = new FileInputStream(path.toFile());  
        org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
        response.flushBuffer();
    } catch (IOException ex) {
        logger.error("Error writing file to output stream. Path: '" + path + "', requestURI: '" + requestURI + "'");
        throw new RuntimeException("IOError writing file to output stream");
    }
}

0
为了改进@Daniel Jay Marcaida的答案
@RequestMapping( "/{id}/**" )
public void foo( @PathVariable String id, HttpServletRequest request ) {
    String restOfUrl = new AntPathMatcher()
           .extractPathWithinPattern(
            request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),
            request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString());
}

或者

@RequestMapping( "/{id}/**" )
public void foo( @PathVariable String id, HttpServletRequest request ) {
    String restOfUrl = new AntPathMatcher()
    .extractPathWithinPattern(
            request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(),
            request.getServletPath());
}

0
private final static String MAPPING = "/foo/*";

@RequestMapping(value = MAPPING, method = RequestMethod.GET)
public @ResponseBody void foo(HttpServletRequest request, HttpServletResponse response) {
    final String mapping = getMapping("foo").replace("*", ""); 
    final String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    final String restOfPath = url.replace(mapping, "");
    System.out.println(restOfPath);
}

private String getMapping(String methodName) {
    Method methods[] = this.getClass().getMethods();
    for (int i = 0; i < methods.length; i++) {
        if (methods[i].getName() == methodName) {
            String mapping[] = methods[i].getAnnotation(RequestMapping.class).value();
            if (mapping.length > 0) {
                return mapping[mapping.length - 1];
            }
        }
    }
    return null;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接