Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of Controller Lookup of Spring MVC

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article will explain in detail the example analysis of Spring MVC's Controller search. The editor thinks it is very practical, so I share it for you as a reference. I hope you can get something after reading this article.

1 SpringMVC request process

The process for Controller to find steps 1 through 2 corresponding to the figure above

Detailed operation flow chart of SpringMVC

2 SpringMVC initialization process

2.1 know two classes first

1.RequestMappingInfo

Encapsulate RequestMapping comments

Contains information about HTTP request headers

An instance corresponds to a RequestMapping annotation

2.HandlerMethod

Encapsulating the request processing method of Controller

Contains the bean object to which the method belongs, the method object corresponding to the method, the parameters of the method, etc.

Inheritance relationship of RequestMappingHandlerMapping

During SpringMVC initialization

First execute the afterPropertiesSet of RequestMappingHandlerMapping

Then enter the afterPropertiesSet of AbstractHandlerMethodMapping

This method goes into the initHandlerMethods of the class

Responsible for scanning beans from applicationContext and then finding and registering processor methods from bean

/ / Scan beans in the ApplicationContext, detect and register handler methods.protected void initHandlerMethods () {. / / get all bean name String [] beanNames = (this.detectHandlerMethodsInAncestorContexts?) in applicationContext. BeanFactoryUtils.beanNamesForTypeIncludingAncestors (getApplicationContext (), Object.class): getApplicationContext (). GetBeanNamesForType (Object.class); / / traversing the beanName array for (String beanName: beanNames) {/ / isHandler determines whether there are Controller annotations or RequestMapping annotations in the bean definition based on bean (isHandler (getApplicationContext (). GetType (beanName)) {detectHandlerMethods (beanName);}} handlerMethodsInitialized (getHandlerMethods ());}

RequestMappingHandlerMapping#isHandler

The above method determines whether the current bean definition has Controlller annotations or RequestMapping annotations

What if only RequestMapping works? It won't!

In this case, Spring will not register the class as Spring bean when initializing, and will not traverse this class when traversing beanNames, so it is possible to replace Controller with Compoent here, but it is not common to do so.

When bean is determined to be handler, the specific handler method (that is, the request handling method defined under the Controller class) will be found from the bean. The search code is as follows

/ * Look for handler methods ina handler * @ param handler the bean name of a handler or a handler instance * / protected void detectHandlerMethods (final Object handler) {/ / get the class object Class handlerType = (handler instanceof String) of the current Controller bean? GetApplicationContext () .getType ((String) handler): handler.getClass (); / / avoid repeated calls to getMappingForMethod to rebuild the RequestMappingInfo instance final Map mappings = new IdentityHashMap (); / / as above, it is also the class object of the Controller bean final Class userType = ClassUtils.getUserClass (handlerType) / / get all the handler method of the current bean / / whether there is a RequestMapping according to the definition of method / / if so, create a RequestMappingInfo instance Set 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;}) / / traverse and register all handler method for of the current bean (Method method: methods) {/ / register handler method, enter the following method registerHandlerMethod (handler, method, mappings.get (method));}

GetMappingForMethod is called in two places in the above code

Create a RequestMappingInfo using method and type-level RequestMapping annotations

