該系列文檔是本人在學習 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 源碼分析》