Spring Framework源码(十三):SpringMVC之从ModelMap到页面渲染

    SpringMVC在调用了Controller的方法后会返回ModelAndView对象,这个对象会被传回DispatcherServlet的doDispatch方法中。接下来再调用以下的方法渲染页面:

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
                //view是否被正确设置的控制变量
		boolean errorView = false;
                //如果前面的过程发生异常
		if (exception != null) {
			//如果是ModelAndViewDefiningException异常
			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);
			}
		}

		//请求处理器是否返回了一个正确的view给渲染器
		if (mv != null && !mv.wasCleared()) {
			//开始渲染页面
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		//省略代码若干...
	}

     由上面的代码可以看出SpringMVC在检查请求处理过程中没有发生什么异常后就开始渲染页面了。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		//将用户使用的Locale(区域语言信息)传给response
		Locale locale = this.localeResolver.resolveLocale(request);
		response.setLocale(locale);

		View view;
		//如果用户的controller的方法返回的是字符串即view name
		if (mv.isReference()) {
			// 查找view name对应的view并返回View对象
			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() + "'");
			}
		}
		//如果用户的controller的方法返回的是View对象则直接获取
		else {
			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() + "'");
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		//调用view的渲染函数
		try {
			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;
		}
	}

     这里有两个地方我们需要注意下就是View对象的查找过程和View对象的render过程。这里我们以jsp的渲染举例说明:

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
			HttpServletRequest request) throws Exception {
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
		return null;
	}

     看到这里我们立刻明白是在我们注册的ViewResolver中挨个试探性的处理这个viewName,试探的过程就是在IOC容器中找viewName对应的View类型的bean,如果类型匹配返回这个bean,如果不匹配则返回null。看到这个viewResolvers我们立刻想到DispatcherServlet中的initStrategies方法,里面用initViewResolvers(context)初始化了这个成员变量。那么什么事viewResolver呢?我们以jsp为例来看看:

<bean id="jspViewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/jsp/" />
		<property name="suffix" value=".jsp" />
		<property name="order" value="1" />
	</bean>

    这就是一个viewResolver,再来看看对应的InternalResourceView是怎么渲染的,它的render函数是在它的父类AbstractView中实现的。

        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		//创建整合后需要返回给浏览器的Model
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		//设置一些Response的头部信息
		prepareResponse(request, response);
		//渲染数据
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

    InternalResourceView覆盖了父类的renderMergedOutputModel方法。

protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //将model中的数据设置到request(因为不见得所有数据都是通过request.setAttribute加入的)
		exposeModelAsRequestAttributes(model, request);
		//本类中的此函数是空函数,留给子类比如JstlView去实现自定义逻辑
		exposeHelpers(request);
		//设置跳转目的页面路径
		String dispatcherPath = prepareForRendering(request, response);
        //获取跳转控制器RequestDispatcher
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}
                //决定跳转到另一个控制器即forward操作还是返回用户请求资源,
		//如果用户设置或配置INCLUDE_REQUEST_URI_ATTRIBUTE
		//属性或response已经提交数据则直接返回资源给用户
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(request, response);
		}
                //携带request和response跳转到另一个控制器方法
		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.forward(request, response);
		}
	}

        这一步完成后SpringMVC就基本上完成自己的使命了,当找到对应的url路径,springmvc就继续访问url对应的servlet,剩下的渲染工作就交由Servlet去处理了,所以由此我们不难看出,springmvc实际是在tomcat和servlet中间加了一层,处理request将浏览器传递的参数封装成model再由应用来加工处理这些数据后,再将这些数据传递给servlet让servlet将这些经过应用处理的数据在页面上表现出来,从而完成web请求,目的在于页面和数据分离即页面表现和业务逻辑的分离,使开发中可以集中精力处理业务逻辑提高开发效率。  

        Spring Transation和Spring JDBC我们就不具体分析了,感兴趣的看官可以自己下载份源码研究。Spring Framework的源码分析至此结束。

 

 

 

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/flashflight/article/details/43018333
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