Spring源码分析(五)SpringMVC是怎样处理请求的?

前言

上一节我们看到了SpringMVC在初始化的时候做了大量的准备工作,本章节就重点跟踪下SpringMVC的实际调用过程。

1、DispatcherServlet

记不记得,在大学期间或者刚接触Java WEB开发的时候,前后端交互往往需要一个Servlet来接收请求,并返回信息。时至今日,Servlet仍不过时。

如果是一个SpringMVC的项目,在WEB.XML里面需要配置一个DispatcherServlet,它本质就是个Servlet。或许我们还有印象,在请求到达的时候,就会调用到Servlet的service方法。那么,说回到SpringMVC,就是FrameworkServlet类的service方法。

	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String method = request.getMethod();
		if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}
复制代码

经过一系列的判断匹配,最后调用到DispatcherServlet类的doService方法。可以看到的是,doService方法向request里面添加了很多默认的属性,如果需要,我们就可以拿到。并在最后调用了实际的处理方法,doDispatch()。

protected void doService(HttpServletRequest request, HttpServletResponse response) {
	// Make framework objects available to handlers and view objects.
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

	FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
	if (inputFlashMap != null) {
		request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
	}
	request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
	request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
	
	doDispatch(request, response);
}
复制代码

2、doDispatch

实际处理的时候,大致分为几个步骤。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
	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);
			//第一步 确定请求的处理器
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				//URL没有找到匹配项,返回404
				noHandlerFound(processedRequest, response);
				return;
			}
			// 第二步 确定请求的处理适配器
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			//第三步 调用拦截器 (方法调用前执行)
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}
			// 第四步  方法调用 
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			//第五步 调用拦截器  (方法调用后执行)
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		//第六步 处理方法返回 (返回页面或者属性)
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		//第七步 调用拦截器(请求完成后执行)
		mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
	}
}
复制代码

不着急,下面我们一个一个的来看。

2.1、 获取处理器 getHandler

获取处理器,就是根据请求的uri,匹配到Method方法。具体做法,我们在上一节Spring源码分析(四)SpringMVC初始化做了预估,实际上Spring也确实是这样做的。 根据uri获取handlerMapping,再以handlerMapping为key,从handlerMethods容器中拿到相应的HandlerMethod对象。不过,它把HandlerMethod对象做了两次封装,源码来看。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	List matches = new ArrayList();
	//lookupPath就是uri,拿到mapping。以/user/index为例,mapping如下:
	//[{[/user/index],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}]
	List directPathMatches = this.urlMap.get(lookupPath);
	if (directPathMatches != null) {
		//从handlerMethods容器中拿到Method对象,封装成Match对象
		//Match对象其实就两个属性 mapping和handlerMethod对象
		addMatchingMappings(directPathMatches, matches, request);
	}
	HandlerMethod handler = matches.get(0).handlerMethod;

	//最后返回的是HandlerExecutionChain对象
	//里面包含两个属性。handler>就是HandlerMethod对象
	//还有个拦截器列表。interceptorList,在第三步调用拦截器就是循环这个List来调用
	return getHandlerExecutionChain(handler, request);
}

复制代码

2.2、获取适配器 getHandlerAdapter

这个比较简单。在初始化的时候,注册了几个适配器。判断上一步拿到的Handler是什么类型,就返回什么适配器,这里返回的是RequestMappingHandlerAdapter实例。

2.3、调用拦截器 (方法调用前执行)

