该系列文档是本人在学习 Spring MVC 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.2.4.RELEASEjava
该系列其余文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》git
ViewResolver
组件,视图解析器,根据视图名和国际化,得到最终的视图 View 对象github
先来回顾一下在 DispatcherServlet
中处理请求的过程当中哪里使用到 ViewResolver
组件,能够回到《一个请求的旅行过程》中的 DispatcherServlet
的 render
方法中看看,以下:web
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. // <1> 解析 request 中得到 Locale 对象,并设置到 response 中 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); // 得到 View 对象 View view; String viewName = mv.getViewName(); // 状况一,使用 viewName 得到 View 对象 if (viewName != null) { // We need to resolve the view name. // <2.1> 使用 viewName 得到 View 对象 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { // 获取不到,抛出 ServletException 异常 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } // 状况二,直接使用 ModelAndView 对象的 View 对象 else { // No need to lookup: the ModelAndView object contains the actual View object. // 直接使用 ModelAndView 对象的 View 对象 view = mv.getView(); if (view == null) { // 获取不到,抛出 ServletException 异常 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. // 打印日志 if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { // <3> 设置响应的状态码 if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // <4> 渲染页面 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } } @Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { // 遍历 ViewResolver 数组 for (ViewResolver viewResolver : this.viewResolvers) { // 根据 viewName + locale 参数,解析出 View 对象 View view = viewResolver.resolveViewName(viewName, locale); // 解析成功,直接返回 View 对象 if (view != null) { return view; } } } return null; }
若是 ModelAndView 对象不为null
,且须要进行页面渲染,则调用 render
方法,若是设置的 View 对象是 String
类型,也就是 viewName
,则须要调用 resolveViewName
方法,经过 ViewResolver
根据 viewName
和 locale
解析出对应的 View 对象spring
这是先后端未分离的状况下重要的一个组件后端
org.springframework.web.servlet.ViewResolver
,视图解析器,根据视图名和国际化,得到最终的视图 View 对象,代码以下:数组
public interface ViewResolver { /** * 根据视图名和国际化,得到最终的 View 对象 */ @Nullable View resolveViewName(String viewName, Locale locale) throws Exception; }
ViewResolver 接口体系的结构以下:缓存
ViewResolver 的实现类比较多,其中 Spring MVC 默认使用 org.springframework.web.servlet.view.InternalResourceViewResolver
这个实现类app
Spring Boot 中的默认实现类以下:
能够看到有三个实现类:
org.springframework.web.servlet.view.ContentNegotiatingViewResolver
org.springframework.web.servlet.view.ViewResolverComposite
,默认没有实现类
org.springframework.web.servlet.view.BeanNameViewResolver
org.springframework.web.servlet.view.InternalResourceViewResolver
在 DispatcherServlet
的 initViewResolvers(ApplicationContext context)
方法,初始化 ViewResolver 组件,方法以下:
private void initViewResolvers(ApplicationContext context) { // 置空 viewResolvers 处理 this.viewResolvers = null; // 状况一,自动扫描 ViewResolver 类型的 Bean 们 if (this.detectAllViewResolvers) { // Find all ViewResolvers in the ApplicationContext, including ancestor contexts. Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<>(matchingBeans.values()); // We keep ViewResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.viewResolvers); } } // 状况二,得到名字为 VIEW_RESOLVER_BEAN_NAME 的 Bean 们 else { try { ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); 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. /** * 状况三,若是未得到到,则得到默认配置的 ViewResolver 类 * {@link org.springframework.web.servlet.view.InternalResourceViewResolver} */ if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); if (logger.isTraceEnabled()) { logger.trace("No ViewResolvers declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
若是“开启”探测功能,则扫描已注册的 ViewResolver 的 Bean 们,添加到 viewResolvers
中,默认开启
若是“关闭”探测功能,则得到 Bean 名称为 "viewResolver" 对应的 Bean ,将其添加至 viewResolvers
若是未得到到,则得到默认配置的 ViewResolver 类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)
方法,就是从 DispatcherServlet.properties
文件中读取 ViewResolver 的默认实现类,以下:
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
在 Spring Boot 不是经过这样初始化的,感兴趣的能够去看看
org.springframework.web.servlet.view.ContentNegotiatingViewResolver
,实现 ViewResolver、Ordered、InitializingBean 接口,继承 WebApplicationObjectSupport 抽象类,基于内容类型来获取对应 View 的 ViewResolver 实现类。其中,内容类型指的是 Content-Type
和拓展后缀
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean { @Nullable private ContentNegotiationManager contentNegotiationManager; /** * ContentNegotiationManager 的工厂,用于建立 {@link #contentNegotiationManager} 对象 */ private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean(); /** * 在找不到 View 对象时,返回 {@link #NOT_ACCEPTABLE_VIEW} */ private boolean useNotAcceptableStatusCode = false; /** * 默认 View 数组 */ @Nullable private List<View> defaultViews; /** * ViewResolver 数组 */ @Nullable private List<ViewResolver> viewResolvers; /** * 顺序,优先级最高 */ private int order = Ordered.HIGHEST_PRECEDENCE; }
viewResolvers
:ViewResolver 数组。对于来讲,ContentNegotiatingViewResolver 会使用这些 ViewResolver们,解析出全部的 View 们,而后基于内容类型,来获取对应的 View 们。此时的 View 结果,多是一个,多是多个,因此须要比较获取到最优的 View 对象。defaultViews
:默认 View 数组。那么此处的默认是什么意思呢?在 viewResolvers
们解析出全部的 View 们的基础上,也会添加 defaultViews
到 View 结果中order
:顺序,优先级最高。因此,这也是为何它排在最前面在上图中能够看到,在 Spring Boot 中 viewResolvers
属性有三个实现类,分别是 BeanNameViewResolver
、ViewResolverComposite
、InternalResourceViewResolver
实现 initServletContext(ServletContext servletContext)
方法,初始化 viewResolvers
属性,方法以下:
在父类 WebApplicationObjectSupport 的父类 ApplicationObjectSupport 中能够看到,由于实现了 ApplicationContextAware 接口,则在初始化该 Bean 的时候会调用
setApplicationContext(@Nullable ApplicationContext context)
方法,在这个方法中会调用initApplicationContext(ApplicationContext context)
这个方法,这个方法又会调用initServletContext(ServletContext servletContext)
方法
@Override protected void initServletContext(ServletContext servletContext) { // <1> 扫描全部 ViewResolver 的 Bean 们 Collection<ViewResolver> matchingBeans = BeanFactoryUtils. beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values(); // <1.1> 状况一,若是 viewResolvers 为空,则将 matchingBeans 做为 viewResolvers 。 // BeanNameViewResolver、ThymeleafViewResolver、ViewResolverComposite、InternalResourceViewResolver if (this.viewResolvers == null) { this.viewResolvers = new ArrayList<>(matchingBeans.size()); for (ViewResolver viewResolver : matchingBeans) { if (this != viewResolver) { // 排除本身 this.viewResolvers.add(viewResolver); } } } // <1.2> 状况二,若是 viewResolvers 非空,则和 matchingBeans 进行比对,判断哪些未进行初始化,进行初始化 else { for (int i = 0; i < this.viewResolvers.size(); i++) { ViewResolver vr = this.viewResolvers.get(i); // 已存在在 matchingBeans 中,说明已经初始化,则直接 continue if (matchingBeans.contains(vr)) { continue; } // 不存在在 matchingBeans 中,说明还未初始化,则进行初始化 String name = vr.getClass().getName() + i; obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name); } } // <1.3> 排序 viewResolvers 数组 AnnotationAwareOrderComparator.sort(this.viewResolvers); // <2> 设置 cnmFactoryBean 的 servletContext 属性 this.cnmFactoryBean.setServletContext(servletContext); }
扫描全部 ViewResolver 的 Bean 们 matchingBeans
viewResolvers
为空,则将 matchingBeans
做为 viewResolvers
viewResolvers
非空,则和 matchingBeans
进行比对,判断哪些未进行初始化,进行初始化viewResolvers
数组设置 cnmFactoryBean
的 servletContext
属性为当前 Servlet 上下文
由于 ContentNegotiatingViewResolver 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工做,方法以下:
@Override public void afterPropertiesSet() { // 若是 contentNegotiationManager 为空,则进行建立 if (this.contentNegotiationManager == null) { this.contentNegotiationManager = this.cnmFactoryBean.build(); } if (this.viewResolvers == null || this.viewResolvers.isEmpty()) { logger.warn("No ViewResolvers configured"); } }
实现 resolveViewName(String viewName, Locale locale)
方法,代码以下:
@Override @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); // <1> 得到 MediaType 数组 List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); if (requestedMediaTypes != null) { // <2> 得到匹配的 View 数组 List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); // <3> 筛选最匹配的 View 对象 View bestView = getBestView(candidateViews, requestedMediaTypes, attrs); // 若是筛选成功,则返回 if (bestView != null) { return bestView; } } String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : ""; // <4> 若是匹配不到 View 对象,则根据 useNotAcceptableStatusCode ,返回 NOT_ACCEPTABLE_VIEW 或 null if (this.useNotAcceptableStatusCode) { if (logger.isDebugEnabled()) { logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo); } return NOT_ACCEPTABLE_VIEW; } else { logger.debug("View remains unresolved" + mediaTypeInfo); return null; } }
调用 getMediaTypes(HttpServletRequest request)
方法,得到 MediaType 数组,详情见下文
调用 getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
方法,得到匹配的 View 数组,详情见下文
调用 getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs)
方法,筛选出最匹配的 View 对象,若是筛选成功则直接返回,详情见下文
若是匹配不到 View 对象,则根据 useNotAcceptableStatusCode
,返回 NOT_ACCEPTABLE_VIEW
或 null
,其中NOT_ACCEPTABLE_VIEW
变量,代码以下:
private static final View NOT_ACCEPTABLE_VIEW = new View() { @Override @Nullable public String getContentType() { return null; } @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); } };
这个 View 对象设置状态码为 406
getCandidateViews(HttpServletRequest request)
方法,得到 MediaType 数组,以下:
@Nullable protected List<MediaType> getMediaTypes(HttpServletRequest request) { Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set"); try { // 建立 ServletWebRequest 对象 ServletWebRequest webRequest = new ServletWebRequest(request); // 从请求中,得到可接受的 MediaType 数组。默认实现是,从请求头 ACCEPT 中获取 List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest); // 得到可产生的 MediaType 数组 List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request); // 经过 acceptableTypes 来比对,将符合的 producibleType 添加到 mediaTypesToUse 结果数组中 Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>(); for (MediaType acceptable : acceptableMediaTypes) { for (MediaType producible : producibleMediaTypes) { if (acceptable.isCompatibleWith(producible)) { compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible)); } } } // 按照 MediaType 的 specificity、quality 排序 List<MediaType> selectedMediaTypes = new ArrayList<>(compatibleMediaTypes); MediaType.sortBySpecificityAndQuality(selectedMediaTypes); return selectedMediaTypes; } catch (HttpMediaTypeNotAcceptableException ex) { if (logger.isDebugEnabled()) { logger.debug(ex.getMessage()); } return null; } }
getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
方法,得到匹配的 View 数组,以下:
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception { // 建立 View 数组 List<View> candidateViews = new ArrayList<>(); // <1> 来源一,经过 viewResolvers 解析出 View 数组结果,添加到 candidateViews 中 if (this.viewResolvers != null) { Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set"); // <1.1> 遍历 viewResolvers 数组 for (ViewResolver viewResolver : this.viewResolvers) { // <1.2> 状况一,得到 View 对象,添加到 candidateViews 中 View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { candidateViews.add(view); } // <1.3> 状况二,带有拓展后缀的方式,得到 View 对象,添加到 candidateViews 中 for (MediaType requestedMediaType : requestedMediaTypes) { // <1.3.2> 得到 MediaType 对应的拓展后缀的数组(默认状况下未配置) List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); // <1.3.3> 遍历拓展后缀的数组 for (String extension : extensions) { // <1.3.4> 带有拓展后缀的方式,得到 View 对象,添加到 candidateViews 中 String viewNameWithExtension = viewName + '.' + extension; view = viewResolver.resolveViewName(viewNameWithExtension, locale); if (view != null) { candidateViews.add(view); } } } } } // <2> 来源二,添加 defaultViews 到 candidateViews 中 if (!CollectionUtils.isEmpty(this.defaultViews)) { candidateViews.addAll(this.defaultViews); } return candidateViews; }
来源一,经过 viewResolvers
解析出 View 数组结果,添加到 List<View> candidateViews
中
viewResolvers
数组candidateViews
中List<MediaType> requestedMediaTypes
,将带有拓展后缀的类型再经过当前 ViewResolver 实现类得到 View 对象,添加到 candidateViews
中candidateViews
中来源二,添加 defaultViews
到 candidateViews
中
getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs)
方法,筛选出最匹配的 View 对象,以下:
@Nullable private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) { // <1> 遍历 candidateView 数组,若是有重定向的 View 类型,则返回它 for (View candidateView : candidateViews) { if (candidateView instanceof SmartView) { SmartView smartView = (SmartView) candidateView; if (smartView.isRedirectView()) { return candidateView; } } } // <2> 遍历 MediaType 数组(MediaTy数组已经根据pespecificity、quality进行了排序) for (MediaType mediaType : requestedMediaTypes) { // <2> 遍历 View 数组 for (View candidateView : candidateViews) { if (StringUtils.hasText(candidateView.getContentType())) { // <2.1> 若是 MediaType 类型匹配,则返回该 View 对象 MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType()); if (mediaType.isCompatibleWith(candidateContentType)) { if (logger.isDebugEnabled()) { logger.debug("Selected '" + mediaType + "' given " + requestedMediaTypes); } attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, RequestAttributes.SCOPE_REQUEST); return candidateView; } } } } return null; }
candidateView
数组,若是有重定向的 View 类型,则返回它。也就是说,重定向的 View ,优先级更高。pespecificity
、quality
进行了排序)和 candidateView
数组
viewResolvers
的位置,越靠前,优先级越高。org.springframework.web.servlet.view.BeanNameViewResolver
,实现 ViewResolver、Ordered 接口,继承 WebApplicationObjectSupport 抽象类,基于 Bean 的名字得到 View 对象的 ViewResolver 实现类
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered { /** * 顺序,优先级最低 */ private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered }
实现 resolveViewName(String viewName, Locale locale)
方法,根据名称获取 View 类型对应的 Bean(View 对象),以下:
@Override @Nullable public View resolveViewName(String viewName, Locale locale) throws BeansException { ApplicationContext context = obtainApplicationContext(); // 若是对应的 Bean 对象不存在,则返回 null if (!context.containsBean(viewName)) { // Allow for ViewResolver chaining... return null; } // 若是 Bean 对应的 Bean 类型不是 View ,则返回 null if (!context.isTypeMatch(viewName, View.class)) { if (logger.isDebugEnabled()) { logger.debug("Found bean named '" + viewName + "' but it does not implement View"); } // Since we're looking into the general ApplicationContext here, // let's accept this as a non-match and allow for chaining as well... return null; } // 得到 Bean 名字对应的 View 对象 return context.getBean(viewName, View.class); }
org.springframework.web.servlet.view.ViewResolverComposite
,实现 ViewResolver、Ordered、InitializingBean、ApplicationContextAware、ServletContextAware 接口,复合的 ViewResolver 实现类
public class ViewResolverComposite implements ViewResolver, Ordered, InitializingBean, ApplicationContextAware, ServletContextAware { /** * ViewResolver 数组 */ private final List<ViewResolver> viewResolvers = new ArrayList<>(); /** * 顺序,优先级最低 */ private int order = Ordered.LOWEST_PRECEDENCE; }
由于 ViewResolverComposite 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工做,方法以下:
@Override public void afterPropertiesSet() throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { if (viewResolver instanceof InitializingBean) { ((InitializingBean) viewResolver).afterPropertiesSet(); } } }
实现 resolveViewName(String viewName, Locale locale)
方法,代码以下:
@Override @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { // 遍历 viewResolvers 数组,逐个进行解析,但凡成功,则返回该 View 对象 for (ViewResolver viewResolver : this.viewResolvers) { // 执行解析 View view = viewResolver.resolveViewName(viewName, locale); // 解析成功,则返回该 View 对象 if (view != null) { return view; } } return null; }
org.springframework.web.servlet.view.AbstractCachingViewResolver
,实现 ViewResolver 接口,继承 WebApplicationObjectSupport 抽象类,提供通用的缓存的 ViewResolver 抽象类。对于相同的视图名,返回的是相同的 View 对象,因此经过缓存,能够进一步提供性能。
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver { /** Default maximum number of entries for the view cache: 1024. */ public static final int DEFAULT_CACHE_LIMIT = 1024; /** Dummy marker object for unresolved views in the cache Maps. */ private static final View UNRESOLVED_VIEW = new View() { @Override @Nullable public String getContentType() { return null; } @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { } }; /** The maximum number of entries in the cache. */ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; // 缓存上限。若是 cacheLimit = 0 ,表示禁用缓存 /** Whether we should refrain from resolving views again if unresolved once. */ private boolean cacheUnresolved = true; // 是否缓存空 View 对象 /** Fast access cache for Views, returning already cached instances without a global lock. */ private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT); // View 的缓存的映射 /** Map from view key to View instance, synchronized for View creation. */ // View 的缓存的映射。相比 {@link #viewAccessCache} 来讲,增长了 synchronized 锁 @SuppressWarnings("serial") private final Map<Object, View> viewCreationCache = new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) { if (size() > getCacheLimit()) { viewAccessCache.remove(eldest.getKey()); return true; } else { return false; } } }; }
经过 viewAccessCache
属性,提供更快的访问 View 缓存
经过 viewCreationCache
属性,提供缓存的上限的功能
KEY 是经过 getCacheKey(String viewName, Locale locale)
方法,得到缓存 KEY,方法以下:
protected Object getCacheKey(String viewName, Locale locale) { return viewName + '_' + locale; }
实现 resolveViewName(String viewName, Locale locale)
方法,代码以下:
@Override @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { // 若是禁用缓存,则建立 viewName 对应的 View 对象 if (!isCache()) { return createView(viewName, locale); } else { // 得到缓存 KEY Object cacheKey = getCacheKey(viewName, locale); // 从 viewAccessCache 缓存中,得到 View 对象 View view = this.viewAccessCache.get(cacheKey); // 若是得到不到缓存,则从 viewCreationCache 中,得到 View 对象 if (view == null) { synchronized (this.viewCreationCache) { // 从 viewCreationCache 中,得到 View 对象 view = this.viewCreationCache.get(cacheKey); if (view == null) { // Ask the subclass to create the View object. // 建立 viewName 对应的 View 对象 view = createView(viewName, locale); // 若是建立失败,可是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } // 若是 view 非空,则添加到 viewAccessCache 缓存中 if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } } } } else { if (logger.isTraceEnabled()) { logger.trace(formatKey(cacheKey) + "served from cache"); } } return (view != UNRESOLVED_VIEW ? view : null); } } @Nullable protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); } @Nullable protected abstract View loadView(String viewName, Locale locale) throws Exception;
逻辑比较简单,主要是缓存的处理,须要经过子类去建立对应的 View 对象
org.springframework.web.servlet.view.UrlBasedViewResolver
,实现 Ordered 接口,继承 AbstractCachingViewResolver 抽象类,基于 Url 的 ViewResolver 实现类
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered { public static final String REDIRECT_URL_PREFIX = "redirect:"; public static final String FORWARD_URL_PREFIX = "forward:"; /** * View 的类型,不一样的实现类,会对应一个 View 的类型 */ @Nullable private Class<?> viewClass; /** * 前缀 */ private String prefix = ""; /** * 后缀 */ private String suffix = ""; /** * ContentType 类型 */ @Nullable private String contentType; private boolean redirectContextRelative = true; private boolean redirectHttp10Compatible = true; @Nullable private String[] redirectHosts; /** * RequestAttributes 暴露给 View 使用时的属性 */ @Nullable private String requestContextAttribute; /** Map of static attributes, keyed by attribute name (String). */ private final Map<String, Object> staticAttributes = new HashMap<>(); /** * 是否暴露路径变量给 View 使用 */ @Nullable private Boolean exposePathVariables; @Nullable private Boolean exposeContextBeansAsAttributes; @Nullable private String[] exposedContextBeanNames; /** * 是否只处理指定的视图名们 */ @Nullable private String[] viewNames; /** * 顺序,优先级最低 */ private int order = Ordered.LOWEST_PRECEDENCE; }
实现 initApplicationContext()
方法,进一步初始化,代码以下:
在父类 WebApplicationObjectSupport 的父类 ApplicationObjectSupport 中能够看到,由于实现了 ApplicationContextAware 接口,则在初始化该 Bean 的时候会调用
setApplicationContext(@Nullable ApplicationContext context)
方法,在这个方法中会调用initApplicationContext(ApplicationContext context)
这个方法,这个方法又会调用initApplicationContext()
方法
@Override protected void initApplicationContext() { super.initApplicationContext(); if (getViewClass() == null) { throw new IllegalArgumentException("Property 'viewClass' is required"); } }
在子类中会看到 viewClass
属性通常会在构造中法中设置
重写 getCacheKey(String viewName, Locale locale)
方法,忽略 locale
参数,仅仅使用 viewName
做为缓存 KEY,以下:
@Override protected Object getCacheKey(String viewName, Locale locale) { // 重写了父类的方法,去除locale直接返回viewName return viewName; }
也就是说,不支持 Locale 特性
canHandle(String viewName, Locale locale)
方法,判断传入的视图名是否能够被处理,以下:
protected boolean canHandle(String viewName, Locale locale) { String[] viewNames = getViewNames(); return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName)); } @Nullable protected String[] getViewNames() { return this.viewNames; }
通常状况下,viewNames
指定的视图名们为空,因此会知足 viewNames == null 代码块。也就说,全部视图名均可以被处理
applyLifecycleMethods(String viewName, AbstractUrlBasedView view)
方法,代码以下:
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) { // 状况一,若是 viewName 有对应的 View Bean 对象,则使用它 ApplicationContext context = getApplicationContext(); if (context != null) { Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName); if (initialized instanceof View) { return (View) initialized; } } // 状况二,直接返回 view return view; }
重写 createView(String viewName, Locale locale)
方法,增长了对 REDIRECT、FORWARD 的状况的处理,以下:
@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. // 是否能处理该视图名称 if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { // 若是是 REDIRECT 开头,建立 RedirectView 视图 String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); String[] hosts = getRedirectHosts(); if (hosts != null) { // 设置 RedirectView 对象的 hosts 属性 view.setHosts(hosts); } // 应用 return applyLifecycleMethods(REDIRECT_URL_PREFIX, view); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { // 若是是 FORWARD 开头,建立 InternalResourceView 视图 String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); InternalResourceView view = new InternalResourceView(forwardUrl); // 应用 return applyLifecycleMethods(FORWARD_URL_PREFIX, view); } // Else fall back to superclass implementation: calling loadView. // 建立视图名对应的 View 对象 return super.createView(viewName, locale); }
实现 loadView(String viewName, Locale locale)
方法,加载 viewName 对应的 View 对象,方法以下:
@Override protected View loadView(String viewName, Locale locale) throws Exception { // <x> 建立 viewName 对应的 View 对象 AbstractUrlBasedView view = buildView(viewName); // 应用 View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); }
其中,<x>
处,调用 buildView(String viewName)
方法,建立 viewName
对应的 View 对象,方法以下:
protected AbstractUrlBasedView buildView(String viewName) throws Exception { Class<?> viewClass = getViewClass(); Assert.state(viewClass != null, "No view class"); // 建立 AbstractUrlBasedView 对象 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); // 设置各类属性 view.setUrl(getPrefix() + viewName + getSuffix()); String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); Boolean exposePathVariables = getExposePathVariables(); if (exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); if (exposeContextBeansAsAttributes != null) { view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); } String[] exposedContextBeanNames = getExposedContextBeanNames(); if (exposedContextBeanNames != null) { view.setExposedContextBeanNames(exposedContextBeanNames); } return view; }
requiredViewClass()
方法,定义了产生的视图,代码以下:
protected Class<?> requiredViewClass() { return AbstractUrlBasedView.class; }
org.springframework.web.servlet.view.InternalResourceViewResolver
,继承 UrlBasedViewResolver 类,解析出 JSP 的 ViewResolver 实现类
public class InternalResourceViewResolver extends UrlBasedViewResolver { /** * 判断 javax.servlet.jsp.jstl.core.Config 是否存在 */ private static final boolean jstlPresent = ClassUtils.isPresent( "javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader()); @Nullable private Boolean alwaysInclude; public InternalResourceViewResolver() { // 得到 viewClass Class<?> viewClass = requiredViewClass(); if (InternalResourceView.class == viewClass && jstlPresent) { viewClass = JstlView.class; } // 设置 viewClass setViewClass(viewClass); } }
从构造方法中,能够看出,视图名会是 InternalResourceView 或 JstlView 类。😈 实际上,JstlView 是 InternalResourceView 的子类。
重写 buildView(String viewName)
方法,代码以下:
@Override protected AbstractUrlBasedView buildView(String viewName) throws Exception { // 调用父方法 InternalResourceView view = (InternalResourceView) super.buildView(viewName); if (this.alwaysInclude != null) { view.setAlwaysInclude(this.alwaysInclude); } // 设置 View 对象的相关属性 view.setPreventDispatchLoop(true); return view; }
设置两个属性
org.springframework.web.servlet.View
,Spring MVC 中的视图对象,用于视图渲染,代码以下:
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"; @Nullable default String getContentType() { return null; } /** * 渲染视图 */ void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
View 接口体系的结构以下:
能够看到 View 的实现类很是多,本文不会详细分析,简单讲解两个方法
在 DispatcherServlet 中会直接调用 View 的 render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
来进行渲染页面
// AbstractView.java @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 合并返回结果,将 Model 中的静态数据和请求中的动态数据进行合并 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); // 进行一些准备工做(修复 IE 中存在的 BUG) prepareResponse(request, response); // 进行渲染 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }
将 Model 对象与请求中的数据进行合并,生成一个 Map 对象,保存进入页面的一些数据
进行一些准备工做(修复 IE 中存在的 BUG)
调用 renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
方法,页面渲染,以下:
// InternalResourceView.java @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. // 往请求中设置一些属性,Locale、TimeZone、LocalizationContext exposeHelpers(request); // Determine the path for the request dispatcher. // 获取须要转发的路径 String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). // 获取请求转发器 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } // 最后进行转发 rd.forward(request, response); } }
是否是很熟悉?😈
经过 Servlet 的 javax.servlet.RequestDispatcher
请求派发着,转到对应的 URL
本文分析了 ViewResolver
组件,视图解析器,根据视图名和国际化,得到最终的视图 View 对象。Spring MVC 执行完处理器后生成一个 ModelAndView 对象,若是该对象不为 null
而且有对应的 viewName
,那么就须要经过 ViewResolver
根据 viewName
解析出对应的 View 对象。
在 Spring MVC 和 Spring Boot 中最主要的仍是 InternalResourceViewResolver
实现类,例如这么配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 自动给后面 action 的方法 return 的字符串加上前缀和后缀,变成一个可用的地址 --> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>
当返回的视图名称为 login
时,View 对象的 url
就是 /WEB-INF/jsp/login.jsp
,调用 View 的 render
方法进行页面渲染时,请求会转发到这个 url
固然,还有其余的 ViewResolver
实现类,例如 BeanNameViewResolver
,目前大多数都是先后端分离的项目,这个组件也许你不多用到
至此,《精尽 Spring MVC 源码分析》系列最后一篇文档已经讲述完了,笔者写的过程当中也是懵懵懂懂的状态,写完以后才更加的理解,对于 Spring MVC 中大部分的内容都有分析到,你会发现 Spring MVC 原来是这么回事,开心~ 😄😄😄 其中涉及到 Spring 相关内容在努力阅读中,敬请期待~
但愿这系列文档可以帮助你对 Spring MVC 的理解,路漫漫其修远兮~
参考文章:芋道源码《精尽 Spring MVC 源码分析》