首页

关于springframework的spring-webmvc包中通过ViewRendererServlet实现mvc中model和view的解耦源码说明

标签:springframework,spring-webmvc,ViewRendererServlet,mvc原理,model和view解耦,springmvc实现     发布时间:2018-09-19   

一、前言

关于springframeworkspring-webmvc包定义org.springframework.web.servlet.View视图接口、org.springframework.web.servlet.ViewRendererServlet模型servlet服务端实现类,通过各种pdf、velocity、json、freemarker、tile、xml等视图场景实现定义view接口,再通过HttpServlet的ViewRendererServlet子类实现Modeal数据和展示模板view的解析合成。

二、源码说明

1. View接口<-AbstractView抽象类 <-AbstractPdfView的pdf视图类

package org.springframework.web.servlet;@b@@b@import java.util.Map;@b@import javax.servlet.http.HttpServletRequest;@b@import javax.servlet.http.HttpServletResponse;@b@@b@public abstract interface View@b@{@b@  public static final String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";@b@  public static final String PATH_VARIABLES = View.class.getName() + ".pathVariables";@b@  public static final String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";@b@@b@  public abstract String getContentType();@b@@b@  public abstract void render(Map<String, ?> paramMap, HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse)@b@    throws Exception;@b@}
package org.springframework.web.servlet.view;@b@@b@import java.io.ByteArrayOutputStream;@b@import java.io.IOException;@b@import java.util.Collections;@b@import java.util.HashMap;@b@import java.util.Map;@b@import java.util.Properties;@b@import java.util.StringTokenizer;@b@import javax.servlet.ServletOutputStream;@b@import javax.servlet.http.HttpServletRequest;@b@import javax.servlet.http.HttpServletResponse;@b@@b@import org.springframework.beans.factory.BeanNameAware;@b@import org.springframework.util.CollectionUtils;@b@import org.springframework.web.context.support.WebApplicationObjectSupport;@b@import org.springframework.web.servlet.View;@b@import org.springframework.web.servlet.support.RequestContext;@b@@b@ @b@public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {@b@@b@	/** Default content type. Overridable as bean property. */@b@	public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";@b@@b@	/** Initial size for the temporary output byte array (if any) */@b@	private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 4096;@b@@b@@b@	private String beanName;@b@@b@	private String contentType = DEFAULT_CONTENT_TYPE;@b@@b@	private String requestContextAttribute;@b@@b@	/** Map of static attributes, keyed by attribute name (String) */@b@	private final Map<String, Object> staticAttributes = new HashMap<String, Object>();@b@@b@	/** Whether or not the view should add path variables in the model */@b@	private boolean exposePathVariables = true;@b@@b@	/**@b@	 * Set the view's name. Helpful for traceability.@b@	 * <p>Framework code must call this when constructing views.@b@	 */@b@	public void setBeanName(String beanName) {@b@		this.beanName = beanName;@b@	}@b@@b@	/**@b@	 * Return the view's name. Should never be <code>null</code>,@b@	 * if the view was correctly configured.@b@	 */@b@	public String getBeanName() {@b@		return this.beanName;@b@	}@b@@b@	/**@b@	 * Set the content type for this view.@b@	 * Default is "text/html;charset=ISO-8859-1".@b@	 * <p>May be ignored by subclasses if the view itself is assumed@b@	 * to set the content type, e.g. in case of JSPs.@b@	 */@b@	public void setContentType(String contentType) {@b@		this.contentType = contentType;@b@	}@b@@b@	/**@b@	 * Return the content type for this view.@b@	 */@b@	public String getContentType() {@b@		return this.contentType;@b@	}@b@@b@	/**@b@	 * Set the name of the RequestContext attribute for this view.@b@	 * Default is none.@b@	 */@b@	public void setRequestContextAttribute(String requestContextAttribute) {@b@		this.requestContextAttribute = requestContextAttribute;@b@	}@b@@b@	/**@b@	 * Return the name of the RequestContext attribute, if any.@b@	 */@b@	public String getRequestContextAttribute() {@b@		return this.requestContextAttribute;@b@	}@b@@b@	/**@b@	 * Set static attributes as a CSV string.@b@	 * Format is: attname0={value1},attname1={value1}@b@	 * <p>"Static" attributes are fixed attributes that are specified in@b@	 * the View instance configuration. "Dynamic" attributes, on the other hand,@b@	 * are values passed in as part of the model.@b@	 */@b@	public void setAttributesCSV(String propString) throws IllegalArgumentException {@b@		if (propString != null) {@b@			StringTokenizer st = new StringTokenizer(propString, ",");@b@			while (st.hasMoreTokens()) {@b@				String tok = st.nextToken();@b@				int eqIdx = tok.indexOf("=");@b@				if (eqIdx == -1) {@b@					throw new IllegalArgumentException("Expected = in attributes CSV string '" + propString + "'");@b@				}@b@				if (eqIdx >= tok.length() - 2) {@b@					throw new IllegalArgumentException(@b@							"At least 2 characters ([]) required in attributes CSV string '" + propString + "'");@b@				}@b@				String name = tok.substring(0, eqIdx);@b@				String value = tok.substring(eqIdx + 1);@b@@b@				// Delete first and last characters of value: { and }@b@				value = value.substring(1);@b@				value = value.substring(0, value.length() - 1);@b@@b@				addStaticAttribute(name, value);@b@			}@b@		}@b@	}@b@@b@	 @b@	public void setAttributes(Properties attributes) {@b@		CollectionUtils.mergePropertiesIntoMap(attributes, this.staticAttributes);@b@	}@b@@b@	 @b@	public void setAttributesMap(Map<String, ?> attributes) {@b@		if (attributes != null) {@b@			for (Map.Entry<String, ?> entry : attributes.entrySet()) {@b@				addStaticAttribute(entry.getKey(), entry.getValue());@b@			}@b@		}@b@	}@b@@b@	 @b@	public Map<String, Object> getAttributesMap() {@b@		return this.staticAttributes;@b@	}@b@@b@	 @b@	public void addStaticAttribute(String name, Object value) {@b@		this.staticAttributes.put(name, value);@b@	}@b@@b@	 @b@	public Map<String, Object> getStaticAttributes() {@b@		return Collections.unmodifiableMap(this.staticAttributes);@b@	}@b@@b@	 @b@	public void setExposePathVariables(boolean exposePathVariables) {@b@		this.exposePathVariables = exposePathVariables;@b@	}@b@@b@	/**@b@	 * Returns the value of the flag indicating whether path variables should be added to the model or not.@b@	 */@b@	public boolean isExposePathVariables() {@b@		return exposePathVariables;@b@	}@b@@b@	 @b@	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {@b@		if (logger.isTraceEnabled()) {@b@			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +@b@				" and static attributes " + this.staticAttributes);@b@		}@b@		@b@		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);@b@@b@		prepareResponse(request, response);@b@		renderMergedOutputModel(mergedModel, request, response);@b@	}@b@@b@	 @b@	protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,@b@			HttpServletResponse response) {@b@		@SuppressWarnings("unchecked")@b@		Map<String, Object> pathVars = this.exposePathVariables ?@b@			(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null;@b@@b@		// Consolidate static and dynamic model attributes.@b@		int size = this.staticAttributes.size();@b@		size += (model != null) ? model.size() : 0;@b@		size += (pathVars != null) ? pathVars.size() : 0;@b@		Map<String, Object> mergedModel = new HashMap<String, Object>(size);@b@		mergedModel.putAll(this.staticAttributes);@b@		if (pathVars != null) {@b@			mergedModel.putAll(pathVars);@b@		}@b@		if (model != null) {@b@			mergedModel.putAll(model);@b@		}@b@@b@		// Expose RequestContext?@b@		if (this.requestContextAttribute != null) {@b@			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));@b@		}@b@		@b@		return mergedModel;@b@	}@b@@b@	 @b@	protected RequestContext createRequestContext(@b@			HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) {@b@@b@		return new RequestContext(request, response, getServletContext(), model);@b@	}@b@@b@	 @b@	protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {@b@		if (generatesDownloadContent()) {@b@			response.setHeader("Pragma", "private");@b@			response.setHeader("Cache-Control", "private, must-revalidate");@b@		}@b@	}@b@ @b@	protected boolean generatesDownloadContent() {@b@		return false;@b@	}@b@ @b@	protected abstract void renderMergedOutputModel(@b@			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;@b@@b@@b@	 @b@	protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {@b@		for (Map.Entry<String, Object> entry : model.entrySet()) {@b@			String modelName = entry.getKey();@b@			Object modelValue = entry.getValue();@b@			if (modelValue != null) {@b@				request.setAttribute(modelName, modelValue);@b@				if (logger.isDebugEnabled()) {@b@					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +@b@							"] to request in view with name '" + getBeanName() + "'");@b@				}@b@			}@b@			else {@b@				request.removeAttribute(modelName);@b@				if (logger.isDebugEnabled()) {@b@					logger.debug("Removed model object '" + modelName +@b@							"' from request in view with name '" + getBeanName() + "'");@b@				}@b@			}@b@		}@b@	}@b@@b@	 @b@	protected ByteArrayOutputStream createTemporaryOutputStream() {@b@		return new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);@b@	}@b@@b@	 @b@	protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException {@b@		// Write content type and also length (determined via byte array).@b@		response.setContentType(getContentType());@b@		response.setContentLength(baos.size());@b@@b@		// Flush byte array to servlet output stream.@b@		ServletOutputStream out = response.getOutputStream();@b@		baos.writeTo(out);@b@		out.flush();@b@	}@b@@b@@b@	@Override@b@	public String toString() {@b@		StringBuilder sb = new StringBuilder(getClass().getName());@b@		if (getBeanName() != null) {@b@			sb.append(": name '").append(getBeanName()).append("'");@b@		}@b@		else {@b@			sb.append(": unnamed");@b@		}@b@		return sb.toString();@b@	}@b@@b@}
package org.springframework.web.servlet.view.document;@b@@b@import java.io.ByteArrayOutputStream;@b@import java.io.OutputStream;@b@import java.util.Map;@b@import javax.servlet.http.HttpServletRequest;@b@import javax.servlet.http.HttpServletResponse;@b@@b@import com.lowagie.text.Document;@b@import com.lowagie.text.DocumentException;@b@import com.lowagie.text.PageSize;@b@import com.lowagie.text.pdf.PdfWriter;@b@@b@import org.springframework.web.servlet.view.AbstractView;@b@ @b@ @b@public abstract class AbstractPdfView extends AbstractView {@b@@b@	/**@b@	 * This constructor sets the appropriate content type "application/pdf".@b@	 * Note that IE won't take much notice of this, but there's not a lot we@b@	 * can do about this. Generated documents should have a ".pdf" extension.@b@	 */@b@	public AbstractPdfView() {@b@		setContentType("application/pdf");@b@	}@b@@b@@b@	@Override@b@	protected boolean generatesDownloadContent() {@b@		return true;@b@	}@b@@b@	@Override@b@	protected final void renderMergedOutputModel(@b@			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {@b@@b@		// IE workaround: write into byte array first.@b@		ByteArrayOutputStream baos = createTemporaryOutputStream();@b@@b@		// Apply preferences and build metadata.@b@		Document document = newDocument();@b@		PdfWriter writer = newWriter(document, baos);@b@		prepareWriter(model, writer, request);@b@		buildPdfMetadata(model, document, request);@b@@b@		// Build PDF document.@b@		document.open();@b@		buildPdfDocument(model, document, writer, request, response);@b@		document.close();@b@@b@		// Flush to HTTP response.@b@		writeToResponse(response, baos);@b@	}@b@@b@	 @b@	protected Document newDocument() {@b@		return new Document(PageSize.A4);@b@	}@b@@b@	 @b@	protected PdfWriter newWriter(Document document, OutputStream os) throws DocumentException {@b@		return PdfWriter.getInstance(document, os);@b@	}@b@@b@	 @b@	protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request)@b@			throws DocumentException {@b@@b@		writer.setViewerPreferences(getViewerPreferences());@b@	}@b@@b@	 @b@	protected int getViewerPreferences() {@b@		return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage;@b@	}@b@@b@	 @b@	protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) {@b@	}@b@@b@	 @b@	protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,@b@			HttpServletRequest request, HttpServletResponse response) throws Exception;@b@@b@}

2. ViewRendererServlet合成实现类

package org.springframework.web.servlet;@b@@b@import java.io.IOException;@b@import java.util.Map;@b@import javax.servlet.ServletException;@b@import javax.servlet.http.HttpServlet;@b@import javax.servlet.http.HttpServletRequest;@b@import javax.servlet.http.HttpServletResponse;@b@import org.springframework.web.util.NestedServletException;@b@@b@public class ViewRendererServlet extends HttpServlet@b@{@b@  public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE;@b@  public static final String VIEW_ATTRIBUTE = ViewRendererServlet.class.getName() + ".VIEW";@b@  public static final String MODEL_ATTRIBUTE = ViewRendererServlet.class.getName() + ".MODEL";@b@@b@  protected final void doGet(HttpServletRequest request, HttpServletResponse response)@b@    throws ServletException, IOException@b@  {@b@    processRequest(request, response);@b@  }@b@@b@  protected final void doPost(HttpServletRequest request, HttpServletResponse response)@b@    throws ServletException, IOException@b@  {@b@    processRequest(request, response);@b@  }@b@@b@  protected final void processRequest(HttpServletRequest request, HttpServletResponse response)@b@    throws ServletException, IOException@b@  {@b@    try@b@    {@b@      renderView(request, response);@b@    }@b@    catch (ServletException ex) {@b@      throw ex;@b@    }@b@    catch (IOException ex) {@b@      throw ex;@b@    }@b@    catch (Exception ex) {@b@      throw new NestedServletException("View rendering failed", ex);@b@    }@b@  }@b@@b@  protected void renderView(HttpServletRequest request, HttpServletResponse response)@b@    throws Exception@b@  {@b@    View view = (View)request.getAttribute(VIEW_ATTRIBUTE);@b@    if (view == null)@b@      throw new ServletException("Could not complete render request: View is null");@b@@b@    Map model = (Map)request.getAttribute(MODEL_ATTRIBUTE);@b@    view.render(model, request, response);@b@  }@b@}