spring-mvc 视图模式之freemarker整合解析

spring-mvc 版本4.04

今天翻项目中freemarker相关代码,疑惑springmvc是怎么发现freemarker的,于是单步进去。 DispatcherServlet的doDispatch方法里有这么一句:

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

可以猜到,这是handler业务处理后,到渲染页面的阶段了,也就是freemarker该出场的时候了。

进入processDispatchResult这个方法,看到这么一段

// 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方法

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对象里了,那什么时候存的呢,代码里搜索下,找到如下方法:

/**
 * 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的,接着说上面遗留的问题 再看这句代码:

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));//做模式匹配
}

最后类图,注意方法的重写。子类总是调用最近上级节点方法。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏haifeiWu与他朋友们的专栏

美团外卖开源路由框架 WMRouter 源码分析

上周四美团外卖技术团队开源了一个 Android Router 的框架: WMRouter,博客详细介绍了用法以及设计方案,还不熟悉的同学可以先去看一下。本篇博...

39310
来自专栏GIS讲堂

excel中提取中文拼音

概述:在工作时,有时候会用到汉语拼音,本文讲述如何在Excel中通过vba程序提取汉字的拼音。

24630
来自专栏JackieZheng

Spring读书笔记——bean加载

我们的日常开发几乎离不开Spring,他为我们的开发带来了很大的便捷,那么Spring框架是如何做到方便他人的呢。今天就来说说bean如何被加载加载。 我们在x...

22290
来自专栏刘望舒

Android APT(编译时代码生成)最佳实践

越来越多第三方库使用apt技术,如DBflow、Dagger2、ButterKnife、ActivityRouter、AptPreferences。在编译时根据...

15950
来自专栏Google Dart

Flutter 构建完整应用手册-列表 顶

显示数据列表是移动应用程序的基本模式。 Flutter包含ListView部件,使列表变得轻而易举!

29020
来自专栏zhisheng

看透 Spring MVC 源代码分析与实践 —— Spring MVC 组件分析

组件概览 HandlerMapping 根据 request 找到对应的处理器 Handler 和 Interceptors。内部只有一个方法 Handler...

29680
来自专栏不会写文章的程序员不是好厨师

Spring源码初探-IOC(5)-ApplicationContext功能扩展及其扩展点

前面几篇关于Spring的文章简单阐述了使用BeanFactory作为容器时bean的初始化过程。然而在实际使用中,我们并不会直接接触和编码BeanFactor...

12320
来自专栏流媒体

MediaCodec进行AAC编解码(文件格式转换)

AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性...

26150
来自专栏lzj_learn_note

阿里ARouter使用及源码解析(一)

在app的开发中,页面之间的相互跳转是最基本常用的功能。在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生...

19220
来自专栏飞扬的花生

在ASP.MVC中使用Ajax

      Asp.net MVC 抛弃了Asp.net WebForm那种高度封装的控件,让我们跟底层的HTML有了更多的亲近。可以更自由、更灵活的去控制HT...

22690

扫码关注云+社区

领取腾讯云代金券