参考:https://blog.csdn.net/andy_zhang2007/article/details/99644770
接口View定义了如下两个方法 :
1. String getContentType():返回Content-Type字符串。如果不能提前确定,返回null。
2. render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response):使用指定的数据模型model渲染页面。如果没有模型数据,model可以是null或者空对象。
为了提供更多便利,Spring MVC对接口View提供了抽象实现AbstractView,封装了一些通用逻辑。框架某处,或者开发人员如果要实现一个View,继承AbstractView做自己的扩展定制即可。
如上是Spring框架自身提供的一些View实现,从此清单可以看出,绝大多数继承自AbstractView。
View接口:
package org.springframework.web.servlet; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; public interface View { /** * Name of the HttpServletRequest attribute that contains the response status code. * Note: This attribute is not required to be supported by all View implementations. * @since 3.0 */ String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; /** * Name of the HttpServletRequest attribute that contains a Map with path variables. * The map consists of String-based URI template variable names as keys and their corresponding * Object-based values -- extracted from segments of the URL and type converted. * Note: This attribute is not required to be supported by all View implementations. * @since 3.1 */ String PATH_VARIABLES = View.class.getName() + ".pathVariables"; /** * The org.springframework.http.MediaType selected during content negotiation, * which may be more specific than the one the View is configured with. For example: * "application/vnd.example-v1+xml" vs "application/*+xml". * @since 3.2 */ String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; /** * Return the content type of the view, if predetermined. * Can be used to check the view's content type upfront, * i.e. before an actual rendering attempt. * @return the content type String (optionally including a character set), * or null if not predetermined */ @Nullable default String getContentType() { return null; } /** * Render the view given the specified model. * The first step will be preparing the request: In the JSP case, this would mean * setting model objects as request attributes. The second step will be the actual * rendering of the view, for example including the JSP via a RequestDispatcher. * @param model a Map with name Strings as keys and corresponding model * objects as values (Map can also be null in case of empty model) * @param request current HTTP request * @param response he HTTP response we are building * @throws Exception if rendering failed */ void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
抽象基类AbstractView:
package org.springframework.web.servlet.view; // 省略 import 行 public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware { /** Default content type. Overridable as bean property. */ public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1"; /** Initial size for the temporary output byte array (if any). */ private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 4096; @Nullable private String contentType = DEFAULT_CONTENT_TYPE; // 如果要将 RequestContext 作为一个属性放到数据模型中,就使用该变量 // 指定相应的属性名称。缺省值为 null,表示不把 RequestContext 放到 // 数据模型中 @Nullable private String requestContextAttribute; // 静态属性,在当前 View 对象创建时被填充内容 private final Map<String, Object> staticAttributes = new LinkedHashMap<>(); // 是否暴露路径变量到模型,缺省使用 true private boolean exposePathVariables = true; // 是否将所有 Spring IoC 容器中的 bean 作为请求属性暴露,属性名称使用 bean 名称。 // 缺省为 false。 // 如果将该值设置为 true,则所有 bean 会作为请求属性暴露,在 JSP 2.0 // 中,通过 ${...}这种方式就能经可以访问到 bean 。 // 一旦开启该功能,请求属性的覆盖的优先级为 : // model 属性 > bean > 自定义的 request/session 属性 // 跟该属性功能类似的属性是 exposedContextBeanNames,但二者不同 private boolean exposeContextBeansAsAttributes = false; // 要暴露到请求属性空间的 bean 的名称清单 // 这个属性 exposedContextBeanNames 和 exposeContextBeansAsAttributes=true 的 // 区别是 exposedContextBeanNames 仅仅暴露部分bean,而 // exposeContextBeansAsAttributes=true 暴露所有 bean @Nullable private Set<String> exposedContextBeanNames; // 记录当前View的 bean 名称,方便跟踪使用,框架构造View对象时会设置该值 @Nullable private String beanName; /** * Set the content type for this view. * Default is "text/html;charset=ISO-8859-1". * May be ignored by subclasses if the view itself is assumed * to set the content type, e.g. in case of JSPs. */ public void setContentType(@Nullable String contentType) { this.contentType = contentType; } /** * Return the content type for this view. */ @Override @Nullable public String getContentType() { return this.contentType; } /** * Set the name of the RequestContext attribute for this view. * Default is none. */ public void setRequestContextAttribute(@Nullable String requestContextAttribute) { this.requestContextAttribute = requestContextAttribute; } /** * Return the name of the RequestContext attribute, if any. */ @Nullable public String getRequestContextAttribute() { return this.requestContextAttribute; } /** * 使用 CSV 格式字符串添加静态属性 * Set static attributes as a CSV string. * Format is: attname0={value1},attname1={value1} * "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(@Nullable 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); } } } /** * 通过 Properties 对象形式添加静态属性 * Set static attributes for this view from a * java.util.Properties object. * "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. * This is the most convenient way to set static attributes. Note that * static attributes can be overridden by dynamic attributes, if a value * with the same name is included in the model. * Can be populated with a String "value" (parsed via PropertiesEditor) * or a "props" element in XML bean definitions. * @see org.springframework.beans.propertyeditors.PropertiesEditor */ public void setAttributes(Properties attributes) { CollectionUtils.mergePropertiesIntoMap(attributes, this.staticAttributes); } /** * 通过 Map 对象形式添加静态属性 * Set static attributes for this view from a Map. This allows to set * any kind of attribute values, for example bean references. * "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. * Can be populated with a "map" or "props" element in XML bean definitions. * @param attributes a Map with name Strings as keys and attribute objects as values */ public void setAttributesMap(@Nullable Map<String, ?> attributes) { if (attributes != null) { attributes.forEach(this::addStaticAttribute); } } /** * 返回静态属性 * Allow Map access to the static attributes of this view, * with the option to add or override specific entries. * Useful for specifying entries directly, for example via * "attributesMap[myKey]". This is particularly useful for * adding or overriding entries in child view definitions. */ public Map<String, Object> getAttributesMap() { return this.staticAttributes; } /** * 添加一个静态属性 * Add static data to this view, exposed in each view. * "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. * Must be invoked before any calls to render. * @param name the name of the attribute to expose * @param value the attribute value to expose * @see #render */ public void addStaticAttribute(String name, Object value) { this.staticAttributes.put(name, value); } /** * 返回静态属性 * Return the static attributes for this view. Handy for testing. * Returns an unmodifiable Map, as this is not intended for * manipulating the Map but rather just for checking the contents. * @return the static attributes in this view */ public Map<String, Object> getStaticAttributes() { return Collections.unmodifiableMap(this.staticAttributes); } /** * Specify whether to add path variables to the model or not. * Path variables are commonly bound to URI template variables through the @PathVariable * annotation. They're are effectively URI template variables with type conversion applied to * them to derive typed Object values. Such values are frequently needed in views for * constructing links to the same and other URLs. * Path variables added to the model override static attributes (see #setAttributes(Properties)) * but not attributes already present in the model. * By default this flag is set to true. Concrete view types can override this. * @param exposePathVariables true to expose path variables, and false otherwise */ public void setExposePathVariables(boolean exposePathVariables) { this.exposePathVariables = exposePathVariables; } /** * Return whether to add path variables to the model or not. */ public boolean isExposePathVariables() { return this.exposePathVariables; } /** * Set whether to make all Spring beans in the application context accessible * as request attributes, through lazy checking once an attribute gets accessed. * This will make all such beans accessible in plain ${...} * expressions in a JSP 2.0 page, as well as in JSTL's c:out * value expressions. * Default is "false". Switch this flag on to transparently expose all * Spring beans in the request attribute namespace. * NOTE: Context beans will override any custom request or session * attributes of the same name that have been manually added. However, model * attributes (as explicitly exposed to this view) of the same name will * always override context beans. * @see #getRequestToExpose */ public void setExposeContextBeansAsAttributes(boolean exposeContextBeansAsAttributes) { this.exposeContextBeansAsAttributes = exposeContextBeansAsAttributes; } /** * Specify the names of beans in the context which are supposed to be exposed. * If this is non-null, only the specified beans are eligible for exposure as * attributes. * If you'd like to expose all Spring beans in the application context, switch * the #setExposeContextBeansAsAttributes "exposeContextBeansAsAttributes" * flag on but do not list specific bean names for this property. */ public void setExposedContextBeanNames(String... exposedContextBeanNames) { this.exposedContextBeanNames = new HashSet<>(Arrays.asList(exposedContextBeanNames)); } /** * Set the view's name. Helpful for traceability. * Framework code must call this when constructing views. */ @Override public void setBeanName(@Nullable String beanName) { this.beanName = beanName; } /** * Return the view's name. Should never be null, * if the view was correctly configured. */ @Nullable public String getBeanName() { return this.beanName; } /** * Prepares the view given the specified model, merging it with static * attributes and a RequestContext attribute, if necessary. * Delegates to renderMergedOutputModel for the actual rendering. * @see #renderMergedOutputModel * 这里参数 model 是控制器返回的动态属性 */ @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes)); } // 合并静态属性和动态属性到最终使用的数据模型 mergedModel // 静态属性和动态属性同名时,采用动态属性的值 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); // 渲染数据模型到结果页面前准备响应对象 prepareResponse(request, response); // 渲染数据模型到结果页面 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } /** * Creates a combined output Map (never null) that includes dynamic values and static attributes. * Dynamic values take precedence over static attributes. */ protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { // 获取路径变量名称值对儿到 Map pathVars @SuppressWarnings("unchecked") Map<String, Object> pathVars = (this.exposePathVariables ? (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null); //合并静态属性,路径变量,动态属性 // Consolidate static and dynamic model attributes. int size = this.staticAttributes.size(); size += (model != null ? model.size() : 0); size += (pathVars != null ? pathVars.size() : 0); Map<String, Object> mergedModel = new LinkedHashMap<>(size); mergedModel.putAll(this.staticAttributes); if (pathVars != null) { mergedModel.putAll(pathVars); } if (model != null) { mergedModel.putAll(model); } // Expose RequestContext? if (this.requestContextAttribute != null) { mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel)); } return mergedModel; } /** * Create a RequestContext to expose under the specified attribute name. * The default implementation creates a standard RequestContext instance for the * given request and model. Can be overridden in subclasses for custom instances. * @param request current HTTP request * @param model combined output Map (never null), * with dynamic values taking precedence over static attributes * @return the RequestContext instance * @see #setRequestContextAttribute * @see org.springframework.web.servlet.support.RequestContext */ protected RequestContext createRequestContext( HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) { return new RequestContext(request, response, getServletContext(), model); } /** * Prepare the given response for rendering. 渲染视图前准备响应对象 * 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) { // 缺省实现实际上什么都不做 // 因为 generatesDownloadContent() 缺省返回 false if (generatesDownloadContent()) { // 如果当前是下载场景,添加如下头部 response.setHeader("Pragma", "private"); response.setHeader("Cache-Control", "private, must-revalidate"); } } /** * Return whether this view generates download content * (typically binary content like PDF or Excel files). * The default implementation returns false. Subclasses are * encouraged to return true 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; } /** * Get the request handle to expose to #renderMergedOutputModel, i.e. to the view. * The default implementation wraps the original request for exposure of Spring beans * as request attributes (if demanded). * @param originalRequest the original servlet request as provided by the engine * @return the wrapped request, or the original request if no wrapping is necessary * @see #setExposeContextBeansAsAttributes * @see #setExposedContextBeanNames * @see org.springframework.web.context.support.ContextExposingHttpServletRequest */ protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) { if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) { WebApplicationContext wac = getWebApplicationContext(); Assert.state(wac != null, "No WebApplicationContext"); return new ContextExposingHttpServletRequest(originalRequest, wac, this.exposedContextBeanNames); } return originalRequest; } /** * 此方法为抽象方法,必须有抽象子类提供,这里的参数 model 是经过合并静态属性,动态属性,路径变量 * 之后最重要应用到视图的数据模型,除非开发人员有其他自定义属性要渲染,否则直接使用该数据模型 * 渲染视图即可 * Subclasses must implement this method to actually render the view. * The first step will be preparing the request: In the JSP case, * this would mean setting model objects as request attributes. * The second step will be the actual rendering of the view, * for example including the JSP via a RequestDispatcher. * @param model combined output Map (never null), * with dynamic values taking precedence over static attributes * @param request current HTTP request * @param response current HTTP response * @throws Exception if rendering failed */ protected abstract void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception; /** * 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 javax.servlet.RequestDispatcher. * @param model a Map of model objects to expose * @param request current HTTP request */ protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); } /** * 工具方法 : 为当前 View 创建一个临时的 OutputStream 对象 * Create a temporary OutputStream for this view. * This is typically used as IE workaround, for setting the content length header * from the temporary stream before actually writing the content to the HTTP response. */ protected ByteArrayOutputStream createTemporaryOutputStream() { return new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE); } /** * Write the given temporary OutputStream to the HTTP response. * 工具方法 : 将指定的临时 OutputStream baos 中的数据写入到 response * @param response current HTTP response * @param baos the temporary OutputStream to write * @throws IOException if writing/flushing failed */ protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException { // Write content type and also length (determined via byte array). response.setContentType(getContentType()); response.setContentLength(baos.size()); // Flush byte array to servlet output stream. ServletOutputStream out = response.getOutputStream(); baos.writeTo(out); out.flush(); } /** * 工具方法 : 结合考虑请求属性 View.SELECTED_CONTENT_TYPE 和 #getContentType 返回值设置响应的 Content Type * Set the content type of the response to the configured * #setContentType(String) content type unless the * View#SELECTED_CONTENT_TYPE request attribute is present and set * to a concrete media type. */ protected void setResponseContentType(HttpServletRequest request, HttpServletResponse response) { MediaType mediaType = (MediaType) request.getAttribute(View.SELECTED_CONTENT_TYPE); if (mediaType != null && mediaType.isConcrete()) { response.setContentType(mediaType.toString()); } else { response.setContentType(getContentType()); } } @Override public String toString() { return getClass().getName() + ": " + formatViewName(); } protected String formatViewName() { return (getBeanName() != null ? "name '" + getBeanName() + "'" : "[" + getClass().getSimpleName() + "]"); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2023-03-06 js阻塞的解决方法有哪些?
2023-03-06 WebGL大场景性能优化
2023-03-06 WebGPU是什么?WebGPU和WebGL的区别
2023-03-06 arcgis stretched value和pixel value区别
2017-03-06 点云库PCL学习