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的源码分析至此结束。

时间: 2024-08-29 19:06:30

Spring Framework源码(十三):SpringMVC之从ModelMap到页面渲染的相关文章

[自编号1-2]将spring framework源码导入Spring Tool Suite中

先讲基本步骤,如下: 去GitHub中下载spring framework的源码,https://github.com/spring-projects/spring-framework/releases 下载3.2.X其中的一个版本; 安装Gradle软件,官网下载,解压即可,设置GRADLE_HOME,和PATH. 命令行中运行gradle -v,检查一下是否正确安装配置: 命令行中运行spring framework根目录下的import-into-eclipse.bat 连续两次回车,就可

spring framework源码下载并导入eclipse

工作后开始学习Javaspring,好多地方都不明白,工厂模式来管理bean,他是怎么管理的呢,于是想到了看源码,但是源码好难搞,下载下来了,不知从哪下手,花了很多时间,都没弄好,偶然在网上看到篇文章,这才搞定,拿出来分享.原文地址:http://blog.csdn.net/buyaore_wo/article/details/8977746 一. 准备工作 1.下载安装sts(springsource推荐使用), 毕竟人家的框架用他自家的ide是最好的,当然sts也是基本eclipse的, 下

Spring Framework源码(六):Spring AOP之解析标签

首先看下spring framework配置例子: <aop:config> <aop:aspect id="myaop" ref="log"> <aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/> <aop:before pointcut-ref="mycut"

Spring Framework源码(十):SpringMVC之文件上传

我们这一章讲SpringMVC中文件上传的应用,首先我们还是从DispatcherServlet这个核心分发器开始讲起: processedRequest = checkMultipart(request); 还记得上一章讲doDispatch这个方法时见过的方法吧?现在我们来分析下这个方法的具体解析过程: protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartExcepti

Spring Framework源码:spring beans之BeanFactory

先来认识一下两个重要容器BeanFactory和ApplicationContext的类图: 接下来,我们挨个对出现的类做个介绍: ??public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredT

深入源码分析SpringMVC底层原理(二)

原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到Handler的处理 1.3 根据Handler寻找Adapter 1.4 拦截器的处理 1.5 Adapter处理请求 1.6 异常视图的处理 1.7 页面的跳转 2.总结 在上一篇文章中我们讲到了SpringMVC的初始化,分别初始化两个ApplicationContext,并且初始化一些处理器

spring mvc源码解析

1.从DispatcherServlet开始     与很多使用广泛的MVC框架一样,SpringMVC使用的是FrontController模式,所有的设计都围绕DispatcherServlet 为中心来展开的.见下图,所有请求从DispatcherServlet进入,DispatcherServlet根据配置好的映射策略确定处理的 Controller,Controller处理完成返回ModelAndView,DispatcherServlet根据配置好的视图策略确定处理的 View,由V

做一个合格的程序猿之浅析Spring AOP源码(十八) Spring AOP开发大作战源码解析

其实上一篇文章价值很小,也有重复造轮子的嫌疑,网上AOP的实例很多,不胜枚举,其实我要说的并不是这个,我想要说的就是上一节中spring的配置文件: 我们这边并没有用到我们上几节分析的哪几个AOP的主要实现类:ProxyFactoryBean.java , ProxyFactory.java ,AspectJProxyFactory.java ,在我们这个配置文件中,根本没有显示的去配置这些类,那么spring到底是怎么做到的呢? 大家可以这么想,spring到底是怎么去杀害目标对象的呢?真正的

spring事务源码研读1

转载摘录自:Spring事务源码分析(一)Spring事务入门 有时为了保证一些操作要么都成功,要么都失败,这就需要事务来保证. 传统的jdbc事务如下: @Test public void testAdd(){ Connection con=null; try { con=DriverManager.getConnection(url , username , password ) con.setAutoCommit(false); //操作一 PreparedStatement ps = c