Spring MVC learning notes: Controller search (based on Spring4.0.3), mvcspring4.0.3

Source: Internet
Author: User

Spring MVC learning notes: Controller search (based on Spring4.0.3), mvcspring4.0.3

0 Abstract

This article briefly explains SpringMVC's processor ing process at the source code level, that is, the detailed process of searching for controllers.

1 SpringMVC request process


The process of finding the Controller in the corresponding steps 1 to 2


SpringMVC detailed operation Flowchart

2 SpringMVC initialization process

2.1 first understand two classes

1. RequestMappingInfo

Encapsulate RequestMapping annotations

Information about the HTTP Request Header

An instance corresponds to a RequestMapping Annotation

2. HandlerMethod

Encapsulate the request processing method of the Controller

Includes the bean object to which the method belongs, the method object corresponding to the method, and the parameters of the method.

Inheritance relationship of RequestMappingHandlerMapping

Spring MVC Initialization

First, execute afterPropertiesSet of RequestMappingHandlerMapping.

Then enter the afterPropertiesSet of AbstractHandlerMethodMapping.

This method will enter the initHandlerMethods of this class

Scans beans from applicationContext, and then searches for and registers the processor method from bean.

// Scan beans in the ApplicationContext, detect and register handler methods. protected void initHandlerMethods (){... // obtain all bean names in applicationContext String [] beanNames = (this. detectHandlerMethodsInAncestorContexts? BeanFactoryUtils. beanNamesForTypeIncludingAncestors (getApplicationContext (), Object. class): getApplicationContext (). getBeanNamesForType (Object. class); // traverse the beanName array for (String beanName: beanNames) {// isHandler checks whether the bean definition contains the Controller annotation or RequestMapping annotation if (isHandler (getApplicationContext () based on bean (). getType (beanName) {detectHandlerMethods (beanName) ;}} handlerMethodsInitialized (getHandlerMethods ());}

RequestMappingHandlerMapping # isHandler

The method is to determine whether the current bean definition carries the Controlller annotation or RequestMapping annotation.

If only RequestMapping takes effect? No!

In this case, the class will not be registered as Spring bean during Spring initialization, and the class will not be traversed during beanNames traversal. Therefore, you can change Controller to Compoent here, but this is generally not the case.

