【MVC】SpringMVC源码分析之请求流程

        上篇文章写了SpringMVC启动时的加载源码,这篇文章我会介绍一下SpringMVC的请求过程,从前端发送请求到后端相应、处理、转发、返回、渲染的全流程。

一、SpringMVC的请求转换

        我们知道一个web服务一定符合标准的servlet规范,前端发送请求时必定也是交给servlet处理的,要想知道servlet如何处理请求的,我们可以从servlet接口入手。

servlet接口fangfa

由图上可知,servlet接口中一共有五个方法,根据翻译即可大致知道方法的功能:

  • init:servlet初始化方法;
  • getServletConfig:获取servlet的配置;
  • service:处理请求;
  • getServletInfo:获取servlet的信息;
  • destroy:servlet销毁方法。

所以在servlet中必定是由service方法去处理的来自前端的请求的,我们再来看下谁是真正实现这个方法的。

servlet继承关系图

从继承关系图上我们可以知道,第一个实现的是GenericServlet,但是这个方法是抽象的,GenericServlet并没有真正实现。

public abstract void service(ServletRequest req, ServletResponse res)
	throws ServletException, IOException;

 我们继续往下找,下一个实现是HttpServlet

	public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
	protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

我们可以看到,HttpServlet中有两个service实现方法,看入参和继承关系我们应该知道servlet的request类型是ServletRequest(response一样),第一个service会先将属于是servlet的ServletRequest转换成HttpServletRequest,然后再调用第二个service方法。

        第二个service方法的作用是什么呢?我们根据源码可以看出,第二个service的主要目的是拿到request的请求方式,根据不同的请求方式调用不同的处理方法,我们这次就以get方法为例,探究SpringMVC是如何处理来自前端的请求的。

二、SpringMVC请求的入口

        上面我们说了servlet会在HttpServlet中将ServletRequest转换成HttpServletRequest,并且根据不同的请求方式找到对应的处理方法,这个是servlet提供的功能,那么整个SpringMVC的处理入口在哪呢?

        通过servlet的继承关系图我们可以知道,诸如doGet这些方法最早是HttpServlet定义的,但是FrameworkServlet重写了他的方法,而且由于FrameworkServlet的doGet方法是有final修饰,因此get请求的最终入口就走到了FrameworkServlet的doGet方法去了,它的源码如下:

	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response); 
	}

 我们继续点进去看processRequest里面具体做了哪些东西:

	//	重点关注:doService
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 获取系统当前时间戳
		long startTime = System.currentTimeMillis();
		// 申明一个异常顶层对象Throwable
		Throwable failureCause = null;
		// 本地化、国际化处理
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		// 构建一个本地国际化上下文,SimpleLocaleContex
		LocaleContext localeContext = buildLocaleContext(request);
		// 获取当前绑定到线程的RequestAttributes
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		// 构建ServletRequestAttributes
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		// 将localeContext和requestAttributes放入当前线程中,
		// request作用域每个请求线程都不一样,参数,attribute需要上下文直接传递
		// (回顾:request,session,application)
		initContextHolders(request, localeContext, requestAttributes);

		try {
			//前面都是一堆准备工作,重点在这里!跳到DispatcherServlet 类中(子类重写) 【关键点!】
			doService(request, response);
		} catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		} catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		} finally {
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				} else {
					if (asyncManager.isConcurrentHandlingStarted()) {
						logger.debug("Leaving response open for concurrent processing");
					} else {
						this.logger.debug("Successfully completed request");
					}
				}
			}

			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

 可以看到processRequest主要做了下面的事情;

  1. 记录请求时间;
  2. 处理本地化和国际化问题;
  3. 构造localeContext和requestAttributes,设置当前request的生命周期;
  4. 最后调用doService()方法,这个动作最为重要,是当前方法的核心功能。

执行doService()方法的时候我们可以看到,实际上调用的是DispatcherServlet的doService(),这样,请求就交给了DispatcherServlet进行处理了,那么DispatcherServlet的doService()方法里又做了些什么东西呢?

	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects
		// 扔一堆东西进request.
		// web上下文,可以在后面的请求链路上通过request.getAttribute取出来使用
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 本地化处理器
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);  // 主题处理器,2003年的产物
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 主题资源文件

		if (this.flashMapManager != null) {
			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);
		}

		try {
			//重点关注,核心
			doDispatch(request, response);
		} finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

我们可以看到DispatcherServlet的doService()方法又set了一些参数,但是里面有一个核心的方法doDispatch(),这个是Spring MVC的核心代码,Spring MVC的所有步骤都在这个方法,所以我们要重点关注这个方法。

    //	Spring MVC的最核心代码
	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);

