0 Summary
This article briefly explains the processor mapping link of SpringMVC from the source code level, that is, the detailed process of finding the Controller
1 SpringMVC request process
Controller searches for steps 1 to 2 corresponding to the above image
SpringMVC detailed operation flow chart
2 SpringMVC initialization process
2.1 First understand two categories
1.RequestMappingInfo
Encapsulation RequestMapping annotation
Contains relevant information about HTTP request headers
An instance corresponds to a RequestMapping annotation
2.HandlerMethod
Method of handling requests for encapsulating Controller
Includes the bean object to which the method belongs, the method object corresponding to the method, the parameters of the method, etc.
RequestMappingHandlerMapping inheritance relationship
During SpringMVC initialization
First execute the afterPropertiesSet of RequestMappingHandlerMapping
Then enter the AbstractHandlerMethodMapping afterPropertiesSet
This method will enter the initHandlerMethods of this class
Responsible for scanning beans from applicationContext, then finding and registering processor methods from beans
//Scan beans in the ApplicationContext, detect and register handler methods.protected void initHandlerMethods() { ... //Get all beans in the applicationContext String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludedAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); //Transfer the beanName array for (String beanName: beanNames) { //isHandler will judge whether the bean definition contains Controller annotation or RequestMapping annotation based on the bean. If (isHandler(getApplicationContext().getType(beanName))){ detectHandlerMethods(beanName); } } handlerMethodsInitialized(getHandlerMethods());} RequestMappingHandlerMapping#isHandler
The above method is to determine whether the current bean definition contains Controller annotation or RequestMapping annotation
If only RequestMapping takes effect? No!
Because in this case, the class will not be registered as a Spring bean when initializing Spring, and the class will not be traversed when traversing beanNames, so it is OK to replace the Controller with Compoent here, but this is not done in general
After confirming that the bean is a handler, the specific handler method will be found from the bean (that is, the request processing method defined in the Controller class). The search code is as follows
/** * Look for handler methods in a handler * @param handler the bean name of a handler or a handler instance */protected void detectHandlerMethods(final Object handler) { //Get the class object of the current Controller beanClass<?> handlerType = (handler instanceof String) ? getApplicationContext().getType((String) handler) : handler.getClass(); //Avoid repeated calls to getMappingForMethod to rebuild the RequestMappingInfo instance final Map<Method, T> mappings = new IdentityHashMap<Method, T>(); //Same as above, it is also the class object of the Controller bean final Class<?> userType = ClassUtils.getUserClass(handlerType); //Get all handler methods of the current bean //Define whether it has RequestMapping according to the method //If there is, create a RequestMappingInfo instance Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { @Override public boolean matches(Method method) { T mapping = getMappingForMethod(method, userType); if (mapping != null) { mappings.put(method, mapping); return true; } else { return false; } } }); //Travel and register all handler method of the current bean for (Method method : methods) { //Register handler method and enter the following method registerHandlerMethod(handler, method, mappings.get(method)); } There are two places in the above code that call getMappingForMethod
Create RequestMappingInfo using the method and type level RequestMapping annotation
@Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = null; //Get the method's @RequestMapping RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (methodAnnotation != null) { RequestCondition<?> methodCondition = getCustomMethodCondition(method); info = createRequestMappingInfo(methodAnnotation, methodCondition); //Get the @RequtestMapping annotation of the bean to which the method belongs RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); if (typeAnnotation != null) { RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType); //Merge two @RequestMapping annotations info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); } } return info; } The purpose of this method is to create a RequestMappingInfo object based on the handler method method. First, determine whether the mehtod contains the RequestMpping annotation. If so, create a RequestMappingInfo object directly based on the content of the annotation. After creation, determine whether the bean to which the current method also contains the RequestMapping annotation. If this annotation is included, a RequestMappingInfo object will be created based on the annotation on the class. Then, the RequestMappingInfo object on the merge method is returned, and the merged object is finally returned. Now, looking back at the detectHandlerMethods method, there are two calls to getMappingForMethod method. I personally think this can be optimized. When judging whether the method is a handler in the first place, the created RequestMappingInfo object can be saved and used directly later, which means that the process of creating a RequestMappingInfo object is missing. Then immediately enter the registerHandlerMehtod method, as follows
protected void registerHandlerMethod(Object handler, Method method, T mapping) { //Create HandlerMethod HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); HandlerMethod oldHandlerMethod = handlerMethods.get(mapping); //Check whether there is ambiguity in the configuration if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) { throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() + "' bean method /n" + newHandlerMethod + "/nto " + mapping + ": There is already '" + oldHandlerMethod.getBean() + "' bean method/n" + oldHandlerMethod + " mapped."); } this.handlerMethods.put(mapping, newHandlerMethod); if (logger.isInfoEnabled()) { logger.info("Mapped /"" + mapping + "/" onto " + newHandlerMethod); } //Get the value of @RequestMapping annotation, and then add the value->RequestMappingInfo mapping record to the urlMap Set<String> patterns = getMappingPathPattern(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this.urlMap.add(pattern, mapping); } }} Here the type of T is RequestMappingInfo. This object is the relevant information of the RequestMapping annotation of the method under the specific controller encapsulated. A RequestMapping annotation corresponds to a RequestMappingInfo object. HandlerMethod is similar to RequestMappingInfo, and is an encapsulation of specific processing methods under Controlelr. First look at the first line of the method and create a HandlerMethod object based on handler and mehthod. The second line uses handlerMethods map to get the HandlerMethod corresponding to the current mapping. Then determine whether the same RequestMapping configuration exists. The following configuration will cause the thrown here
Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map...
abnormal
@Controller@RequestMapping("/AmbiguousTest")public class AmbiguousTestController { @RequestMapping(value = "/test1") @ResponseBody public String test1(){ return "method test1"; } @RequestMapping(value = "/test1") @ResponseBody public String test2(){ return "method test2"; }} Check whether the RequestMapping configuration is ambiguity during the SpringMVC startup (initialization) phase, which is one of the things to check for ambiguity (a place to check for ambiguity at runtime will be mentioned later). Then, after confirming that the configuration is normal, the RequestMappingInfo and HandlerMethod objects will be added to handlerMethods (LinkedHashMap), and then the RequestMapping annotated value and ReuqestMappingInfo objects will be added to the urlMap.
Simple summary of registerHandlerMethod method
There are three main responsibilities of this method
1. Check whether the RequestMapping annotation configuration is ambiguity.
2. Build a map of RequestMappingInfo to HandlerMethod. This map is the member variable handlerMethods of AbstractHandlerMethodMapping. LinkedHashMap.
3. Build the member variable urlMap of AbstractHandlerMethodMapping and MultiValueMap. This data structure can be understood as Map>. The String type key stores the value of the RequestMapping annotation on the processing method. It's the specific uri
First there is the following controller
@Controller@RequestMapping("/UrlMap")public class UrlMapController { @RequestMapping(value = "/test1", method = RequestMethod.GET) @ResponseBody public String test1(){ return "method test1"; } @RequestMapping(value = "/test1") @ResponseBody public String test2(){ return "method test2"; } @RequestMapping(value = "/test3") @ResponseBody public String test3(){ return "method test3"; }} After the initialization is completed, the structure of the urlMap corresponding to AbstractHandlerMethodMapping is as follows
The above is the main process of SpringMVC initialization
Search process
In order to understand the search process, with a question, the following controllers are available
@Controller@RequestMapping("/LookupTest")public class LookupTestController { @RequestMapping(value = "/test1", method = RequestMethod.GET) @ResponseBody public String test1(){ return "method test1"; } @RequestMapping(value = "/test1", headers = "Referer=https://www.baidu.com") @ResponseBody public String test2(){ return "method test2"; } @RequestMapping(value = "/test1", params = "id=1") @ResponseBody public String test3(){ return "method test3"; } @RequestMapping(value = "/*") @ResponseBody public String test4(){ return "method test4"; }} There are requests as follows
Which method will this request enter?
After receiving the request, the web container (Tomcat, jetty) is handed over to the DispatcherServlet for processing. The FrameworkServlet calls the corresponding request method (eg:get calls doGet), and then calls the processRequest method. After entering the processRequest method, after a series of processing, enter the doService method in line:936. Then enter the doDispatch method in Line856. Get the currently requested processor handler in line:896. Then enter the lookupHandlerMethod method of AbstractHandlerMethodMapping. The code is as follows
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); //Get directPathMatches based on uri List<T> directPathMatches = this.urlMap.get(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } //There is no direct matching RequestMappingInfo, iterates through all RequestMappingInfo if (matches.isEmpty()) { // No choice but to go through all mappings addMatchingMappings(this.handlerMethods.keySet(), matches, request); } //Get the HandlerMethod corresponding to the best match RequestMappingInfo if (!matches.isEmpty()) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); if (logger.isTraceEnabled()) { logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches); } //Check the ambiguity of the configuration again Match bestMatch = matches.get(0); if (matches.size() > 1) { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(handlerMethods.keySet(), lookupPath, request); }} Enter the lookupHandlerMethod method, where lookupPath="/LookupTest/test1", according to lookupPath, that is, the requested uri. Directly find the urlMap and get the RequestMappingInfo list that matches directly. Here we will match 3 RequestMappingInfos. as follows
Then enter the addMatchingMappings method
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { for (T mapping : mappings) { T match = getMatchingMapping(mapping, request); if (match != null) { matches.add(new Match(match, handlerMethods.get(mapping))); } }} The responsibility of this method is to traverse whether the current requested uri and the RequestMappingInfo in mappings can match, and if so, create the same RequestMappingInfo object. Then get the handlerMethod corresponding to RequestMappingInfo. Then create a Match object and add it to the matches list. After executing the addMatchingMappings method, return to lookupHandlerMethod. At this time, matches still have 3 RequestMappingInfo objects that can match. The next process is to sort the matchers list and then get the first element of the list as the best match. Returns the HandlerMethod of Match. Here we enter the compareTo method of RequestMappingInfo and take a look at the specific sorting logic. The code is as follows
public int compareTo(RequestMappingInfo other, HttpServletRequest request) { int result = patternsCondition.compareTo(other.getPatternsCondition(), request); if (result != 0) { return result; } result = paramsCondition.compareTo(other.getParamsCondition(), request); if (result != 0) { return result; } result = headersCondition.compareTo(other.getHeadersCondition(), request); if (result != 0) { return result; } result = consumerCondition.compareTo(other.getConsumesCondition(), request); if (result != 0) { return result; } result = producesCondition.compareTo(other.getProducesCondition(), request); if (result != 0) { return result; } result = methodsCondition.compareTo(other.getMethodsCondition(), request); if (result != 0) { return result; } result = customConditionHolder.compareTo(other.customConditionHolder, request); if (result != 0) { return result; } return 0;} As you can see in the code, the order of matches is value>params>headers>consumes>produces>methods>custom. Seeing this, the previous question can be easily answered. In the case of the same value, params can match first. So that request will enter the test3() method. Go back to lookupHandlerMethod and find HandlerMethod. SpringMVC will check the ambiguity of the configuration again here. The principle of the check here is to compare the two RequestMappingInfos with the highest matching degree. There may be questions here that when initializing SpringMVC, there is ambiguous configuration check, why is it checked again here? If there are two methods in the Controller now, the following configuration can be checked through initialization ambiguity.
@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.POST})@ResponseBodypublic String test5(){ return "method test5";}@RequestMapping(value = "/test5", method = {RequestMethod.GET, RequestMethod.DELETE})@ResponseBodypublic String test6(){ return "method test6";} Now execute the http://localhost:8080/SpringMVC-Demo/LookupTest/test5 request, and it will be thrown in the lookupHandlerMethod method
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/SpringMVC-Demo/LookupTest/test5' exception. The exception is thrown here because the compareTo method of RequestMethodsRequestCondition is the comparison method number. The code is as follows
public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) { return other.methods.size() - this.methods.size();}When does wildcard match? When the RequestMappingInfo that directly matches the value cannot be obtained through urlMap, wildcard matching will be used to enter the addMatchingMappings method.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.