@ Override protected RequestMappingInfo getMappingForMethod (Method method, Class handlerType) {RequestMappingInfo info = null; / / get @ RequestMapping RequestMapping methodAnnotation = AnnotationUtils.findAnnotation (method, RequestMapping.class) of method; if (methodAnnotation! = null) {RequestCondition methodCondition = getCustomMethodCondition (method); info = createRequestMappingInfo (methodAnnotation, methodCondition); / / get @ RequtestMapping annotation 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 RequestMpping annotations. If so, create a RequestMappingInfo object directly based on the contents of the annotation. After creation, determine whether the bean to which the current method belongs also contains RequestMapping annotations. If this comment is included, a RequestMappingInfo object is created based on the annotation on the class. Then merge the RequestMappingInfo object on method, and finally return the merged object. Now looking back at the detectHandlerMethods method, there are two places where the getMappingForMethod method is called, and I think it can be optimized here. When you determine whether the method is handler in the first place, the created RequestMappingInfo object can be saved and used directly later, thus missing the process of creating a RequestMappingInfo object. Then go into 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 the configuration for ambiguity 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 annotated by @ RequestMapping, and then add the value- > RequestMappingInfo mapping record to urlMap Set patterns = getMappingPathPatterns (mapping); for (String pattern: patterns) {if (! getPathMatcher (). IsPattern (pattern)) {this.urlMap.add (pattern, mapping);}

The type of T here is RequestMappingInfo. This object is the information about the RequestMapping annotations of the methods under the specific Controller encapsulated. A RequestMapping annotation corresponds to a RequestMappingInfo object. HandlerMethod is similar to RequestMappingInfo in that it encapsulates the specific processing methods under Controlelr. First, take a look at the first line of the method and create a HandlerMethod object based on handler and mehthod. The second line gets the HandlerMethod corresponding to the current mapping through handlerMethods map. Then determine if the same RequestMapping configuration exists. The following configuration will cause it to be 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";}}

Checking the RequestMapping configuration for ambiguity during the SpingMVC startup (initialization) phase is one of the places to check for ambiguity (a place to check for ambiguity at run time will be mentioned later). After confirming that the configuration is normal, the RequestMappingInfo and HandlerMethod objects are added to the handlerMethods (LinkedHashMap), and the RequestMapping annotated value and ReuqestMappingInfo objects are then added to the urlMap.

Simple summary of registerHandlerMethod method

There are three main responsibilities of this method

1. Check the RequestMapping annotation configuration for ambiguity.

two。 Build a mapping map from RequestMappingInfo to HandlerMethod. The map is the member variable handlerMethods of AbstractHandlerMethodMapping. LinkedHashMap .

3. Build the member variable urlMap,MultiValueMap of AbstractHandlerMethodMapping. This data structure can be understood as Map >. The key of type String holds the value of the RequestMapping annotations on the processing method. It's a specific uri.

Let's start with 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 initialization, the structure of the urlMap of the corresponding AbstractHandlerMethodMapping is as follows

This is the main process of initialization of SpringMVC

Search process

To understand the lookup process, with a question, the following Controller is 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";}}

Have the following request

Which method will this request enter?

After the web container (Tomcat, jetty) receives the request, it is handed over 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, 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 matches = new ArrayList (); / / get direct matching RequestMappingInfos List directPathMatches = this.urlMap.get (lookupPath) based on uri; if (directPathMatches! = null) {addMatchingMappings (directPathMatches, matches, request);} / / there is no direct matching RequetMappingInfo, traversing all RequestMappingInfo if (matches.isEmpty ()) {/ / No choice but to go through all mappings addMatchingMappings (this.handlerMethods.keySet (), matches, request) } / / get the best matching HandlerMethod if (! matches.isEmpty ()) {Comparator 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 Match bestMatch = matches.get (0) again 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. Find the urlMap directly and get the RequestMappingInfo list that matches directly. There will be 3 RequestMappingInfo matches here. As follows

Then enter the addMatchingMappings method

Private void addMatchingMappings (Collection mappings, List 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 RequestMappingInfo in the currently requested uri and mappings can match, and if so, create the same RequestMappingInfo object. Then get the handlerMethod corresponding to RequestMappingInfo. Then create a Match object to add to the matches list. After executing the addMatchingMappings method, go back to lookupHandlerMethod. At this point, matches also has three RequestMappingInfo objects that can match. The next step is to sort the matchers list and get the first element of the list as the best match. Returns the HandlerMethod of Match. Go into 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 = 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;}

As you can see in the code, the order of matching is value > params > headers > consumes > produces > methods > custom. Seeing here, you can easily get the answer to the previous question. When value is the same, params is better able to match first. So that request goes into the test3 () method. Go back to lookupHandlerMethod and find HandlerMethod. SpringMVC also checks the configuration for ambiguity again, which is done by comparing the two RequestMappingInfo with the highest degree of match. It may be doubtful here that the initialization SpringMVC has ambiguity in checking the configuration, so why do you 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";}

Now that the http://localhost:8080/SpringMVC-Demo/LookupTest/test5 request is executed, 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 number of method of the comparison. The code is as follows

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

When do I match wildcards? Wildcard matching enters the addMatchingMappings method only when the RequestMappingInfo that directly matches the value cannot be obtained through urlMap.

This is the end of this article on "sample Analysis of Spring MVC's Controller search". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, please share it out for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report