我正在尝试使应用程序的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) {
...
为了更加宁静,我希望该端点同时执行以下操作:
因此,在我的控制器中,我可以测试{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
答案 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:
/thing
/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:
/thing
: getThings
/thing/
: getThings
/thing/foo
: getThing
An example of this workaround, including test cases can be found here.