请求路径匹配路由
在spring中,当一个请求过来的时候会做路径匹配,下面我们就从源码层面分析一下路径匹配。
示例:
1
|
@RequestMapping (value = "/user/{aid}/online/**" , method = RequestMethod.GET) |
我们一起看看这个方法是如何寻找的,和一些相应的工具类
入口
我的项目使用的是自动配置的RequestMappingHandlerMapping类,在getHandlerInternal()方法中:
1
|
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); |
上面这行是根据你的请求path和request去查找合适的method了。在项目启动的时候,Spring就把路径和对应的方法加载到了内存中。
进入上面方法
1
2
3
4
5
6
7
8
|
List<T> directPathMatches = this .mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null ) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings( this .mappingRegistry.getMappings().keySet(), matches, request); } |
可以看到如果根据lookupPath直接匹配上了,走第一个方法,如果没有,则需要根据规则匹配,走第二个方法。
mappingRegistry.getMappings().keySer()这个方法获取的类型为RequestMappingInfo类型,后面进入了RequestMappingInfo的getMatchingCondition()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { RequestMethodsRequestCondition methods = this .methodsCondition.getMatchingCondition(request); ParamsRequestCondition params = this .paramsCondition.getMatchingCondition(request); HeadersRequestCondition headers = this .headersCondition.getMatchingCondition(request); ConsumesRequestCondition consumes = this .consumesCondition.getMatchingCondition(request); ProducesRequestCondition produces = this .producesCondition.getMatchingCondition(request); if (methods == null || params == null || headers == null || consumes == null || produces == null ) { return null ; } PatternsRequestCondition patterns = this .patternsCondition.getMatchingCondition(request); if (patterns == null ) { return null ; } |
可以看到代码里面会查看各种条件是否匹配,包括,请求方法methods,参数params,请求头headers,还出入参类型等相关的consumers,produces等,最后一行就是我们要找的路径匹配patternsCondition.getMatchingCondition(request)。
这个方法会走到PatternRequestCondition的getMatchingPattern方法,然后调用如下方法,获取pattern:
1
2
3
|
if ( this .pathMatcher.match(pattern, lookupPath)) { return pattern; } |
上面这个pathMatcher的类型就是AntPathMatcher类,就是通过调用AntPathMatcher类的match方法,查看是否匹配,然后返回pattern。
SpringMVC 将请求找到匹配的处理
在SpringMVC的模式下,浏览器的一个请求是如何映射到指定的controller的呢?
初始化映射关系
在web服务器启动时,Spring容器中会保存一个map的数据结构,里边记录这controller和url请求中的对应关系。那么这个map中的数据是如何来的呢?
首先来看AbstractHandlerMethodMapping的initHandlerMethods方法(至于为什么直接找到这个方法,我也是网上搜索的,之前的调用链没去纠结)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug( "Looking for request mappings in application context: " + getApplicationContext()); } //获取Spring容器装配的所有bean的名称 String[] beanNames = ( this .detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object. class ) : getApplicationContext().getBeanNamesForType(Object. class )); //遍历 for (String beanName : beanNames) { //判断该bean是否有@controller或者@RequestMapping注解 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && isHandler(getApplicationContext().getType(beanName))){ //如果有上述注解,则需要保存对应关系 detectHandlerMethods(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } protected void detectHandlerMethods( final Object handler) { //获取传过来handler的类信息 Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances //初始化一个保存映射信息的map final Map<Method, T> mappings = new IdentityHashMap<Method, T>(); final Class<?> userType = ClassUtils.getUserClass(handlerType); Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { @Override public boolean matches(Method method) { //获取该类里所有方法的映射信息 T为RequestMappingInfo //mapping值的形式为{[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]} T mapping = getMappingForMethod(method, userType); if (mapping != null ) { //将信息加入map mappings.put(method, mapping); return true ; } else { return false ; } } }); for (Method method : methods) { //注册HandlerMethod,在里边进行一些重复验证等 registerHandlerMethod(handler, method, mappings.get(method)); } } |
上述方法中调用了一个比较重要的方法,getMappingForMethod,通过这个方法生成后续我们一直会用到的一个RequestMappingInfo对象。具体方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Override //该方法接收两个参数,一个是具体方法,一个是方法所在的类 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = null ; //找到方法的@RequestMapping注解 RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping. class ); if (methodAnnotation != null ) { //这个方法返回null RequestCondition<?> methodCondition = getCustomMethodCondition(method); //创建RequestMappingInfo对象 info = createRequestMappingInfo(methodAnnotation, methodCondition); //找到类的@RequestMapping注解 RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping. class ); if (typeAnnotation != null ) { //该方法也返回一个null RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType); //如果类和方法都有@RequestMapping注解,则进行combine操作 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); } } return info; } |
那么上述方法中调用的createRequestMappingInfo方法有事如何真正的创建出一个RequestMappingInfo对象的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) { //拿到@RequestMapping注解上的value值 String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value()); //创建一个RequestMappingInfo,参数为一堆condition,出了PatternsRequestCondition,其余全部使用@RequestMapping注解上的值 return new RequestMappingInfo( annotation.name(), new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), this .useSuffixPatternMatch, this .useTrailingSlashMatch, this .fileExtensions), new RequestMethodsRequestCondition(annotation.method()), new ParamsRequestCondition(annotation.params()), new HeadersRequestCondition(annotation.headers()), new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), new ProducesRequestCondition(annotation.produces(), annotation.headers(), this .contentNegotiationManager), customCondition); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
protected void registerHandlerMethod(Object handler, Method method, T mapping) { //handler此处为带有@controller或者@RequestMapping的类的名称 //初始化一个HandlerMethod,包含一些类的名称和方法等信息 HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); HandlerMethod oldHandlerMethod = this .handlerMethods.get(mapping); //判断是否有handlerMethods是否有重复数据,有则抛异常,没有则将其加入handlerMethods map中 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); } //将没有*号和问好的pattern加入到urlMap中 Set<String> patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this .urlMap.add(pattern, mapping); } } //维护一个nameMap,key为名字,格式为congroller类大写字母+#+方法名 //比如TestBank类的getBank方法,可以为TB#getBank if ( this .namingStrategy != null ) { String name = this .namingStrategy.getName(newHandlerMethod, mapping); updateNameMap(name, newHandlerMethod); } } |
由上述registerHandlerMethod方法我们可以看出,该方法共维护了三个map分别是:
-
handlermethods
: key为RequestMappingInfo value为HandlerMethod -
urlMap
: key为没有*和?的pattern(比如/test/test1)value为RequestMappingInfo -
nameMap
: key为名字,格式为congroller类大写字母+#+方法名,比如TestBank类的getBank方法,key为TB#getBank
上述三个map在后续匹配浏览器请求用哪个方法来处理时会重点用到。
从映射关系中寻找匹配方法
那么DispatcherServlet是如何处理一个请求的呢?
我们从DispatcherServlet的doService方法来看起,该方法中,最终会调用到AbstractHandlerMethodMapping类的lookupHandlerMethod方法来确定这个请求应该由哪个方法处理,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); //从urlMap中寻找能匹配的处理方法 List<T> directPathMatches = this .urlMap.get(lookupPath); //如果从urlMap中找到匹配的处理方法,则调用addMatchingMappings方法,将匹配的方法放入matches集合 if (directPathMatches != null ) { addMatchingMappings(directPathMatches, matches, request); } //如果urlMap中没有找到直接匹配的方法 if (matches.isEmpty()) { // No choice but to go through all mappings... addMatchingMappings( this .handlerMethods.keySet(), matches, request); } 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); } //如果成功匹配的方法只有一个,拿这个方法返回。如果匹配到多个方法,取最匹配的前两个进行比较。 //如果比较结果为0,则抛出没有找到唯一合适处理方法的异常 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 { //没有找到匹配的则返回null return handleNoMatch(handlerMethods.keySet(), lookupPath, request); } } |
从上述代码可以看出,程序会先从this.urlMap中寻找是否有匹配的方法,那么这个urlMap中的数据是从什么时候加载的呢?我们网上翻看registerHandlerMethod方法,在web服务器启动时,该方法初始化了urlMap中的数据。
通过上述分析,大致可以了解到Spring容器是如何维护url和方法之间的映射关系,以及当收到请求时又是如何将请求匹配到正确的方法的。
至于没有分析到的当类和方法都有@RequestMapping注解时触发的combine操作究竟做了什么,当找到多个匹配方法是又是如何通过比较器进行排序的,我们下次再分析。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/lz710117239/article/details/81226919