拦截器是链式调用,因为可能会有多个拦截器。拦截器的第一个方法,也是预处理方法preHandle是有返回值的。如果返回false,整个请求就到此结束。在业务里,我们可以让这个拦截器做一些校验工作,不符合预期就返回false。需要注意的是interceptorIndex 这个变量,它记录当前调用到了第几个拦截器。为什么要记录这个呢?等看到后置拦截的时候我们就知道了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
	if (getInterceptors() != null) {
		for (int i = 0; i < getInterceptors().length; i++) {
			HandlerInterceptor interceptor = getInterceptors()[i];
			//preHandle方法如果返回false,接着就调用拦截器的后置方法。
			//因为整个请求已经结束了
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}
复制代码

2.4、方法调用

方法调用就是解析请求的参数,拿到Method对象直接invoke即可。调用之后根据返回值渲染视图。

2.4.1、参数解析

我们在上一章节已经看到,SpringMVC初始化的时候,加载注册了很多解析器的类,其中有参数解析器和返回值类型解析器,如今就派上用场。

一个HTTP请求的方法,不管有多少参数,有多少种参数的类型。最终,它们都是从哪来呢?没错,就是Request。一切从Request而来。参数解析的方法最终返回的是一个Object[] args,就是参数值的数组。

  • 拿到方法上的参数列表,循环此列表
  • 调用解析器来解析参数。它们的顶层接口是HandlerMethodArgumentResolver。它只有两个方法,supportsParameter、resolveArgument。解析过程全靠这两个方法,supports用来判断是否应该由此类解析,resolve才是真正解析。
  • 返回参数值
private Object[] getMethodArgumentValues(NativeWebRequest request, 
      ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
	//获取方法参数列表
	MethodParameter[] parameters = getMethodParameters();
	//args就是解析后的参数值的数组
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		if (args[i] != null) {
			continue;
		}
		//判断parameter是否能被某一个解析器所解析
		if (this.argumentResolvers.supportsParameter(parameter)) {
			try {
				//拿到上一步的解析器,解析拿到返回值放入args
				args[i] = this.argumentResolvers.resolveArgument(
						parameter, mavContainer, request, this.dataBinderFactory);
				continue;
			}
		}
	}
	return args;
}
复制代码

下面我们看一下HttpServletRequest在解析器里面具体的实现。这个参数会调用到ServletRequestMethodArgumentResolver解析器,里面其实是一些if else。

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

	Class paramType = parameter.getParameterType();
	if (WebRequest.class.isAssignableFrom(paramType)) {
		return webRequest;
	}
	HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
	if (ServletRequest.class.isAssignableFrom(paramType) ) {
		Object nativeRequest = webRequest.getNativeRequest(paramType);
		return nativeRequest;
	}
	else if (HttpSession.class.isAssignableFrom(paramType)) {
		return request.getSession();
	}
	else if (HttpMethod.class.equals(paramType)) {
		return ((ServletWebRequest) webRequest).getHttpMethod();
	}
	else if (Principal.class.isAssignableFrom(paramType)) {
		return request.getUserPrincipal();
	}
	else if (Locale.class.equals(paramType)) {
		return RequestContextUtils.getLocale(request);
	}
	else if (InputStream.class.isAssignableFrom(paramType)) {
		return request.getInputStream();
	}
	else if (Reader.class.isAssignableFrom(paramType)) {
		return request.getReader();
	}
	//未完......
}
复制代码
2.4.2、invoke

上一步通过各种解析器之后,返回一个Object类型的参数数组。有了Method对象,参数,调用就变得简单了。

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	Object returnValue = doInvoke(args);
	if (logger.isTraceEnabled()) {
		logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
	}
	return returnValue;
}
复制代码
2.4.3、返回值的解析

invoke之后,方法返回Object 类型的returnValue。上面说了,解析器分为参数解析器和返回值解析器两种。So,返回值解析器就是在这里被调用。

它的解析和参数解析器套路基本一致,先判断是否该由此类解析,然后交由该类解析。记得刚工作的时候,只要是返回页面的,都是通过new ModelAndView().setName("xxx")来返回,后来发现直接返回视图名字的字符串也可以,大感惊奇。原来,SpringMVC是在这里处理的。 我们来看这个类ViewNameMethodReturnValueHandler的解析方法。

public void handleReturnValue(Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	//如果返回的值是字符串类型
	//比如 /index,setViewName的工作它来搞
	if (returnValue instanceof String) {
		String viewName = (String) returnValue;
		mavContainer.setViewName(viewName);
		if (isRedirectViewName(viewName)) {
			mavContainer.setRedirectModelScenario(true);
		}
	}
}
复制代码

SpringMVC比较重要的一点是支持restful,是通过ResponseBody注解。它又是怎么处理的呢?在注册HandlerAdapter的时候,默认给它添加了消息转换器。里面有7种类型,其中有一个MappingJacksonHttpMessageConverter就是专门负责ResponseBody注解的。

来到RequestResponseBodyMethodProcessor类,它来负责匹配解析ResponseBody注解。判断方法很简单

public boolean supportsReturnType(MethodParameter returnType) {
	return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), 
                            ResponseBody.class) != null ||
			returnType.getMethodAnnotation(ResponseBody.class) != null);
}
复制代码

再来看它的handle方法。

