《看透springMvc源代码分析与实践》学习笔记
SpringMVC 版本 4.1.5.RELEASE
ViewResolver 是根据视图名和Locale解析出视图。SpringMVC中ViewResolver的结构如下图:
除了AbstractCachingViewResolver
一家独大,其他三类只有它们自身一个实现类.
List viewResolvers
,在初始化时需要对该变量进行设置.解析时for-each viewResolvers
,任一解析成功,立即返回。ContentNegotiatingViewResolver
解析器的作用是在别的解析器的结果上增加了对MediaType(也叫Content-Type)
和后缀名
的支持。
整个处理过程:
viewResolvers
-bean开发者可用通过向spring容器注入
viewResolver
,来实现对不同“模板引擎”的支持.
如:ThymeleafViewResolver
,FreeMarkerViewResolver
等。
viewResolvers初始化
viewResolvers
初始化包含两部分:
viewResolvers
属性手动配置//org.springframework.web.servlet.view.ContentNegotiatingViewResolver
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
private List viewResolvers;
@Override
protected void initServletContext(ServletContext servletContext) {
//取出Spring容器中所有的ViewResolver (注意不是SpringMVC容器)
Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
//若没有手动设置的viewResolvers, 直接添加容器中的ViewResolver
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
//排除自身
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}else {
for (int i=0; i < viewResolvers.size(); i++) {
if (matchingBeans.contains(viewResolvers.get(i))) {
continue;
}
String name = viewResolvers.get(i).getClass().getName() + i;
//如果手动设置的viewResolver,没有在容器中,则进行初始化
getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
}
}
//排序
OrderComparator.sort(this.viewResolvers);
this.cnManagerFactoryBean.setServletContext(servletContext);
}
}
resolveViewName解析视图
//org.springframework.web.servlet.view.ContentNegotiatingViewResolver
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
//获取RequestAttributes
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
//获取满足条件的MediaType
List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
//根据viewName + requestedMediaTypes 以及内部使用后缀名 查找 候选列表candidateViews
List candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//选出最优视图:: 如果有redirect视图直接返回,否则从candidateViews列表中遍历匹配
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
if (this.useNotAcceptableStatusCode) {
return NOT_ACCEPTABLE_VIEW;
}
return null;
}
AbstractCachingViewResolver
提供了统一的缓存功能,当视图解析过一次就被缓存起来,直到缓存被删除前
视图的解析都会自动从缓存中获取。
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
public static final int DEFAULT_CACHE_LIMIT = 1024;
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
private final Map
它的直接继承类有三个:
ResourceBundleViewResolver
:加载properties属性配置文件。默认值为
views
,可以配置baseName或baseNames
,
最终使用${baseName}-${locacle}
配置文件。
例: 如使用默认baseName,locale为zh, 则会加载 “views-zh.properties”
XmlViewResolver
: 加载xml文件,默认位置为"/WEB-INF/views.xml",可以通过location
参数配置。UrlBasedViewResolver
: 比较复杂,下面详细介绍。UrlBasedViewResolver重写了父类的getCacheKey,createView和loadView
方法。
//org.springframework.web.servlet.view.UrlBasedViewResolver
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
//prefix,suffix
private String prefix = "";
private String suffix = "";
//可以使用正则表达式:如 add*Page
private String[] viewNames;
public static final String REDIRECT_URL_PREFIX = "redirect:";
public static final String FORWARD_URL_PREFIX = "forward:";
@Override
protected Object getCacheKey(String viewName, Locale locale) {
//父类中 return viewName + "_" + locale; //由此得知UrlBasedViewResolver 不支持Locale
return viewName;
}
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// canHandle逻辑:如果viewNames为空,或viewNames数组(可以为正则)匹配viewName,则返回true
if (!canHandle(viewName, locale)) {
return null;
}
// redirect视图
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);
}
// forward视图
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
//如果都不是,则调用父类createView方法,而父类createView逻辑是调用模板方法:loadView
return super.createView(viewName, locale);
}
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
//创建具体的View
AbstractUrlBasedView view = buildView(viewName);
//调用applyLifecycleMethods,初始化view
View result = applyLifecycleMethods(viewName, view);
//检验view 对应的模板是否存在
return (view.checkResource(locale) ? result : null);
}
//获取beanFactory,初始化bean,并返回View
private View applyLifecycleMethods(String viewName, AbstractView view) {
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
}
buildView
//org.springframework.web.servlet.view.UrlBasedViewResolver
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//创建view
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
//拼接 url
view.setUrl(getPrefix() + viewName + getSuffix());
//如果设置了contentType, 这里设置contentType
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
//标识 view是否可以使用 @PathVariables 注释的参数。
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
//标识 view是否允许使用容器中注册的bean
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
//标识 view能够使用容器中哪些beans
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
//viewClass必须满足一定的条件
public void setViewClass(Class> viewClass) {
if (viewClass == null || !requiredViewClass().isAssignableFrom(viewClass)) {
throw new IllegalArgumentException(".........");
}
this.viewClass = viewClass;
}
//UrlBasedViewResolver中默认使用 AbstractUrlBasedView.class ,它的各个子类可以通过重写该方法使用不同的类型;
protected Class> requiredViewClass() {
return AbstractUrlBasedView.class;
}
UrlBasedViewResolver的子类主要做如下三件事:
requiredViewClass
,指定特定类型的viewClasssetViewClass
设置指定的viewClassInternalResourceViewResolver
//org.springframework.web.servlet.view.InternalResourceViewResolver
public class InternalResourceViewResolver extends UrlBasedViewResolver {
//是否jstl
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
//是否一致允许使用inclue: 默认为false
private Boolean alwaysInclude;
public InternalResourceViewResolver() {
Class> viewClass = requiredViewClass();
if (viewClass.equals(InternalResourceView.class) && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
//指定InternalResourceView类型
@Override
protected Class> requiredViewClass() {
return InternalResourceView.class;
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
//为了防止循环调用
view.setPreventDispatchLoop(true);
return view;
}
}
//org.springframework.web.servlet.view.AbstractTemplateViewResolver
public class AbstractTemplateViewResolver extends UrlBasedViewResolver {
//是否将所有requestAttributes 暴露给view
private boolean exposeRequestAttributes = false;
//如果requestAttributes中与model存在同名参数,是否允许使用requestAttributes中的值将model的值覆盖
private boolean allowRequestOverride = false;
//是否将所有session 暴露给view
private boolean exposeSessionAttributes = false;
//如果sessionAttributes中与model存在同名参数,是否允许使用sessionAttributes中的值将model的值覆盖
private boolean allowSessionOverride = false;
//是否将RequestContext暴露给view为spring的宏使用??
private boolean exposeSpringMacroHelpers = true;
}
SpringMVC默认支持的模板有:
FreeMarkerViewResolver
VelocityViewResolver
GroovyMarkupViewResolver
通过ViewResolver
解析出View
之后,最终会调用view.render
渲染,来响应此次请求.
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
String getContentType();
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
RequestToViewNameTranslator
可以在处理器返回的view为空时使用它,然后根据request获取viewName;
SpringMVC提供的实现类只有一个:DefaultRequestToViewNameTranslator
DefaultRequestToViewNameTranslator
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private static final String SLASH = "/";
private String prefix = "";
private String suffix = "";
private String separator = SLASH;
//剔除 开头、结尾部分的separator ,以及剔除后缀名 标识
private boolean stripLeadingSlash = true;
private boolean stripTrailingSlash = true;
private boolean stripExtension = true;
@Override
public String getViewName(HttpServletRequest request) {
//获取请求url
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
return (this.prefix + transformPath(lookupPath) + this.suffix);
}
//剔除 开头、结尾部分的separator ,以及剔除后缀名
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(1);
}
if (this.stripTrailingSlash && path.endsWith(SLASH)) {
path = path.substring(0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!SLASH.equals(this.separator)) {
path = StringUtils.replace(path, SLASH, this.separator);
}
return path;
}
}