Spring MVC 源码学习札记(三)看看View怎么做到视图转发的

上一节看完了ViewResolver解析视图名产生View的过程,这就到了最后的流程View执行它的render方法,实现我们到视图的跳转,对不起,前两篇都忘了说了,主要是学习札记,整个过程解释的不多其实,如果愿意看下去的朋友,我只是提供看过程的思路,spring给的注释都很通俗易懂,加上我的部分解释,应该很清楚的我想。

view.render(mv.getModelInternal(), request, response);

 下面来看AbstractView中的render方法,并慢慢分析它的意义:

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
				" and static attributes " + this.staticAttributes);
		}

		// Consolidate static and dynamic model attributes.
		Map<String, Object> mergedModel =
				new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0));
		mergedModel.putAll(this.staticAttributes);
		if (model != null) {
			mergedModel.putAll(model);
		}

		// Expose RequestContext?
		if (this.requestContextAttribute != null) {
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		}

		prepareResponse(request, response);
		renderMergedOutputModel(mergedModel, request, response);
	}

 model参数就是ModelAndView对象mv中存放

/** Model Map */
	private ModelMap model;

 ModelMap:

public class ModelMap extends LinkedHashMap<String, Object> 

 render方法的首要作用就是将model的键值对属性和View类中的属性staticAttributes整合在一起,什么是静态属性呢~类似视图的页眉、页脚的固定信息等,它们可以固定不变,所以可以直接写在配置文件中。

在***-servlet.xml中,我们配置View的bean时,我们可以为视图定义

<property name="attributesCSV">
        <value>author=sha0k,copyRight=iteye</value>
</property>

 而这种固定的信息就将被解析为View类的staticAttributtes

/**
	 * Set static attributes as a CSV string.
	 * Format is: attname0={value1},attname1={value1}
	 * <p>"Static" attributes are fixed attributes that are specified in
	 * the View instance configuration. "Dynamic" attributes, on the other hand,
	 * are values passed in as part of the model.
	 */
	public void setAttributesCSV(String propString) throws IllegalArgumentException {
		if (propString != null) {
			StringTokenizer st = new StringTokenizer(propString, ",");
			while (st.hasMoreTokens()) {
				String tok = st.nextToken();
				int eqIdx = tok.indexOf("=");
				if (eqIdx == -1) {
					throw new IllegalArgumentException("Expected = in attributes CSV string '" + propString + "'");
				}
				if (eqIdx >= tok.length() - 2) {
					throw new IllegalArgumentException(
							"At least 2 characters ([]) required in attributes CSV string '" + propString + "'");
				}
				String name = tok.substring(0, eqIdx);
				String value = tok.substring(eqIdx + 1);

				// Delete first and last characters of value: { and }
				value = value.substring(1);
				value = value.substring(0, value.length() - 1);

				addStaticAttribute(name, value);
			}
		}
	}

 通读这个方法的代码,就可以看到它对property value string的解析过程,注意最后一行代码addStaticAttributte。。。

	public void addStaticAttribute(String name, Object value) {
		this.staticAttributes.put(name, value);
	}

 到此就很好理解staticAttributtes了。。。。

 

createRequestContext

这里请先跳过,我也没懂,源码涉及了另外一个类。。。

/**
	 * Prepare the given response for rendering.
	 * <p>The default implementation applies a workaround for an IE bug
	 * when sending download content via HTTPS.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 */
	protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
		if (generatesDownloadContent()) {
			response.setHeader("Pragma", "private");
			response.setHeader("Cache-Control", "private, must-revalidate");
		}
	}

 这个方法的主要内容在于generatesDownloadContent:

/**
	 * Return whether this view generates download content
	 * (typically binary content like PDF or Excel files).
	 * <p>The default implementation returns <code>false</code>. Subclasses are
	 * encouraged to return <code>true</code> here if they know that they are
	 * generating download content that requires temporary caching on the
	 * client side, typically via the response OutputStream.
	 * @see #prepareResponse
	 * @see javax.servlet.http.HttpServletResponse#getOutputStream()
	 */
	protected boolean generatesDownloadContent() {
		return false;
	}

 根据注释就可以看出,这段准备response的代码其实就做了一件事情,判断我们的响应输出是不是下载内容~~~download content!!!这就算懂了,继续了哦

renderMergedOutputModel这个方法,AbstractView类并没有实现它,在它里面是一个抽象方法,它的子类即InternalResourceView的父类AbstractUrlBasedView类也没有实现,所以我们就将代码的注意力跳转到InternalResourceView的renderMergedOutputModel方法中。

/**
	 * Render the internal resource given the specified model.
	 * This includes setting the model as request attributes.
	 */
	@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Determine which request handle to expose to the RequestDispatcher.
		HttpServletRequest requestToExpose = getRequestToExpose(request);

		// Expose the model object as request attributes.
		exposeModelAsRequestAttributes(model, requestToExpose);

		// Expose helpers as request attributes, if any.
		exposeHelpers(requestToExpose);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(requestToExpose, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = requestToExpose.getRequestDispatcher(dispatcherPath);
		if (rd == null) {
			throw new ServletException(
					"Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(requestToExpose, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(requestToExpose, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			exposeForwardRequestAttributes(requestToExpose);
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.forward(requestToExpose, response);
		}
	}

 

来说重点了,我跳过的要么是根据注释就能懂的,跳到源码就更容易了,要么是根本看不懂的。。。。嘿嘿,下面的又回到了AbstractView类的方法中了,因为它的子类并没有重写它

/**
	 * Expose the model objects in the given map as request attributes.
	 * Names will be taken from the model Map.
	 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
	 * @param model Map of model objects to expose
	 * @param request current HTTP request
	 */
	protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			String modelName = entry.getKey();
			Object modelValue = entry.getValue();
			if (modelValue != null) {
				request.setAttribute(modelName, modelValue);
				if (logger.isDebugEnabled()) {
					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
							"] to request in view with name '" + getBeanName() + "'");
				}
			}
			else {
				request.removeAttribute(modelName);
				if (logger.isDebugEnabled()) {
					logger.debug("Removed model object '" + modelName +
							"' from request in view with name '" + getBeanName() + "'");
				}
			}
		}
	}

根据上面的代码通读后,可以看懂,原来跟字面意思一样就是说暴露Model,暴露给谁呢?暴露给HttpSerlvetRequest,也就是把Model里的属性全部加入到request中,原来我们能在jsp页面用request访问我们添加到Model里的属性就是这里实现的啊~~~~哈哈

 

下面就不用我解释了,include和forwad,serlvet里的基本功~~~是不是流程明白了其实不难呢?到此整个流程就完成了,以后就要研究spring惊人的设计模式和方法了,重中之重在于 先把关键的类得看懂和看明白,下一节,就先看HandlerMapping的树结构吧

 

 

 

 

 

 

 

 

 

 

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