SpringMVC视图源码解析

视图解析流程

任何方法的返回值,最终都会被包装成ModelAndView对象

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

视图渲染就是将域中的数据在页面展示,页面就是用来渲染模型数据的

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	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.isTraceEnabled()) {
     
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}

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

		if (mappedHandler != null) {
     
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

调用render(mv, request, response)方法渲染页面

	/**
	 * Render the given ModelAndView.
	 * 

This is the last stage in handling a request. It may involve resolving the view by name. * @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) { // We need to resolve the view name. //viewName是目标方法的返回值,即页面地址 // mv.getModelInternal()是隐含模型中的所有数据 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.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }

ViewResolver接口里面有resolveViewName()方法,作用是根据视图名(方法的返回值)得到View对象

public interface ViewResolver {
     

	/**
	 * Resolve the given view by name.
	 * 

Note: To allow for ViewResolver chaining, a ViewResolver should * return {@code null} if a view with the given name is not defined in it. * However, this is not required: Some ViewResolvers will always attempt * to build View objects with the given name, unable to return {@code null} * (rather throwing an exception when View creation failed). * @param viewName name of the view to resolve * @param locale the Locale in which to resolve the view. * ViewResolvers that support internationalization should respect this. * @return the View object, or {@code null} if not found * (optional, to allow for ViewResolver chaining) * @throws Exception if the view cannot be resolved * (typically in case of problems creating an actual View object) */ @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }

resolveViewName()方法得到View对象

	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {
     

		if (this.viewResolvers != null) {
     
			//循环遍历所有的视图解析器
			for (ViewResolver viewResolver : this.viewResolvers) {
     
				//viewResolver视图解析器根据方法的返回值得到一个View对象
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
     
					return view;
				}
			}
		}
		return null;
	}

视图解析器在初始化的时候会找是否有配置的视图解析器,如果没有就会使用默认的解析器

	/**
	 * Initialize the ViewResolvers used by this class.
	 * 

If no ViewResolver beans are defined in the BeanFactory for this * namespace, we default to InternalResourceViewResolver. */ private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; if (this.detectAllViewResolvers) { // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<>(matchingBeans.values()); // We keep ViewResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { try { ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default ViewResolver later. } } // Ensure we have at least one ViewResolver, by registering // a default ViewResolver if no other resolvers are found. if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); if (logger.isTraceEnabled()) { logger.trace("No ViewResolvers declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }

循环遍历时每一个viewResolver调用resolveViewName(viewName, locale)里面的createView(viewName, locale)得到一个View对象

	public View resolveViewName(String viewName, Locale locale) throws Exception {
     
		if (!isCache()) {
     
			return createView(viewName, locale);
		}
		else {
     
			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) {
     
						// Ask the subclass to create the View object.
						//根据方法的返回值创建出视图对象
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
     
							view = UNRESOLVED_VIEW;
						}
						if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
     
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
     
				if (logger.isTraceEnabled()) {
     
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}

createView创建View对象时,首先判断是否可以能进行处理,然后判断是否以redirect前缀或forward前缀的试图名,会分别创建new RedirectView(redirectUrl,isRedirectContextRelative(), isRedirectHttp10Compatible())对象和new InternalResourceView(forwardUrl)对象,而InternalResourceViewResolver解析器就是为了得到InternalResourceView这个对象。如有没有任何前缀就会调用父类的构造器默认创建一个View对象。

	/**
	 * Overridden to implement check for "redirect:" prefix.
	 * 

Not possible in {@code loadView}, since overridden * {@code loadView} versions in subclasses might rely on the * superclass always creating instances of the required view class. * @see #loadView * @see #requiredViewClass */ @Override protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { 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); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); InternalResourceView view = new InternalResourceView(forwardUrl); return applyLifecycleMethods(FORWARD_URL_PREFIX, view); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); }

最后返回创建的View对象

整个视图解析器得到View对象的流程就是,通过循环,所有配置的视图解析器都会尝试根据视图名(目标方法的返回值)得到View视图对象,如果能得到就返回这个对象,得不到就换下一个视图解析器。

接着在render()方法中调用得到的View对象的view.render(mv.getModelInternal(), request, response)方法

	/**
	 * 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
	 */
	@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));
		}
		//创建一个要输出的模型map
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		//渲染要给页面输出的所有数据
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}
	/**
	 * 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 {
     

		// Expose the model object as request attributes.
		//将隐含模型中的数据放在请求域中,可以获取
		exposeModelAsRequestAttributes(model, request);

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

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

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		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 [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
     
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
     
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			//转发页面
			rd.forward(request, response);
		}
	}

通过源码可以看出,视图解析器只是为了得到视图对象,而视图对象可以将模型数据全部放在请求域中,并且转发或者重定向到页面。所以,只有视图对象才能真正的渲染视图

你可能感兴趣的:(SpringMVC,视图解析,View,SpringMVC)