Spring控制器RequestMapping PathVariable中URL的零长度部分会破坏解析

时间:2019-01-07 13:17:08

标签: java rest spring-web

我正在尝试使应用程序的REST API更富RESTful,感觉好像我没有按预期的方式使用Spring RequestMappings。

我只有一个GET端点可以进行读取:

@RequestMapping(value = "thing/{thingName}",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getThing(
        @PathVariable(value = "thingName", required = false)
                String thingName,
        @RequestParam(value = "findByComponent", required = false)
                String findByComponentQuery,
        @AuthenticationPrincipal User user) {
...

为了更加宁静,我希望该端点同时执行以下操作:

  1. GET / thing / {thingName}返回一个具有该名称的东西
  2. 使用查询参数获取/ thing或/ thing /返回事物列表

因此,在我的控制器中,我可以测试{thingName}是空还是零长度,如果是,请检查查询参数中的已知查询名称。

但是使用http://localhost:8080/thing/?findByComponent=component123调用它会从Spring返回404并显示以下记录:

12:45:18.485 PageNotFound : No mapping found for HTTP request with URI [/thing/] in DispatcherServlet with name 'dispatcher' : WARN : XNIO-1 task-3 : org.springframework.web.servlet.DispatcherServlet  

1 个答案:

答案 0 :(得分:1)

Spring does not allow path variables ({thingName}) to be mapped to an empty String. In practice, this means that the URL /thing/?findByComponent=component123 does not map to thing/{thingName} with an empty {thingName}, but rather, it expects there to be some mapping for thing. Since there is no endpoint that maps to the path thing (without the path variable), a 404 error is returned.

To solve this issue, you can break this single endpoint into two separate endpoints:

@RequestMapping(value = "thing/{thingName}",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getThing(
        @PathVariable(value = "thingName") String thingName,
        @AuthenticationPrincipal User user) {
    // ...
}

@RequestMapping(value = "thing",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getThings(,
        @RequestParam(value = "findByComponent", required = false) String findByComponentQuery,
        @AuthenticationPrincipal User user) {
    // ...
}

For more information, see With Spring 3.0, can I make an optional path variable?.

The required=false flag allows for two types of requests:

  1. /thing
  2. /thing/<some_value>

Strictly speaking, including a trailing slash at the end of the URL (i.e. /thing/) means that a decision was made to include a value for the path variable, but none was provided. In the context of REST APIs, /thing and /thing/ are two different endpoints, where the latter means that a value after the trailing slash was expected.

A workaround for not having to create three separate request mappings (one for each case above) is to set the @RequestMapping value for the controller to the base path and then have a "" and "/{thingName} request mapping for the two endpoints:

@RestController
@RequestMapping("thing")
public class ThingController {

    @RequestMapping(value = "/{thingName}",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public String getThing(
            @PathVariable(value = "thingName") String thingName) {
        return "foo";
    }

    @RequestMapping(value = "",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public String getThings(
            @RequestParam(value = "findByComponent", required = false) String findByComponentQuery) {
        return "bar";
    }
}

In this case, the following mappings will occur:

  1. /thing: getThings
  2. /thing/: getThings
  3. /thing/foo: getThing

An example of this workaround, including test cases can be found here.