当我们使用 Spring MVC开发Web应用页面时,一般会使用某种视图模版引擎技术,比如FreeMarkerVelocity之类的,然后还会写很多控制器方法用来处理某个请求,这些控制器方法基本的套路是:

  1. 写一个视图模板,配置到合适的位置;
  2. 控制器方法接收处理来自客户端的参数接收处理来自客户端的参数;
  3. 控制器方法调用服务端逻辑生成将要渲染到客户端的数据Model对象;
  4. 控制器方法返回所开发的视图模板的名称;

对于开发人员来讲,完成这几个步骤,针对某个请求的开发就可以认为是结束了,用户请求相应的url,然后就能看到相应的页面。这个过程听起来很简单,但是,控制器方法返回的数据Model对象和视图模板名称,又是怎样变成最终的视图返回给客户端的呢 ?

这篇文章我们就看一下这里背后发生的事情,以使用FreeMarker为例。

首先看看我们所开发的控制器方法 :

@Controller // 定义一个控制器类
public class SampleController {
    // 控制器方法定义
    // 当用户请求/weather时使用这个控制器方法处理该请求
    @RequestMapping("/weather")
    public String weather(Model model) {
    	// 1.参数接受和处理
    	 该方法仅用于展示今天的天气情况,所以不用接收和处理什么参数

    	// 2.业务处理逻辑
    	 下面的代码模拟的是调用服务层逻辑完成业务处理病更新模型对象model
    	 用于渲染响应页面给客户端
        model.addAttribute("today", LocalDate.now().toString());
        int max = new Random().nextInt(100);
        model.addAttribute("max", "" + max);
        int min = max - new Random().nextInt(30);
        model.addAttribute("min", "" + min);

	// 3.返回视图模板名称
	 上面的逻辑已经调用了服务层逻辑进行了相应的业务处理,
	 现在将要返回给用户的数据,也就是上面的model对象也已经生成,
	 现在返回用于渲染最终页面的视图名称
        return "weather";
    }
}

然后我们来看当这个控制器方法执行完成后发生的事情。

// Spring MVC 前端控制器Servlet DispatcherServlet 对一个请求处理的核心流程方法
	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 {
				// 是否是文件上传请求,如果是,会将参数request封装为另外一个
				// MultipartHttpServletRequest对象request记到processedRequest
				processedRequest = checkMultipart(request);
				// 如果发现processedRequest和参数request对象不是同一个对象,
				// 说明当前处于一个文件上传请求中
				multipartRequestParsed = (processedRequest != request);

				// 确定针对当前请求的 handler, 可以将一个 handler 理解成
				// 开发人员实现的某个控制器方法(@Controller+@RequestMapping注解的某个方法),
				// 该控制器方法会被封装成一个 HandlerExecutionChain 实例 (内含对应目标控制器
				// 方法 handler 和一组相应的 pre/post/complete HandlerInterceptor)
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
				// 针对当前请求,如果没有找到HandlerExecutionChain,
				// 则抛出异常NoHandlerFoundException或者向浏览器端返回响应 404 
					noHandlerFound(processedRequest, response);
					return;
				}
		
				// 确定针对当前请求的 HandlerAdapter , DispatcherServlet 并不会直接调用
				// HandlerExecutionChain 中的 handler ,而是通过一个叫做 HandlerAdapter 
				// 的一个中间层做隔离,这种隔离了 DispatcherServlet 主流程和具体的 handler 
				// 的实现细节,从而 DispatcherServlet 不需要关注业务处理细节,而只需要查找
				// 和调用相应的 HandlerAdapter 即可。
				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 (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				
				// handler 前置拦截方法调用 
				 在目标HandlerAdapter处理请求之前,调用HandlerExecutionChain中各个
				 HandlerInterceptor的preHandle		
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// handler 调用
				// 调用目标HandlerAdapter,最终是一个开发人员提供的Web Controller方法,
				// 返回结果是一个 ModelAndView 对象,View处理器会使用该对象渲染最终结果到 response
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				// 当 ModelAndView 对象存在但是没有指定view属性时,采用缺省view处理响应结果
				applyDefaultViewName(processedRequest, mv);
				//  handler 后置拦截方法调用
				 在目标HandlerAdapter处理请求之后,在view最终渲染之前,
				 调用HandlerExecutionChain中各个HandlerInterceptor的postHandle
				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);
			}
			// 处理分发给HandlerAdapter,也就是目标Controller方法的请求的处理结果,这里面主要包含两部分信息 : 
			// 1. ModelAndView view -- 控制器方法成功处理完请求后返回的对象
			// 2. Exception dispatchException -- 控制器方法处理遇到异常时的异常信息对象
			// 该方法主要做以下事情 : 
			// 1. 如果有异常,处理异常
			// 2. 如果没有异常,根据 ModelAndView ,结合view模板和model数据渲染响应结果
			// 3. 调用HandlerExecutionChain中各个HandlerInterceptor的方法 afterCompletion
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			// 在 view 被渲染之后调用相应的拦截器方法 afterCompletion
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			// 在 view 被渲染之后调用相应的拦截器方法 afterCompletion
			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);
				}
			}
		}
	}