//				根据当前的请求去拿一个Handler.查找处理器映射器,进入!!!!!!!!!!!
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					// 如果当前请求没有handler进行处理,那么就抛出异常
					noHandlerFound(processedRequest, response);
					return;
				}

				// handler适配器,找到:RequestMappingHandlerAdapter ,可以通过debug变量查看类型
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// get方法
				String method = request.getMethod();
				//get方法为true
				boolean isGet = "GET".equals(method);
				//method为get
				if (isGet || "HEAD".equals(method)) { // 处理last-modified , 浏览器缓存时间
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				//循环调拦截器对应的 pre 方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {   // ===> applyPreHandle
					return;
				}

				// 让handlerAdapter去执行业务控制器方法,  真正进入controller的入口!
				// com.spring.mvc.test.MvcController.getModeAndView
				// 让它来返回modelAndView对象! debug仔细查看 mv 变量的值
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  //  ===>  controller入口,走起~

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// 设置默认视图,当返回了mav,但是又没设置具体view的时候
				applyDefaultViewName(processedRequest, mv);  
				mappedHandler.applyPostHandle(processedRequest, response, mv);  // ===> 拦截器后置调用 postHandle
			} 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);
			}
			//1、请求视图解析器,解析成view 
			//2、执行页面渲染(jsp)
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);  // ===> go!
		} 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);
				}
			}
		}
	}

至此,Spring MVC的全部请求流程源码就跟踪完了,具体更加详细的实现可以自己点进去看看。 

三、SpringMVC根据url获取到对应的controller流程

我们根据上面SpringMVC地方核心源码可以知道,url获取到对应的controller方法是在doDispatch()中,其入口如下:

//根据当前的请求去拿一个Handler.查找处理器映射器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
    // 如果当前请求没有handler进行处理,那么就抛出异常
    noHandlerFound(processedRequest, response);
    return;
}

那么这个方法里面又做了哪些东西呢,我们可以进去看一下。

    @Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//不止一个,比如BeanNameHandlerMapping、SimpleUrlHandlerMapping,
		// 还有我们需要的RequestHandlerMapping
		if (this.handlerMappings != null) {
			for (HandlerMapping hm : this.handlerMappings) {
				if (logger.isTraceEnabled()) {
					logger.trace(
							"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
				}
				//这个就是执行器链,里面包含了需要处理这个请求的controller(handler),以及所有的拦截器(interceptor),即根据url取到controller的过程
				HandlerExecutionChain handler = hm.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

里面都是一些校验,我们再点进去看 getHandler()方法,其实现过程在org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler下,源码如下:

    @Override
	@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);  // ===>  找到controller和方法的地方!
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {  // 如果是字符串,从factory取出对应的bean来
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		//所谓的handler链就是这里!其实就是包装一下,加上拦截器列表,详情看 HandlerExecutionChain 的源码结构!
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);  //封装成chain对象
		if (CorsUtils.isCorsRequest(request)) { //如果是跨域请求(从header里加了Origin的话)
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config); //就给它加一个拦截器
		}
		return executionChain; // handler链返回给 DS
	}

可以看到,根据url获取controller的方法主要是在第一步getHandlerInternal(request)中,后面都是对找到的controller进行的一些处理,而getHandlerInternal的实现是在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

    @Override
	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);  // 请求的url值
		if (logger.isDebugEnabled()) {
			logger.debug("Looking up handler method for path " + lookupPath);
		}
		this.mappingRegistry.acquireReadLock(); //加一把读锁,防止后期有修改的线程造成数据不一致!
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);  // ===> 藏在这里!
			if (logger.isDebugEnabled()) {
				if (handlerMethod != null) {
					logger.debug("Returning handler method [" + handlerMethod + "]");
				}
				else {
					logger.debug("Did not find handler method for [" + lookupPath + "]");
				}
			}
			//return之前处理一下。如果是string的话,找到对应的bean返回去
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); // ===>
		}
		finally {
			this.mappingRegistry.releaseReadLock(); //释放掉!
		}
	}
    @Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		//定义一个集合,存储所有匹配上的方法
		List<Match> matches = new ArrayList<>();
		// mappingRegistry , 映射关系 component-scan扫进去的!debug一下,留心mappingRegistry里面的 mappingLookUp……
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {  // directPathMatches,所有匹配上的,可能有多个符合条件的
			addMatchingMappings(directPathMatches, matches, request);  // 把他们整理一下,塞到matches这个list里
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			Collections.sort(matches, comparator); //根据定义的比较器,对多个匹配的method排序
			if (logger.isTraceEnabled()) {
				logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
						lookupPath + "] : " + matches);
			}
			Match bestMatch = matches.get(0); // 取匹配优先级最高的那个
			if (matches.size() > 1) {
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {  // 如果第1 第 2 两个匹配优先级相同
					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); // ===> 没干啥事,映射关系在request里设置了个attribute
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

至此,SpringMVC就完成了根据url查找对应的处理器的过程。

其他的比如SpringMVC是如何完成处理过程以及视图渲染,文件上传等功能的,大家可以根据Spring源码参考查看对应的解决源码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值