文章目录

  • 源码版本
  • 测试demo
  • 主要目标
  • 源码分析
  • doDispatch
  • 获取HandlerExecutionChain对象
  • RequestMappingHandlerMapping
  • AbstractHandlerMapping
  • AbstractHandlerMethodMapping lookupHandlerMethod方法
  • 方法执行
  • 总结



源码版本

spring-web:5.3.7

测试demo

@RestController
@RequestMapping("test/v1")
public class ZouController {


    @GetMapping("/test")
    public Student test(int a, int b) {
        System.out.println("请求进来了,开始休眠");
        System.out.println("调用成功");
        Student test = new Student(null, 18, LocalDateTime.now());
        test.setLocalDate(LocalDate.now());
        return test;
    }

    @PostMapping("/post/test")
    public String postTest() {
        return "Success";

    }

}

这里简单准备了一个Controller

然后使用postman调用接口localhost:8080/test/v1/test?a=1&b=2

主要目标

了解整个执行过程核心原理

源码分析

我们都知道Spring MVC的核心路口肯定是从DispatcherServletdoDispatch方法开始的,具体原因可以Google。这里不展开讲解,因为这不是本文重点

doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

这段方法很长,我们一步一步分析。首先看这个方法

mappedHandler = getHandler(processedRequest);

这里主要是获取一个HandlerExecutionChain 这个类里面有什么属性呢?我们debug看一下

springmvc 接口 传递文件流 springmvc调用接口_Spring

可以看到HandlerExecutionChain对象就已经通过url获取到了对应的controller和method。所以我们想要了解Spring MVC 是如何通过url找到对应的controller和method,就必须看看是如何获取到HandlerExecutionChain对象的

获取HandlerExecutionChain对象

我们进入getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

可以看到这里的HandlerExecutionChain 是通过List<HandlerMapping> handlerMappings遍历调用getHandler获取的,我们debug看看有多少个HandlerMapping

springmvc 接口 传递文件流 springmvc调用接口_Spring Boot_02


可以看到有四个HandlerMapping

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping
  • WelcomePageHandlerMapping:

都是处理请求的处理器,比如WelcomePageHandlerMapping就是专门用来处理默认请求/的,例如:http://localhost:8080/,就会返回规定路径下的index.html。其他处理器就不一一介绍了,感兴趣可以自己去研究,今天我们要研究的重点也是最常用的RequestMappingHandlerMapping处理器。

RequestMappingHandlerMapping会将 @Controller, @RequestMapping 注解的类方法注册为 handler,每个请求url可以对应一个 handler method 来处理

RequestMappingHandlerMapping

我们看看RequestMappingHandlerMapping的类UML图

springmvc 接口 传递文件流 springmvc调用接口_springmvc 接口 传递文件流_03


我们会发现RequestMappingHandlerMapping并没有自己实现HandlerMapping接口,是通过继承抽象类AbstractHandlerMapping间接实现了HandlerMapping,而方法getHandler也是直接调用AbstractHandlerMapping 中的getHandler方法,所以我们直接去分析AbstractHandlerMappinggetHandler方法即可

AbstractHandlerMapping

记住我们的主线目标如何获取到HandlerExecutionChain

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// Ensure presence of cached lookupPath for interceptors and others
		if (!ServletRequestPathUtils.hasCachedPath(request)) {
			initLookupPath(request);
		}

		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = getCorsConfiguration(handler, request);
			if (getCorsConfigurationSource() != null) {
				CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
				config = (globalConfig != null ? globalConfig.combine(config) : config);
			}
			if (config != null) {
				config.validateAllowCredentials();
			}
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

可以看到有个方法

Object handler = getHandlerInternal(request);

我们debug看看

springmvc 接口 传递文件流 springmvc调用接口_sed_04

可以看到getHandlerInternal方法就映射到了具体的controllermethod,所以我们在深入看看
getHandlerInternal方法

getHandlerInternal方法是个抽象方法,调用子类RequestMappingInfoHandlerMappinggetHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		try {
			return super.getHandlerInternal(request);
		}
		finally {
			ProducesRequestCondition.clearMediaTypesAttribute(request);
		}
	}

但是子类又调回了父类AbstractHandlerMethodMappinggetHandlerInternal方法,所以实际获取映射的方法还是父类的getHandlerInternal方法,我们看看父类的getHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = initLookupPath(request);
		this.mappingRegistry.acquireReadLock();
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

可以看到这里这个实际其实就是用到了模板设计模式

springmvc 接口 传递文件流 springmvc调用接口_springmvc 接口 传递文件流_05


通过debug我们可以很清晰的看到initLookupPath方法通过request获取到了我们请求的url:/test/v1/test,而实际通过url获取到HandlerMethod还得去看lookupHandlerMethod方法

AbstractHandlerMethodMapping lookupHandlerMethod方法
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);
		}
		if (matches.isEmpty()) {
			addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
		}
		if (!matches.isEmpty()) {
			Match bestMatch = matches.get(0);
			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)) {
					for (Match match : matches) {
						if (match.hasCorsConfig()) {
							return PREFLIGHT_AMBIGUOUS_MATCH;
						}
					}
				}
				else {
					Match secondBestMatch = matches.get(1);
					if (comparator.compare(bestMatch, secondBestMatch) == 0) {
						Method m1 = bestMatch.getHandlerMethod().getMethod();
						Method m2 = secondBestMatch.getHandlerMethod().getMethod();
						String uri = request.getRequestURI();
						throw new IllegalStateException(
								"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
					}
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.getHandlerMethod();
		}
		else {
			return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
		}
	}

这里代码有点长,我们挑重点看,首先是this.mappingRegistry这个对象,我们看看里面有什么

springmvc 接口 传递文件流 springmvc调用接口_Spring Boot_06

可以看到这里面有很多个RequestMappingInfo,看属性是不是很熟悉,就是我们写的controller和method,包括注解@RequestMapping上的url也在
所以如何通过url获取到对应的controller和method在这里就得到了答案,至于this.mappingRegistry中的RequestMappingInfo对象是如何初始化填充的,不在我们之类的重点,但是我们也可以很容易猜到无非就是在Spring容器启动的时候去扫描类上有@controller注解的类,然后解析方法转换为RequestMappingInfo对象

方法执行

回到我们的主线类DispatcherServletdoDispatch方法中。
我们会发现在通过url获取到类和方法后,我们执行方法是在这一行执行的

// 这里获取到的 HandlerAdapter 实际为 RequestMappingHandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

getHandlerAdapter 中默认有四个HandlerAdapter其中这里获取到的是RequestMappingHandlerAdapter

springmvc 接口 传递文件流 springmvc调用接口_Spring MVC_07

最后执行方法的逻辑也就是在RequestMappingHandlerAdapterhandleInternal方法

protected ModelAndView handleInternal(HttpServletRequest request,
			// 省略部分代码
			else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

其中稍微在核心一点的一个方法就是方法参数的绑定
这段代码的逻辑是在InvocableHandlerMethod中的getMethodArgumentValues方法

springmvc 接口 传递文件流 springmvc调用接口_Spring MVC_08

关于方法参数的绑定这里提一点,如果使用注解@RequestParam是直接可以获取到方法参数名字的,但是如果没有使用注解@RequestParam在jdk 1.8之前都只能通过字节码技术去获取方法参数名字。

整个MVC的核心流程到这里就结束了。

总结

其实整体流程看到是很长的,核心思路还是很清晰的。就是通过请求url找到对应的controller的method。然后执行对应的方法,然后返回数据。我们这次只是大致过了一遍整体流程,实际比较多的细节我们并没有重点分析,后续有机会在继续研究