SpringMVC之请求处理源码分析从service到doDispatch之doDispatch分析(二)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。在上一篇文章中我们分析了请求service方法到doDispatch的一部分,今天我们先简单的分析doDispatch这个方法,以后还会有很多围绕着这个方法进行分析的内容。

说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。

在上一篇文章中我们分析了请求service方法到doDispatch的一部分,今天我们先简单的分析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);
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == 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 = "GET".equals(method);
				if (isGet || "HEAD".equals(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) {
				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()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
下面我们分析一下这些代码的意思,先看这句代码:
processedRequest = checkMultipart(request);
从这个方法名字我们能看出来它是用来检查这个请求是不是文件上传的请求的。我们具体的看一下它是怎么判断是否是文件上传的。
	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			}
			else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
			}
			else {
				return this.multipartResolver.resolveMultipart(request);
			}
		}
		return request;
	}
 checkMultipart这个方法的源码如上所示。这里先是判断multipartResolver这个类是不是为空,我们之前分析过,multipartResolver是需要我们进行配置的,通常配置如下所示:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
如果没有配置MultipartResolver的话,则认为不是文件上传的请求,如果配置了MultipartResolver的话,调用isMultipart方法验证是不是文件上传的请求,isMultipart方法的内容如下:
	public boolean isMultipart(HttpServletRequest request) {
		return (request != null && ServletFileUpload.isMultipartContent(request));
	}
在这里我们看到了一个类是:ServletFileUpload,这个类就是大名鼎鼎的文件上传工具包:commons-fileupload中的类。SpringMVC对文件上传的处理是借助于commons-fileupload包来实现的。我们去isMultipartContent这个方法中看一下:
    public static final boolean isMultipartContent(
            HttpServletRequest request) {
        if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
            return false;
        }
        return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
    }
这里首先判断一下是不是POST请求,如果不是POST请求直接返回false,接着通过isMultipartContent方法来继续验证:
    public static final boolean isMultipartContent(RequestContext ctx) {
        String contentType = ctx.getContentType();
        if (contentType == null) {
            return false;
        }
        if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
            return true;
        }
        return false;
    }
这段代码的内容也比较简单,首先获取请求头中Context-Type的值,如果Context-Type等于null,则直接返回false,接着判断Context-Type是不是以multipart/开头。总结下来就是: 如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求。我们接着回到DIspatcherServlet中的checkMultipart这个方法中,继续看:
	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
			if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			
			}
			else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
				
			}
			else {
				return this.multipartResolver.resolveMultipart(request);
			}
		}
		
		return request;
	}
如果是文件上传请求,则继续判断这个请求是不是已经被转换为MultipartHttpServletRequest类型了。为什么会有这个判断呢?在Spring-Web这个jar中有一个过滤器: org.springframework.web.multipart.support.MultipartFilter,如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,并将请求转换为MultipartHttpServletRequest类型。这个过滤器中默认使用的MultipartResolver为StandardServletMultipartResolver。如果不是MultipartHttpServletRequest类型的话,则判断是不是出现异常了。如果上面这两步返回的都是false,则会执行这句:this.multipartResolver.resolveMultipart(request),将请求转换为MultipartHttpServletRequest类型。我们具体看一下:
	public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
		Assert.notNull(request, "Request must not be null");
		if (this.resolveLazily) {
			return new DefaultMultipartHttpServletRequest(request) {
				@Override
				protected void initializeMultipart() {
					MultipartParsingResult parsingResult = parseRequest(request);
					setMultipartFiles(parsingResult.getMultipartFiles());
					setMultipartParameters(parsingResult.getMultipartParameters());
					setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
				}
			};
		}
		else {
			MultipartParsingResult parsingResult = parseRequest(request);
			return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
					parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
		}
	}http://blog.csdn.net/zknxx/article/details/72836388#t5
在CommonsMultipartResolver中有一个属性叫resolveLazily,这个属性值代表是不是延迟解析文件上传,默认为false。最终返回的是一个DefaultMultipartHttpServletRequest的类。这里有一个重要的方法是:parseRequest,这个方法干的事是解析文件上传请求。它的底层是commons-fileupload那一套,不同的是Spring在获取FileItem之后,又进行了一下封装,封装为便于Spring框架整合。如果想简单的了解一下怎么解析文件上传的请求的话,可以参考这个简陋版的文件上传: http://blog.csdn.net/zknxx/article/details/60884573
下面我们接着看这一句话:
multipartRequestParsed = (processedRequest != request);
processedRequest是checkMultipart(request)这个方法返回的值,如果processedRequest和request不相等的话,则认为是文件上传的请求。
我们接着看这一句话:
mappedHandler = getHandler(processedRequest);
这段代码的意思是获取当前请求对应的处理类,在这个处理链中会包含对应的 拦截器的信息。HandlerExecutionChain这个类中包含变和不变量的两部分内容。我们具体的看一下getHandler这个方法的代码:
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}
这段代码的内容是循环handlerMappings,然后获取对应的执行链,只要找到一个对应的执行链就返回。关于handlerMappings,我们之前分析过参考这里:
http://blog.csdn.net/zknxx/article/details/72836388#t5。SpringMVC默认加载三个请求处理映射类:RequestMappingHandlerMapping、SimpleUrlHandlerMapping、和BeanNameUrlHandlerMapping。这三个类有一个共同的父类:AbstractHandlerMapping。在上面代码中hm.getHandler(request)这个getHandler方法在AbstractHandlerMapping中,它的子类都没有重写这个方法。我们去AbstractHandlerMapping这个类中看一下这个方法:
	@Override
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//这里会两个子类重写这个方法:AbstractHandlerMethodMapping和AbstractUrlHandlerMapping
		Object handler = getHandlerInternal(request);
		//如果没有找到的话,去默认的处理类
		if (handler == null) {
			handler = getDefaultHandler();
		}
		//如果没有默认的处理类,返回null
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		//包装为执行器链
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		//是不是cors请求,cors是跨域请求
		if (CorsUtils.isCorsRequest(request)) {
			CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}
		return executionChain;
	}