After the bean is identified as handler, the actual handler method (the specific request processing method defined in the Controller class) will be searched from the bean. 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) {// obtain the class Object Class of the current Controller bean <?> HandlerType = (handler instanceof String )? GetApplicationContext (). getType (String) handler): handler. getClass (); // avoid calling getMappingForMethod repeatedly to reconstruct the final Map <Method, T> mappings = new IdentityHashMap <Method, T> (); // same as above, it is also the final class Object of the Controller bean. <?> UserType = ClassUtils. getUserClass (handlerType); // get all handler methods of the current bean // define whether RequestMapping is included according to the method // If so, create the 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 ;}}); // traverses and registers all the handler methods of the current bean for (method Method: methods) {// register the handler method and enter registerHandlerMethod (handler, method, mappings. get (method ));}

The above code calls getMappingForMethod in two places.

Use the method and type-level RequestMapping annotation to create RequestMappingInfo

@ Override protected RequestMappingInfo getMappingForMethod (Method method, Class <?> HandlerType) {RequestMappingInfo = null; // obtain @ RequestMapping methodAnnotation = AnnotationUtils. findAnnotation (method, RequestMapping. class); if (methodAnnotation! = Null) {RequestCondition <?> MethodCondition = getCustomMethodCondition (method); info = createRequestMappingInfo (methodAnnotation, methodCondition); // obtain @ RequtestMapping annotation RequestMapping typeAnnotation = AnnotationUtils of the bean to which the method belongs. findAnnotation (handlerType, RequestMapping. class); if (typeAnnotation! = Null) {RequestCondition <?> TypeCondition = getCustomTypeCondition (handlerType); // merge two @ RequestMapping annotation info = createRequestMappingInfo (typeAnnotation, typeCondition). combine (info) ;}} return info ;}

This method is used to create the RequestMappingInfo object according to the handler method. First, determine whether the mehtodd contains the RequestMpping annotation. If yes, create the RequestMappingInfo object based on the content of the annotation. After creation, determine whether the bean of the current method also contains the RequestMapping annotation. If this annotation is contained, A RequestMappingInfo object is created based on the Annotation on this class. Then, the RequestMappingInfo object on the merged method is returned, and the merged object is returned. Now let's go back to the detectHandlerMethods method. The getMappingForMethod method is called in two places. I personally think it can be optimized here. When the method is set to handler in the first place, you can save the created RequestMappingInfo object and use it later. This reduces the process of creating a RequestMappingInfo object. Then enter the registerhandlermehtodd method, as shown below:

Protected void registerHandlerMethod (Object handler, Method method, T mapping) {// create HandlerMethod newHandlerMethod = createHandlerMethod (handler, method); HandlerMethod oldHandlerMethod = handlerMethods. get (mapping); // check whether the configuration is ambiguous 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 () {log Ger.info ("Mapped \" "+ mapping +" \ "onto" + newHandlerMethod);} // obtain the value of @ RequestMapping annotation, then add the value-> RequestMappingInfo ing record to Set <String> patterns = getMappingPathPatterns (mapping); for (String pattern: patterns) {if (! GetPathMatcher (). isPattern (pattern) {this. urlMap. add (pattern, mapping );}}}

Here, the T type is RequestMappingInfo. This object is the information related to 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 in Controlelr. First look at the first line of the method, and create the HandlerMethod object based on handler and mehthod. The second line uses handlerMethods map to obtain the HandlerMethod corresponding to the current mapping. Then determine whether the same RequestMapping configuration exists. The following configuration causes
Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map...
Exception

@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 ambiguous in the SpingMVC startup (initialization) phase. This is one of the checks for ambiguity (which will also be mentioned later in the runtime ). After confirming that the configuration is normal, the RequestMappingInfo and HandlerMethod objects will be added to handlerMethods (LinkedHashMap), and the value and ReuqestMappingInfo objects of the RequestMapping annotation will be added to urlMap.

RegisterHandlerMethod

This method has three main responsibilities:

1. Check whether the RequestMapping annotation configuration is ambiguous.

2. Build a map from RequestMappingInfo to HandlerMethod. This map is the member variable handlerMethods of AbstractHandlerMethodMapping. LinkedHashMap.

3. Construct the urlMap and MultiValueMap member variables of AbstractHandlerMethodMapping. This data structure can be understood as Map>. The String type key stores the value of the RequestMapping Annotation on the processing method. Is the specific uri

First, the following Controller is available:

@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 Initialization is complete, the urlMap structure of the corresponding AbstractHandlerMethodMapping is as follows:

The above is the main process of spring MVC initialization.

Search Process

In order to understand the search process, the following problems exist:

@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 the following requests:

Which method will this request enter?

The web Container (Tomcat and jetty) receives the request and submits it to DispatcherServlet for processing. 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, go to line: 936 to enter the doService method. Then enter the doDispatch method in Line856. Obtain the processor handler of the current request in line: 896. Then go to 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> (); // obtain the directly matched RequestMappingInfos List based on the uri <T> directPathMatches = this. urlMap. get (lookupPath); if (directPathMatches! = Null) {addMatchingMappings (directPathMatches, matches, request);} // No directly matched RequetMappingInfo exists, traversing all RequestMappingInfo if (matches. isEmpty () {// No choice but to go through all mappings addMatchingMappings (this. handlerMethods. keySet (), matches, request);} // obtain the HandlerMethod 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 configuration ambiguity 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 request uri. Directly search for urlMap and obtain the directly matched RequestMappingInfo list. Three RequestMappingInfo will be matched here. As follows:

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)));  } }}

This method is used to traverse whether the uri of the current request matches the RequestMappingInfo in mappings. If yes, create an identical RequestMappingInfo object. Then obtain the handlerMethod corresponding to RequestMappingInfo. Create a Match object and add it to the matches list. After the addMatchingMappings method is executed, return to lookupHandlerMethod. At this time, matches has three matching RequestMappingInfo objects. The next step is to sort the matchers list and obtain the first element of the list as the best match. Returns the HandlerMethod of Match. Here we will go to the compareTo method of RequestMappingInfo and 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 = consumesCondition.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;}

From the code, we can see that the matching sequence is value> params> headers> consumes> produces> methods> custom. Here, the previous question can be easily answered. When the values are the same, params can be matched first. Therefore, the request enters the test3 () method. Return to lookupHandlerMethod and find HandlerMethod. SpringMVC will check the configuration ambiguity again here. The check principle here is to compare the two RequestMappingInfo with the highest matching degree. There may be questions about how to check the configuration ambiguity during spring MVC initialization. Why should I check it again. If there are two methods in Controller, the following configuration can pass the initialization ambiguity check.

@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";}

When you execute the http: // localhost: 8080/SpringMVC-Demo/LookupTest/test5 request
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/SpringMVC-Demo/LookupTest/test5'Exception. This exception is thrown because the compareTo method of RequestMethodsRequestCondition is the number of compared methods. The Code is as follows:

public int compareTo(RequestMethodsRequestCondition other, HttpServletRequest request) { return other.methods.size() - this.methods.size();}

When can I match a wildcard? When urlMap cannot obtain RequestMappingInfo that directly matches the value, it will use wildcard matching to enter the addMatchingMappings method.

Summary

The above is all the content of this article. I hope the content of this article has some reference and learning value for everyone's learning or work. If you have any questions, please leave a message to us, thank you for your support.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.