SpringMVC 视图解析流程 [ 以 InternalResourceView 和 InternalResourceViewResolver 为例 ] (SpringMvc 5.0.8)

1.先来看下DispatcherServlet类的doDispatch()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 为当前请求确定 Handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 获取到 HandlerAdapter 
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// 调用过滤器的 preHandle()方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 调用目标方法,拿到 ModelAndView 对象,此对象封装了视图名和响应数据
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				// 调用过滤器的 postHandle() 方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			// 视图解析,此方法的最后将调用过滤器的 afterCompletion() 方法,下面将以此方法为入口深入
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			// 触发过滤器的 afterCompletion() 方法
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
		// 触发过滤器的 afterCompletion() 方法
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

2.接下来从DispatcherServlet类的processDispatchResult()方法深入

2.1 processDispatchResult()方法

	/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;
		
		// 对异常的处理
		if (exception != null) {
			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);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			// 渲染视图,接着从这里往下看
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			// 触发过滤器的 afterCompletion() 方法
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

2.2 render()方法

	/**
	 * 渲染给定的ModelAndView。
	 * 这是处理请求的最后阶段。它可能涉及按名称解析视图。
	 * @param mv the ModelAndView to render
	 * @param request current HTTP servlet request
	 * @param response current HTTP servlet response
	 * @throws ServletException if view is missing or cannot be resolved
	 * @throws Exception if there's a problem rendering the view
	 */
	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		// 获取视图名
		String viewName = mv.getViewName();
		if (viewName != null) {
			// 解析视图名, 获得视图对象 View 
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			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() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isDebugEnabled()) {
			logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
			// 调用视图 View 对象的 render() 方法渲染视图
			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;
		}
	}

2.3 resolveViewName() 拿到所有的视图解析器尝试解析,得到View对象

	/**
	* 将给定的视图名称解析为View对象(要渲染)。默认实现会询问程序的所有ViewResolvers。
	*/
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {

		if (this.viewResolvers != null) {
			// 拿到所有的视图解析器,解析视图,一单返回的view对象不为空,则停止解析,直接返回
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}

3.解析视图

3.1 ViewResolver 接口

  • 视图解析器均实现自ViewResolver 接口,其中有一个resolveViewName()方法
public interface ViewResolver {
	View resolveViewName(String viewName, Locale locale) throws Exception;
}

3.2下面以InternalResourceViewResolver来介绍下resolveViewName()方法

先看下继承关系

  • InternalResourceViewResolver extends UrlBasedViewResolver
  • UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered
  • class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver

先看下AbstractCachingViewResolver中的方法resolveViewName()

@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		if (!isCache()) {
			return createView(viewName, locale);
		}
		else {
			// 获取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 对象
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
							if (logger.isTraceEnabled()) {
								logger.trace("Cached view [" + cacheKey + "]");
							}
						}
					}
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

下面接着看UrlBasedViewResolver 类中重写的createView()方法

@Override
	protected View createView(String viewName, Locale locale) throws Exception {
		//如果此解析程序不应处理给定视图,则返回null,由传递给链中的下一个视图解析器程序解析
		if (!canHandle(viewName, locale)) {
			return null;
		}

		// 检查是否 redirect: 前缀开头的视图名
		if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
			// 获取到 redirect: 后面的重定向地址
			String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
			// 创建一个重定向的视图
			RedirectView view = new RedirectView(redirectUrl,
					isRedirectContextRelative(), isRedirectHttp10Compatible());
			String[] hosts = getRedirectHosts();
			if (hosts != null) {
				view.setHosts(hosts);
			}
			return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
		}

		// 检查是否是 forward: 开头的视图名
		if (viewName.startsWith(FORWARD_URL_PREFIX)) {
			// 获取到forward:后面跟着的转发地址, 并创建一个InternalResourceView类型的视图返回
			String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
			return new InternalResourceView(forwardUrl);
		}

		// 否则回退到父类实现:调用loadView(大部分情况都是调的这行代码)
		// 如 controller 中, return "/account/list";
		return super.createView(viewName, locale);
	}

那继续看看父类中的createView方法是怎么调用的吧

// 此类中其他方法省略了
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
	protected View createView(String viewName, Locale locale) throws Exception {
		return loadView(viewName, locale);
	}
	// 这是一个抽象方法,在子类中有实现,
	protected abstract View loadView(String viewName, Locale locale) throws Exception;
}

看看子类UrlBasedViewResolver 中实现的loadView()方法

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
		// 主要的代码逻辑在 buildView 方法
		AbstractUrlBasedView view = buildView(viewName);
		View result = applyLifecycleMethods(viewName, view);
		return (view.checkResource(locale) ? result : null);
	}
	
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		Class<?> viewClass = getViewClass();
		Assert.state(viewClass != null, "No view class");
		
		// 创建一个视图对象
		AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
		// 设置转发地址; 正好和配置文件里写的配置对应上了
		/*
		<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/WEB-INF/views/"></property>
    		<property name="suffix" value=".jsp"></property>
		</bean>
		*/
		view.setUrl(getPrefix() + viewName + getSuffix());

		String contentType = getContentType();
		if (contentType != null) {
			view.setContentType(contentType);
		}

		view.setRequestContextAttribute(getRequestContextAttribute());
		// 将需要渲染的数据模型放入视图中
		view.setAttributesMap(getAttributesMap());

		Boolean exposePathVariables = getExposePathVariables();
		if (exposePathVariables != null) {
			view.setExposePathVariables(exposePathVariables);
		}
		Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
		if (exposeContextBeansAsAttributes != null) {
			view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
		}
		String[] exposedContextBeanNames = getExposedContextBeanNames();
		if (exposedContextBeanNames != null) {
			view.setExposedContextBeanNames(exposedContextBeanNames);
		}

		return view;
	}

4.渲染视图

4.1 View接口

  • 视图对象均实现自View接口,其有一个主要的方法render()
public interface View {
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
	@Nullable
	default String getContentType() {
		return null;
	}
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;
}

4.2 下面以 InternalResourceView来介绍下render方法的调用.

先看看其继承关系

  • InternalResourceView extends AbstractUrlBasedView
  • AbstractUrlBasedView extends AbstractView
  • AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware

看下AbstractView实现的的render方法

	public void render(@Nullable 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);
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		// 暴露模型,此方法主要在子类中被重写
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

下面看下在InternalResourceView类中重写的renderMergedOutputModel()方法

@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 将ModelAndView 里面的数据遍历出来,放到request的attributes中
		exposeModelAsRequestAttributes(model, request);

		// 这是一个空方法,只在子类JstlView中有实现
		exposeHelpers(request);

		//获取到转发的路径,如果即将转发的路径和当前请求的路径相同,将抛出异常
		String dispatcherPath = prepareForRendering(request, response);

		//获取目标资源(通常是JSP)的 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!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
			}
			rd.include(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() + "'");
			}
			//等同于 request.getRequestDispatcher(dispatcherPath).forward(request,response);
			rd.forward(request, response);
		}
	}

再看看是如何将ModelAndView中的模型数据放到request域中的

// 此类中其他方法省略
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
	protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

		model.forEach((modelName, modelValue) -> {
			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() + "'");
				}
			}
		});
	}
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值