Understanding How Spring MVC's @RequestMapping POST Works
-
24-05-2021 - |
문제
I have a simple Controller that looks like this:-
@Controller
@RequestMapping(value = "/groups")
public class GroupsController {
// mapping #1
@RequestMapping(method = RequestMethod.GET)
public String main(@ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #2
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String changeGroup(@PathVariable Long id, @ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #3
@RequestMapping(method = RequestMethod.POST)
public String save(@Valid @ModelAttribute GroupForm groupForm, BindingResult bindingResult, Model model) {
...
}
}
Basically, this page has the following functionalities:-
- User visits main page (
/groups GET
). - User creates a new group (
/groups POST
) or selects a specific group (/groups/1 GET
). - User edits an existing group (
/groups/1 POST
).
I understand how both GET request mappings work here. Mapping #2 is defined, otherwise (/groups/1 GET
) will cause a "No mapping found" exception.
What I'm trying to understand here is why mapping #3 handles both (/groups POST
) and (/groups/1 POST
)? It makes sense that it should handle (/groups POST
) here since the request mapping matches the URI. Why (/groups/1 POST
) isn't causing a "No mapping found" exception being thrown here? In fact, it almost seems like any POST with URI beginning with /groups (ex: /groups/bla/1 POST
) will also be handled by mapping #3.
Can someone provide a clear explanation of this to me? Thanks much.
CLARIFICATION
I understand the fact that I can use more appropriate methods (like GET, POST, PUT or DELETE)... or I can create yet another request mapping to handle /groups/{id} POST
.
However, what I want to really know is...
.... "Why does mapping #3 handle /groups/1 POST
too?"
The "closest match" reasoning don't seem to hold true because if I remove mapping #2, then I would think mapping #1 will handle /groups/1 GET
, but it doesn't and it causes a "No mapping found" exception.
I'm just a little stumped here.
해결책
This is complicated, I think it is better to read the code.
In Spring 3.0 The magic is done by method public Method resolveHandlerMethod(HttpServletRequest request)
of the inner class ServletHandlerMethodResolver
of org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
.
An instance of this class exists for every Request Controller Class, and has a field handlerMethods
that contains a list of all the request methods.
But let me summarize how I understand it
- Spring first checks if at least one handler method matches (this can contain false negatives)
- Then it creates a map of all really matching handler methods
- Then it sorts the map by request path:
RequestSpecificMappingInfoComparator
- and takes the first one
The sorting works this way: the RequestSpecificMappingInfoComparator
first compares the path with the help of an AntPathMatcher
, if two methods are equal according to this, then other metrics (like number of parameters, number of headers, etc.) are taken into account with respect to the request.
다른 팁
Spring tries to find the mapping which matches the closest.
Hence, in your case of any POST request, the only map found for the request type is Mapping# 3.
Neither of Mapping 1 or Mapping 2 matches your request type, and hence are ignored.
May be you can try removing the Mapping #3, and see that Spring throws a runtime error since it does not find a match!
I would add a PUT mapping for /groups/{id}. I guess POST would work too but not strictly correct from a HTTP perspective.
adding @RequestMapping("/{id}", POST) should cover it?
add @PathVariable to the Long id parameter in mapping #2