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));//做模式匹配
}
最后类图,注意方法的重写。子类总是调用最近上级节点方法。