spring-mvc 視圖模式之freemarker整合解析

spring-mvc 版本4.04html

今天翻項目中freemarker相關代碼,疑惑springmvc是怎麼發現freemarker的,因而單步進去。
DispatcherServlet的doDispatch方法裏有這麼一句:java

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);web

能夠猜到,這是handler業務處理後,到渲染頁面的階段了,也就是freemarker該出場的時候了。spring

進入processDispatchResult這個方法,看到這麼一段spring-mvc

// Did the handler return a view to render? 返回一個view 去渲染
if (mv != null && !mv.wasCleared()) {
	render(mv, request, response);//就是渲染了
	if (errorView) {
		WebUtils.clearErrorRequestAttributes(request);
	}
}

而後render方法,看到緩存

View view;
if (mv.isReference()) {//mv 是String類型的,好比url
	// We need to resolve the view name.
	view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);//獲取來渲染頁面的view
	if (view == null) {
		throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
				"' in servlet with name '" + getServletName() + "'");
	}
}

看到resolveViewName方法mvc

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

	for (ViewResolver viewResolver : this.viewResolvers) {
		View view = viewResolver.resolveViewName(viewName, locale);
		//建立根據viewName(其實就是controller裏返回的url,好比/wiew/test.ftl)建立view
		//那隨便一個url都能有相應的view嗎?固然不是。好比要先匹配,這個下面說,
		if (view != null) {
			return view;
		}
	}
	return null;
}

原來view解決方案已經都存在viewResolvers對象裏了,那何時存的呢,代碼裏搜索下,找到以下方法:app

/**
 * Initialize the ViewResolvers used by this class.
 * <p>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) {//boolean值,是否自動檢測全部的ViewResolver,
		// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
		//是自動檢測,就用BeanFactoryUtils的beansOfTypeIncludingAncestors方法,找全部的ViewResolver
		//能夠看到這個方法頗有用,在項目也能夠用,,可你找到全部ViewResolver.class類型或子類的bean,很好用。
//這也體現了mvc框架的v的部分。任何實現了ViewResolver接口的類,均可做爲視圖用
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.
			OrderComparator.sort(this.viewResolvers);
		}
	}
	else {
		try {
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);//若是不自動檢測
			//String VIEW_RESOLVER_BEAN_NAME = "viewResolver";  就找名字爲viewResolver的bean做爲ViewResolver
			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.isDebugEnabled()) {
			logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
		}
	}
}

initViewResolvers這個方法,是在onRefresh的調用的,實際上是重寫了父類的方法,
在spring的初始化時自動被調用。spring模板方法的神力開始起做用了。框架

protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

--------------------------------------------------------------------------
以上是spring-mvc怎麼發現第三方viewResolver(不限freemarker)的,而後看看,請求來的url怎麼找到匹配的view的,接着說上面遺留的問題
再看這句代碼:ide

View view = viewResolver.resolveViewName(viewName, locale);

這個方法是org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver父類AbstractCachingViewResolver實現的。
跟進去:

@Override
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.viewAccessCache.put(cacheKey, view);
						this.viewCreationCache.put(cacheKey, view);
						if (logger.isTraceEnabled()) {
							logger.trace("Cached view [" + cacheKey + "]");
						}
					}
				}
			}
		}
		return (view != UNRESOLVED_VIEW ? view : null);
	}
}
//因爲UrlBasedViewResolver類從新寫了這個方法因此,是UrlBasedViewResolver的createView方法
@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.
	//就在這裏,代碼首先檢查resolver是否支持這個viewName(url)
	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());
		return applyLifecycleMethods(viewName, view);
	}
	// Check for special "forward:" prefix.
	if (viewName.startsWith(FORWARD_URL_PREFIX)) {
		String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
		return new InternalResourceView(forwardUrl);
	}
	// Else fall back to superclass implementation: calling loadView.
	return super.createView(viewName, locale);
}
//在這裏
protected boolean canHandle(String viewName, Locale locale) {
	String[] viewNames = getViewNames();
	//viewNames就是在xml文件裏配置的如,freemarker
	//<property name="viewNames">
	//<array>
	//<value>*.ftl</value>
		//<value>*.html</value>
	//</array>
	//</property>
	return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));//作模式匹配
}

最後類圖,注意方法的重寫。子類老是調用最近上級節點方法。

相關文章
相關標籤/搜索