Spring中请求URL找到相应Controller的过程

我把Spring请求URL找到相应Controller的过程调试了一遍,在此记录该过程,属于个人记录,过程中存在不细致甚至错误的地方,还请见谅。

代码版本为spring-webmvc-5.2.5.RELEASE-sources.jar

分析

分析入口:org.springframework.web.servlet.DispatcherServlet#doDispatch

doDispatch

跟进getHandler

getHandler

再跟进mapping.getHandler,它是一个interface,由AbstractHandlerMapping实现此接口

mapping.handler

点击148行右侧的绿色图标,跳转到AbstractHandlerMapping#getHandler

AbstractHandlerMapping.getHandler

此处getHandlerIngernal是一个抽象方法,在AbstractHandlerMethodMapping#getHandlerInternal中实现了该抽象方法

getHandlerInternal

getLookupPathForRequest

重点在此,getUrlPathHelper().getLookupPathForRequest(request)中从请求中获取代码中对应的path,可以在此下断点进行调试,详细了解Spring是怎么获取请求路径的。

传入http://localhost/shiro/admin;////..;/manage/page/index进行调试(如果此时传入正常的URL,则getPathWithinServletMapping方法会返回空字符串,再到else中的getPathWithinAPPlication方法)

getLookupPathForRequest

跟进getPathWithinServletMapping

getPathWithinServletMapping

getPathWithinApplication

首先来看getPathWithinApplication

getPathWithinApplication

其中getContextPath为获取Context path,不做重点分析,重点来看getRequestUri

getRequestUri

request.getRequestURI()得到的是/shiro/admin;////..;/manage/page/index,继续跟进decodeAndCleanUriString方法

decodeAndCleanUriString

decodeAndCleanUriString方法对uri进行了三种处理

(1) 移除两个/间,分号和分号后的内容,得到/shiro/admin////../manage/page/index

(例如传入/admin;random/page;jsessionid=1111,会得到/admin/page/

removeSemicolonContent

(2) URL解码

(3) 移除多余的/

getSanitizedPath

经过以上处理,最终得到的uri是/shiro/admin/../manage/page/index

回到getPathWithinApplication方法,其中getRemainingPath方法将移除requestUri中和contextPath中相同的字段(在此是/shiro),得到/admin/../manage/page/index

getPathWithinApplication2

getServletPath

也就是通过request.getServletPath()获取Servlet路径,得到/manage/page/index

getServletPath

getPathWithinApplication 2

经过以上代码,回到getPathWithinApplication。其中第218行sanitizedPathWithinApp变量,再次移除多余的/,uri在此无变化。

之后存在一个if判断,当sanitizedPathWithinApp不是servletPath的子集时,path等于getRemainingPath(pathWithinApp, servletPath, false)

getPathWithinServletMapping2

此时getRequestURI得到的/admin/../manage/page/indexgetServletPath获取到的/manage/page/index开头无法匹配,经过getRemainingPath得到的path为null

getPathWithinServletMapping3

经过以上代码处理,最终返回的是servletPath,也就是返回/manage/page/index

这里多说一下,通过getReqeustURIgetServletPath进行对比,当两者不同时,尝试getPathInfo。若PathInfo为空,最后返回SerletPath。通过这几个if判断,确保能正确获取请求路径(因为getReqeustURI得到的结果不可信,利用各种URL兼容性难以通过URI判断实际请求路径)。

lookupHandlerMethod

得到请求的资源路径之后,回到AbstractHandlerMethodMapping#getHandlerInternal,调用lookupHandlerMethod方法

getHandlerInternal2

看该方法的描述,将资源路径进行最合适的匹配,得到最合适的Controller方法。

Look up the best-matching handler method for the current request.
If multiple matches are found, the best match is selected.

lookupHandlerMethod

它定义了一个matchs的列表,然后内存变量mappingRegistry根据lookupPath获取所有的匹配对象。

如果存在这个匹配对象,那么回调用addMatchingMappings方法将匹配的对象映射到matches上。

如果未匹配到这个lookupPath,那么则会循环mappingRegistry中每个Mapping来匹配该路劲(因为RequestMapping中定义的路劲可以有通配符)。

mappingRegistry

至于if(!matches.isEmpty())内的代码,只是看看有没有一个请求映射到多个处理方法,如果存在并且优先级还一样,那么就会报错:Ambiguous handler methods mapped for HTTP path。

来看addMatchingMappings的代码

addMatchingMappings

注意到AbstractHandlerMethodMapping这个class是个泛型抽象类 getMatchingMapping是个抽象方法,返回值为定义的泛型(T),我们暂且把这个泛型理解成[映射信息]。

根据方法名称,T getMatchingMapping(T,reqeust),根据映射信息和请求获取是否匹配,如果匹配则返回映射信息。

RequestMappingInfoHandlerMapping这个class继承了这个抽象类,泛型类型为:RequestMappingInfo。看看它的方法实现

getMatchingCondition

如果其中methodsparamsheadersconsumesproducespatternscustom都匹配,那么可以说这个[映射信息]就匹配成功了。如果未匹配成功,则返回404。

  1. methods : 请求方法匹配(POST/GET/DELETE/PUT等等),对应RequestMapping注解的method

  2. params : 请求参数匹配 例如myParam=myValue,对应RequestMapping注解的params

  3. headers : 请求头信息(例如:User-Agent: Mozilla/5.0),对应RequestMapping的headers

  4. consumes : 提交内容类型(例如:application/json, text/html;),对应RequestMapping的consumes

  5. produces : 返回的内容类型(例如:application/json),对应RequestMapping的consumes

  6. patterns : URL格式(例如:test/testDo),对应RequestMapping的value

  7. 自定义条件

根据以上匹配得到的信息,就能找到对应的Controller。

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

HandlerExecutionChain

总结

Spring中获取请求路径的过程,在org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping中,其流程大概如下图所示。

diagram

其中比较关键的点是getPathWithinApplication方法,其中对RequestURI的处理有:删除冒号及冒号后的内容,URL解码,去除重复斜杠/(注意是先删除冒号,再URL解码)。

参考

https://blog.csdn.net/hl233211/article/details/77450697