public void handleReturnValue(Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException {
	//这个属性很重要,在此设置为true,先记住,下面再看。
	mavContainer.setRequestHandled(true);
	//具体用消息转换器来写入,这里的消息转换器就是
	//MappingJacksonHttpMessageConverter
	writeWithMessageConverters(returnValue, returnType, webRequest);
}

复制代码
protected  void writeWithMessageConverters(T returnValue, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException {

	//...........省略大部分代码 
	//设置数据格式编码等 application/json;charset=UTF-8
	if (selectedMediaType != null) {
		selectedMediaType = selectedMediaType.removeQualityValue();
		//messageConverters就是包含MappingJacksonHttpMessageConverter在内的多种转换器
		for (HttpMessageConverter messageConverter : this.messageConverters) {
			//先判断是否可写 判断方式也很简单,就是看mediaType是否是application/json
			if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
				//拿到返回值
				returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
	(Class>) messageConverter.getClass(), inputMessage, outputMessage);
				if (returnValue != null) {
					//写的过程,先设置Response的头信息、编码,再调用jackson.databind包里的方法写入
				((HttpMessageConverter) messageConverter).write(returnValue,
                                     selectedMediaType, outputMessage);
					//写完之后刷新
					//outputMessage.getBody().flush();
				}
				return;
			}
		}
	}
}
复制代码
2.4.4、获取ModelAndView

方法调用完了,返回值也都解析了。视图需不需要返回,返回到哪里?ModelAndView来决定。

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
	
	//在解析ResponseBody的时候,我们说有个属性很重要。mavContainer.setRequestHandled(true);
	//在这里就用到了,说明不需要视图
	if (mavContainer.isRequestHandled()) {
		return null;
	}
	ModelMap model = mavContainer.getModel();
	ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
	if (!mavContainer.isViewReference()) {
		mav.setView((View) mavContainer.getView());
	}
	return mav;
}
复制代码

行文至此,关于方法调用算是都已经处理完毕了。我们可以看出来,其实调用很简单, 关键在于做好参数解析和返回值解析的工作。

2.5、调用拦截器 (方法调用后执行)

又到了一个拦截器的调用。这次它调用的是postHandle方法。

for (int i = getInterceptors().length - 1; i >= 0; i--) {
	HandlerInterceptor interceptor = getInterceptors()[i];
	interceptor.postHandle(request, response, this.handler, mv);
}

复制代码

2.6、处理结果

这里是真正响应请求的地方。先是判断ModelAndView是否为空,如果为空说明返回的不是视图,就没必要往下执行。

if (mv != null && !mv.wasCleared()) {
	render(mv, request, response);
	if (errorView) {
		WebUtils.clearErrorRequestAttributes(request);
	}
}
复制代码

重点在于render方法。它最终调用了renderMergedOutputModel方法,渲染输出。我们在配置文件中,都要配置一个视图解析器viewResolver,这里就是用到的它。解析出来完整的视图路径后,利用Servlet的RequestDispatcher直接做转发就完成了这一步的工作。

protected void renderMergedOutputModel(
			Map model, HttpServletRequest request, HttpServletResponse response) {
	//获取返回的路径 视图解析器配置的prefix加上返回的viewName,再加上后缀p:suffix
	String dispatcherPath = prepareForRendering(requestToExpose, response);
	//获取RequestDispatcher,
	RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
	//直接转发
	rd.forward(requestToExpose, response);
}

复制代码

2.7、调用拦截器 (视图渲染后执行)

在调用拦截器的预处理方法时,提到了一个变量:interceptorIndex 。在这里就能看到它的作用。拦截器可能是多个的,它是链式调用的过程。比如有5个拦截器。如果在第3个拦截器的preHandler方法返回了false,后两个拦截器的After不应该再被执行。所以在后置拦截方法是从interceptorIndex 开始的。

void triggerAfterCompletion(HttpServletRequest request, 
		HttpServletResponse response, Exception ex)throws Exception {
	for (int i = this.interceptorIndex; i >= 0; i--) {
		HandlerInterceptor interceptor = getInterceptors()[i];
		try {
			interceptor.afterCompletion(request, response, this.handler, ex);
		}
	}
}
复制代码

3、总结

以上就是SpringMVC处理一个请求的所有流程。DispatcherServlet其本质就是个Servlet,一切请求经过它来处理。它根据请求的uri找到对应的Method对象,然后从Request中拿到参数解析成我们想要的类型,调用具体方法。通过不同的返回值解析器来确定返回的数据的类型是什么,需不需要响应视图。然后调用RequestDispatcher 直接转发。最后通过HandlerInterceptor可以让我们有机会参与到SpringMVC处理环节中去,在具体方法执行的不同时机加入我们自定义的业务。

你可能感兴趣的:(Spring源码分析(五)SpringMVC是怎样处理请求的?)