这里主要干了这几件事:获取Handler、创建执行器链,判断是不是cors跨域请求。获取handler的过程比较复杂,这个我们专开一篇文章来说明。下面我们来分析一下getHandlerExecutionChain这个方法,即创建执行器链的内容:
	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		//判断handler是不是执行器链,如果不是创建一个执行器链
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
		
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		//包装拦截器
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}
这个方法主要是创建执行器链,添加拦截器。拦截器这块,以后会开新的文章详细说明。
HandlerMapping的主要类图如下:

我们回到DIspatcherServlet的doDispatch方法中继续分析。
if (mappedHandler == null || mappedHandler.getHandler() == null) {
	noHandlerFound(processedRequest, response);
	return;
}
如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常。
我们继续往下分析:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
这句话是获取处理适配器,我们进入到getHandlerAdapter这个方法中看一下:
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
关于handlerAdapters看这里: http://blog.csdn.net/zknxx/article/details/72836388#t6。HandlerAdapter的类图如下:

SimpleControllerHandlerAdapter是用来适配SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping的映射的,也就是实现Controller接口的Handler。
AbstractHandlerMethodAdapter是用来适配RequestMappingHandlerMapping,也就是我们常用的RequestMapping注解。
HttpRequestHandlerAdapter是用来适配远程调用的。SimpleServletHandlerAdapter是用来适配Servlet实现类的。supports这个方法的实现都比较简单,我们这里就不细分析了。下面我们接着分析DispatcherServlet。
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
	long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
	if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
		return;
	}
}
这段代码的意思清晰,如果是GET请求,如果内容没有变化的话,则直接返回。HEAD请求这个很奇怪啊。。。
我们接着分析
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}
这段代码的内容是调用执行器链中的拦截器,就是循环装配好的执行器链,执行。代码如下
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
	for (int i = 0; i < interceptors.length; i++) {
		HandlerInterceptor interceptor = interceptors[i];
		if (!interceptor.preHandle(request, response, this.handler)) {
			triggerAfterCompletion(request, response, null);
			return false;
			}
		this.interceptorIndex = i;
		}
	}
	return true;
}
自定义拦截器的话,可以参考这里: http://blog.csdn.net/zknxx/article/details/72633444#t6
我们继续分析:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这句话就是执行handle了。关于这个可以先看一下这个: http://blog.csdn.net/zknxx/article/details/68952518
接续分析:
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
这两段代码的意思是:如果返回的ModelAndView不为null,并且没有设置view的话,这设置默认的view。处理拦截器的postHandle。
我们最后再看一下processDispatchResult这个方法:
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

		boolean errorView = false;
		//如果有异常,则处理异常,返回异常页面
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		//返回的ModelAndView不为null
		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			//解析页面
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}
		//调用处理拦截器的afterCompletion方法
		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}
在这个方法里干了这样的几件事:如果出现异常,返回异常页面。如果没有异常,ModelAndView不为null,则正常渲染页面,调用拦截器的afterCompletion方法。
我们对于doDispatch的分析,就先到这里。
相关文章
|
11月前
|
存储 前端开发 Java
SpringMVC中重定向请求时传输参数原理分析与实践
SpringMVC中重定向请求时传输参数原理分析与实践
186 2
SpringMVC中重定向请求时传输参数原理分析与实践
|
12月前
|
前端开发 应用服务中间件
SpringMVC 文件上传 消息 Required request part ‘file‘ is not present描述 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者
SpringMVC 文件上传 消息 Required request part ‘file‘ is not present描述 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者
1628 0
|
12月前
|
XML 前端开发 Java
源码分析系列教程(05) - 手写SpringMVC
源码分析系列教程(05) - 手写SpringMVC
36 0
|
6月前
|
JSON 数据格式
SpringMVC-接收请求中的json数据及日期类型参数传递
SpringMVC-接收请求中的json数据及日期类型参数传递
135 0
|
15天前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
29 1
Spring MVC——项目创建和建立请求连接
|
2天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
8 1
|
3天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
11 1
|
27天前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
45 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
4月前
|
缓存 前端开发 Java
SpringMVC原理(1)-文件上传请求
【7月更文挑战第2天】SpringMVC文件上传请求原理:文件上传请求的执行流程、文件上传的自动配置原理 涉及组件:MultiPartFile、MultipartResolver、MultipartHttpServlet