Loading

SpringMVC请求映射原理

当我们每次发送请求时,系统是如何找到对应的方法来处理请求的呢?为了解决这个问题,我们查看SpringMVC的底层源代码

环境:SpringBoot 2.4.2

1. DispatcherServlet

SpringBoot底层还是使用的SpringMVC,所以请求过来时,都会到达DispatcherServlet,而DispatcherServlet继承于FrameworkServletFrameworkServlet继承于HttpServletBeanHttpServletBean继承于HttpServlet,所以本质上DispatcherServlet是一个Servlet。那么,在这些类中就要实现doGet()或者doPost()方法。我们看到,在HttpServletBean这个类中并没有实现doGet后者doPost方法,那么我们查看FrameworkServlet类代码

可以看到四个方法都覆盖了HttpServlet中对应的方法,且实现都是processRequest()方法

在这个方法中,关键是调用了doService()方法,我们查看这个方法

发现这是一个抽象方法,需要其子类去实现。所以,我们查看FrameworkServlet的子类DispatcherServlet中的doService()方法。在这个方法中,我们省略其余代码,最关键的部分为

关键是调用了doDispatch()方法,所以,对于每个请求进来,都会调用org.springframework.web.servlet.DispatcherServletdoDispatch()这个方法来处理请求

2. doDispatch()方法

在doDispatch方法处加上断点,开启debug

发送一个原生的get请求,在idea中可以看到发送请求的路径为/test

当我们单步调试到mappedHandler = this.getHandler(processedRequest);这一行后,可以看到,系统已经找到了对应的处理方法

也就是MyController中的test1方法。所以getHandler()方法就是找到请求对应处理方法的关键,

3. HandlerMapping

我们在mappedHandler = this.getHandler(processedRequest);这一行加上断点,重新启动debug,重新发送这个get请求,调试进入getHander()方法

在方法的首行,获取了handlerMappings,这是处理器映射,SpringMVC根据处理器映射里面的映射规则找到对应的处理方法。默认有5个HandlerMapping

处理器映射内部保存着相应的映射规则,我们可以查看熟悉的WelcomePageHandlerMapping,在里面可以找到pathMatcher属性,这是匹配的请求路径

还可以找到对应的跳转,会转发到index页面,也就是首页

所以这就是欢迎页的处理器映射

对于普通的请求,我们需要注意的是RequestMappingHandlerMapping,这其中保存了所有@RequestMapping注解和handler的映射规则,在SpringBoot启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在这个映射处理器中。可以将这个HandlerMapping理解为一个Map,其中key为请求路径,value为handler的处理方法

所以在getHandler()方法中,存在一个for循环,为了找到能处理对应请求的HandlerMapping

进入循环中,当获取到RequestMappingHandlerMapping时,我们查看这个类中的属性值

可以看到所有标注了@RequestMapping注解的请求映射规则都已经存放在了这个RequestMappingHandlerMapping类中

同时,已经获取到了相应的handler,也就是对应的Controller处理方法

4. HandlerMapping的getHandler()方法

读到这里,对于SpringMVC的请求映射原理已经大概熟悉了,接下来,我们分析一下HandlerMapping的getHandler方法,理解是如何根据请求路径从HandlerMapping中找到相应的handler方法的

我们从断点处不断进入,直到进入org.springframework.web.servlet.handler.AbstractHandlerMethodMappinglookupHandlerMethod()方法,我们可以重点分析一下这段代码

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();  // 存储匹配到的结果
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);  // 根据请求路径找到直接匹配的结果(只根据路径名匹配)
    if (directPathMatches != null) {
	addMatchingMappings(directPathMatches, matches, request);  // 从直接匹配的结果中寻找并将最终结果存入matches中(根据请求方法匹配)
    }
    if (matches.isEmpty()) {
	addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
	Match bestMatch = matches.get(0);  // 获取结果集中的第1个值作为最佳匹配
	if (matches.size() > 1) {  // 如果找到了多个匹配的值
	    Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
	    bestMatch = matches.get(0);
	    if (logger.isTraceEnabled()) {
		logger.trace(matches.size() + " matching mappings: " + matches);
	    }
	    if (CorsUtils.isPreFlightRequest(request)) {
		return PREFLIGHT_AMBIGUOUS_MATCH;
       	    }
	    Match secondBestMatch = matches.get(1);  // 获得第二最佳匹配
	    if (comparator.compare(bestMatch, secondBestMatch) == 0) {  // 如果最佳匹配和第二最佳匹配相同,则抛出异常,说明有两个相同的匹配路径
		Method m1 = bestMatch.handlerMethod.getMethod();
		Method m2 = secondBestMatch.handlerMethod.getMethod();
		String uri = request.getRequestURI();
		throw new IllegalStateException(
		    "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
	    }
	}
	request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
	handleMatch(bestMatch.mapping, lookupPath, request);
	return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    }
}

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, this.mappingRegistry.getRegistrations().get(mapping).getHandlerMethod()));
	}
    }
}

5. 总结

所有的请求映射都保存在HandlerMapping中,在项目启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在HandlerMapping映射处理器中

SpringBoot为我们默认定义并配置了5个HandlerMapping,当一个请求进来时,系统会遍历这5个HandlerMapping,找到匹配的handler处理方法

我们也可以将自定义的HandlerMapping放入容器中,使用自定义的映射处理器

posted @ 2021-02-03 18:16  Kinopio  阅读(643)  评论(0编辑  收藏  举报