SpringMVC视图渲染原理

org.springframework.web.servlet.DispatcherServlet.render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response){
    // 使用语言解析器处理当前请求
    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);
    // 获取到视图名称,如果不为String,返回空,否则返回viewName
    String viewName = mv.getViewName();
    // 如果
    if (viewName != null) {
    	// 解析视图
    	View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);{
    		// 遍历所有的视图解析器,对视图进行解析
    		if (this.viewResolvers != null) {
    			for (ViewResolver viewResolver : this.viewResolvers) {
    				// 使用视图解析器解析视图
    				View view = viewResolver.resolveViewName(viewName, locale);{
    					// 获取缓存的Key,将视图缓存,因为视图只需要创建一次,不需要每次都创建
    					Object cacheKey = getCacheKey(viewName, locale);
    					// 没有被缓存
    					View view = this.viewAccessCache.get(cacheKey);
    					if (view == null) {
    						synchronized (this.viewCreationCache) {
    							// 双重校验
    							view = this.viewCreationCache.get(cacheKey);
    							if (view == null) {
    								// 创建视图对象
    								view = createView(viewName, locale);{
    									// 判断当前视图解析器是否可以解析该视图
    									if (!canHandle(viewName, locale)) {
    										return null;
    									}
    									// 校验viewName是不是以redirect:为前缀
    									if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    										// 截取视图名称
    										String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
    										// 创建RedirectView
    										RedirectView view = new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible());
    										// 获取要重定向的主机
    										String[] hosts = getRedirectHosts();
    										if (hosts != null) {
    											view.setHosts(hosts);
    										}
    										// 执行RedirectView的生命周期,成为Bean对象,beanName为返回的视图名,包含前缀
    										return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    									}

    									// 校验ViewName是不是forward:为前缀
    									if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    										// 截取视图名称
    										String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
    										// 创建InternalResourceView对象
    										InternalResourceView view = new InternalResourceView(forwardUrl);
    										// 执行RedirectView的生命周期,成为Bean对象,beanName为返回的视图名,包含前缀
    										return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
    									}

    									// 如果没有前缀,表示不是重定向,也不是指定转发路径,创建根据视图名创建view对象
    									return super.createView(viewName, locale);{
    										return loadView(viewName, locale);{
    											AbstractUrlBasedView view = buildView(viewName);{
    												InternalResourceView view = (InternalResourceView) super.buildView(viewName);{
    													// 初始化view对象
    													// 因为在创建InternalResourceViewResolver的时候,就会判断是否引入了JSTL的包,如果引入了
    													// 并且视图对象是InternalResourceView类型,那么创建的View对象就是JstlView,它继承了InternalResourceView
    													// 用JstlView就能解析一些C:标签
    													AbstractUrlBasedView view = instantiateView();{
    														// 默认是InternalResourceView对象,如果为JstlView,JstlView对象,返回直接反射创建view对象
    														return (getViewClass() == InternalResourceView.class ? new InternalResourceView() :
    														(getViewClass() == JstlView.class ? new JstlView() : super.instantiateView()));
    													}
    													// 设置视图的URL路径,拼接前后缀
    													view.setUrl(getPrefix() + viewName + getSuffix());
    													// 下面都是将自己设置的配置设置当View中
    													// 设置视图自定义的属性
    													view.setAttributesMap(getAttributesMap());
    													// 设置内容类型
    													String contentType = getContentType();
    													if (contentType != null) {
    														view.setContentType(contentType);
    													}
    													// 获取请求上下文的属性放入view中,这个是手动设置的,如果没有设置就没有
    													String requestContextAttribute = getRequestContextAttribute();
    													if (requestContextAttribute != null) {
    														view.setRequestContextAttribute(requestContextAttribute);
    													}
    													return view;
    												}
    												return view;
    											}
    											// 执行view的生命周期方法,就是Spring的生命周期方法
    											// 例如invokeAwareMethods,invokeInitMethods...
    											View result = applyLifecycleMethods(viewName, view);
    											// 校验当前视图是否存在,并且有效,默认返回true,给子类实现的,根据自身逻辑判断此视图是否有效
    											return (view.checkResource(locale) ? result : null);
    										}
    									}
    								}
    								// 如果没有找到或者创建视图,并且当前视图是允许不被解析,cacheUnresolved默认为true
    								if (view == null && this.cacheUnresolved) {
    									// 给定一个空view,render方法不处理任何逻辑
    									view = UNRESOLVED_VIEW;
    								}
    								// 找到了或者创建了对应的视图,并且当前视图允许被缓存,默认返回ture,可以根据自身情况设置
    								if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
    									// 缓存view对象,防止多次创建
    									this.viewAccessCache.put(cacheKey, view);
    									this.viewCreationCache.put(cacheKey, view);
    								}
    							}
    						}
    					}
    					// 如果View是UNRESOLVED_VIEW,表示上面获取View失败了,返回null,否则返回符合条件的View对象
    					return (view != UNRESOLVED_VIEW ? view : null);
    				}
    				if (view != null) {
    					return view;
    				}
    			}
    		}
    		return null;
    	}
    }
    else {
    	// 当前view不是一个String类型,而是一个View,就不需要解析,直接拿
    	view = mv.getView();
    }

    try {
    	// 如果当前mv设置了状态,将这个状态设置给响应体
    	if (mv.getStatus() != null) {
    		response.setStatus(mv.getStatus().value());
    	}
    	// 调用视图的渲染方法
    	view.render(mv.getModelInternal(), request, response);{
    		// 将不同的数据源(静态属性、模型数据、路径变量等)合并到一个新的模型对象中,以便在视图渲染过程中使用。
    		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    		// 预处理响应信息
    		prepareResponse(request, response);{
    			// 根据不同的View来处理不同的逻辑
    			if (generatesDownloadContent()) {
    				response.setHeader("Pragma", "private");
    				response.setHeader("Cache-Control", "private, must-revalidate");
    			}
    		}
    		// 1. 合并Model数据,转发的逻辑
    		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);{
    			// 将模型中的数据放入请求域中
    			exposeModelAsRequestAttributes(model, request);{
    				// 将模型中的数据放入请求域中
    				model.forEach((name, value) -> {
    					if (value != null) { request.setAttribute(name, value); }
    					else { request.removeAttribute(name); }
    				});
    			}

    			// 如果有,暴露帮助信息,默认空实现
    			exposeHelpers(request);

    			// 渲染之前做的预处理,校验url是不是一个死循环 例如 "/haha" 转发到"/haha"
    			String dispatcherPath = prepareForRendering(request, response);

    			// 获取转发器,(通常是JSP)的RequestDispatcher
    			RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    			// 用于在一个 JSP 页面中包含另一个资源(可以是另一个 JSP 页面或者控制器返回的视图)的输出内容。
    			// 这个标签会在当前页面的执行期间,将另一个资源的内容合并到当前页面中,类似于在当前页面中直接插入另一个页面的内容。
    			// 经常用于在一个页面中包含一些共同的内容,例如页头、页脚、菜单等。
    			// 包含的资源的输出会被包含在当前页面的输出流中处理。
    			// if (useInclude(request, response)) {
    				response.setContentType(getContentType());
    				rd.include(request, response);
    			}
    			else {
    				// 请求转发
    				rd.forward(request, response);
    			}
    		}
    		// 2. 合并Model数据,重定向的逻辑
    		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);{
    			// 获取重定向URL
    			String targetUrl = createTargetUrl(model, request);{
    				// 如果存在路径变量,需要替换路径变量,路径变量在Handler处理的时候就已经保存到请求域中了
    				if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
    					targetUrl.append(getUrl());
    					Map<String, String> variables = getCurrentRequestUriVariables(request);
    					targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
    				}
    			}
    			// 根据模型数据和当前的请求信息,动态地更新重定向的目标 URL,确保重定向的目标地址能够包含最新的数据信息
    			// 因为url可能含有路径,替换路径之后
    			targetUrl = updateTargetUrl(targetUrl, model, request, response);

    			// 保存重定向需要用到的属性
    			RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

    			// 重定向
    			sendRedirect(request, response, targetUrl, this.http10Compatible);{
    				String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
    				// 302重定向
    				response.sendRedirect(encodedURL);
    			}
    		}
    	}
    }
    // 视图渲染失败,抛出异常
    catch (Exception ex) {
    	throw ex;
    }
}

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值