前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring-mvc 视图模式之freemarker整合解析

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

作者头像
技术蓝海
发布2018-04-26 14:18:45
8530
发布2018-04-26 14:18:45
举报
文章被收录于专栏:wannshan(javaer,RPC)

spring-mvc 版本4.04

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

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

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

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

代码语言:javascript
复制
// Did the handler return a view to render? 返回一个view 去渲染
if (mv != null && !mv.wasCleared()) {
	render(mv, request, response);//就是渲染了
	if (errorView) {
		WebUtils.clearErrorRequestAttributes(request);
	}
}

然后render方法,看到

代码语言:javascript
复制
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方法

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

代码语言:javascript
复制
/**
 * 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模板方法的神力开始起作用了。

代码语言:javascript
复制
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

-------------------------------------------------------------------------- 以上是spring-mvc怎么发现第三方viewResolver(不限freemarker)的,然后看看,请求来的url怎么找到匹配的view的,接着说上面遗留的问题 再看这句代码:

代码语言:javascript
复制
View view = viewResolver.resolveViewName(viewName, locale);

这个方法是org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver父类AbstractCachingViewResolver实现的。 跟进去:

代码语言:javascript
复制
@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);
	}
}
代码语言:javascript
复制
//由于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));//做模式匹配
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档