在上面的代码中,mv = ha.handle(processedRequest, response, mappedHandler.getHandler())这一行真正执行了开发人员的控制器方法,继续上面的例子,也就是方法SampleController.weather,这行调用所返回的对象mv是类ModelAndView的一个实例,它持有控制器方法weather()所构造的model对象和视图名称weather

这一行代码调用的堆栈如下所示 :

springmvc ModelFactory 如何获取model_客户端

这一行代码执行结束时,要渲染的数据model和要渲染的视图都已经知道了,通过一个ModelAndView对象的方式又回到了DispatcherServlet方法的doDispatch方法。那么拿到这些信息,又怎样到渲染成最终的视图呢 ?这时候就要看DispatcherServlet的方法processDispatchResult了。

/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 * 处理 handler选择和调用的结果,该结果可能是一个ModelAndView对象,也可能是需要转换成一个
	 * ModelAndView对象的一个异常Exception。 
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

		boolean errorView = false;

		// 先检查刚才的handler调用过程中是否出现了异常,根据异常的情况试图获取相应的ModelAndView 对象,				
		// 然后下面的逻辑就可以对正常和异常情况统一到 ModelAndView 这一种方式上来了。
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != 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 (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			// 不管 handler 执行过程中遇到了异常,还是正常返回了ModelAndView,
			// 上面都将其统一按照处理ModelAndView的方式统一通过render()方法渲染
			// 完相应的视图了,现在调用相应的拦截器的 afterCompletion方法。 
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

上面的方法的主要逻辑是将异常Exception和正常ModelAndView的方式统一到处理ModelAndView的这一种方式然后进行渲染。而具体渲染的逻辑则在方法render中:

/**
	 * Render the given ModelAndView. 
	 * 渲染给定的ModelAndView到最终的视图,渲染的结果视图直接写入response。
	 * This is the last stage in handling a request. It may involve resolving the view by name.
	 * 这是一个请求处理的最后一个阶段。可能涉及到根据视图名称分析出相应的视图模板。
	 * @param mv the ModelAndView to render
	 * @param request current HTTP servlet request
	 * @param response current HTTP servlet response
	 * @throws ServletException if view is missing or cannot be resolved
	 * @throws Exception if there's a problem rendering the view
	 */
	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		// 国际化,本地化相关,不是本轮讨论的重点,这里先忽略。
		Locale locale = this.localeResolver.resolveLocale(request);
		response.setLocale(locale);

		View view;
		if (mv.isReference()) {
			// 如果mv的view属性是一个字符串String,则将该属性理解为是一个视图模板的名称,
			// 这里的代码需要根据改名称解析出相应的view对象。该任务由另外一个方法
			// resolveViewName() 完成。resolveViewName()方法签名虽然需要model作为参数,
			// 但实际上在解析相应的view时,model根本没有用,实质上只使用视图模板名称加上
			// locale信息作为参数就够解析目标视图对象了。
			// 对于FreeMarker,view对应o.s.w.servlet.view.freemarker.FreeMarkerView
			// 对于Velocity,view对应o.s.w.servlet.view.velocity.VelocityView
			view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// 如果mv的view属性不是一个字符串String,说明已经是一个view对象了
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
			// 进一步委托真正的视图渲染工作给对象 view
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
						getServletName() + "'", ex);
			}
			throw ex;
		}
	}