經過上幾篇的學習,咱們分析了並試驗了ViewResolverComposite、BeanNameViewResolver和ContentNegotiatingViewResolver,這三個類都直接實現ViewResolver接口。Spring MVC提供了不少的ViewResolver實現,本章咱們繼續分析比較經常使用的幾個視圖解析器。web
本系列文章是基於Spring5.0.5RELEASE。spring
AbstractCachingViewResolver實現ViewResolver接口的抽象類,從類名可知,該類具備緩存功能,即緩存解析過的視圖View對象,後續須要視圖解析時,會先從緩存中查找,若是找到對應的視圖就直接返回,若是未找到就建立一個視圖對象放入緩存Map中,並返回建立對象。從其實現原理上來看,此類視圖解析器的性能是最佳的。瀏覽器
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver { /** 緩存視圖map的初始大小 */ public static final int DEFAULT_CACHE_LIMIT = 1024; /** 最大緩存數量 */ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; /** 解析過的View緩存容器,key是邏輯視圖名,value是視圖View對象 */ private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT); /** 存儲全部建立過的視圖,本容器裏的內容大於等於viewAccessCache中的內容 key是邏輯視圖名,value是視圖View對象 */ 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()) { // 移除緩存容器中最老的View對象 viewAccessCache.remove(eldest.getKey()); return true; } else { return false; } } }; /** 實現ViewResolver接口resolveViewName方法 */ @Override @Nullable public View resolveViewName(String viewName, Locale locale) throws Exception { // 緩存開關(默認開啓)。經過屬性cacheLimit的值控制,大於0開啓緩存,小於0關閉緩存 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) { // 調用子類建立視圖View對象 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 + "]"); } } } } } // 返回View對象 return (view != UNRESOLVED_VIEW ? view : null); } } ... ... }
以上是AbstractCachingViewResolver的核心代碼。簡單說,本類就是實現了視圖解析的緩存功能。緩存
該類是ViewResolver接口的一種實現,並繼承了AbstractCachingViewResolver抽象類,經過指定prefix前綴和suffix後綴,而後拼接邏輯視圖名稱加上前綴和後綴的方式肯定視圖URL。服務器
UrlBasedViewResolver支持返回視圖名稱中包括redirect:前綴,以支持在客戶端的跳轉。好比當訪問一個url"/demo",該url對應的handler返回的邏輯視圖名爲"redirect:/demo1",URLBasedViewResolver在建立視圖時(createView方法中),判斷邏輯視圖名稱的前綴是"redirect:"開頭,接着裁剪掉"redirect:"前綴後,建立RedirectView對象,RedirectView對象將把請求返回的模型數據組合成查詢參數形式拼接到redirect的URL後面,而後調用 HttpServletResponse 對象的 sendRedirect 方法進行重定向。(稍後咱們實踐驗證)app
一樣的,URLBasedViewResolver還支持"forword:"前綴,而後封裝成一個 InternalResourceView 對象,服務器端利用 RequestDispatcher 的 forword 方式跳轉到指定的地址。jsp
說了這麼多,咱們看下源碼是如何實現的,以下:ide
public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered { ... ... /** 建立View實例 */ @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; } // 處理redirect請求 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { // 去掉redirect:前綴 String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); // 根據redirectUrl建立RedirectView實例 RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); String[] hosts = getRedirectHosts(); if (hosts != null) { view.setHosts(hosts); } return applyLifecycleMethods(viewName, view); } // 處理forward請求 if (viewName.startsWith(FORWARD_URL_PREFIX)) { 去掉forward:前綴 String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } // Else fall back to superclass implementation: calling loadView. // 處理普通view(除redirect/forward) // 調用父類createView方法,而後經過模板方法再調回本例的loadView方法 return super.createView(viewName, locale); } @Override protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); } ... ... }
InternalResourceViewResolver繼承UrlBasedViewResolver,故UrlBaseViewResolver具備的功能,InternalResourceViewResolver一樣具有,在實際項目中也是使用最普遍的一種視圖解析器。InternalResourceViewResolver會把返回的視圖對象解析爲InternalResourceView 對象,InternalResourceView 會把 Controller 處理器方法返回的模型屬性都存放到對應的 request 屬性中,而後經過 RequestDispatcher 在服務器端把請求 forword 重定向到目標 URL。代碼以下:函數
public class InternalResourceViewResolver extends UrlBasedViewResolver { ... ... /** 構造函數 */ public InternalResourceViewResolver() { // 獲取InternalResourceView Class<?> viewClass = requiredViewClass(); if (InternalResourceView.class == viewClass && jstlPresent) { viewClass = JstlView.class; } setViewClass(viewClass); } @Override protected Class<?> requiredViewClass() { return InternalResourceView.class; } ... ... }
其餘功能都與UrlBasedViewResolver同樣。性能
Spring配置文件代碼以下:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <!-- 指定前綴 --> <property name="prefix" value="/WEB-INF/jsp/"/> <!-- 指定後綴 --> <property name="suffix" value=".jsp"/> <!-- 是否使用緩存,大於0是表示啓用 --> <property name="cacheLimit" value="0"/> </bean>
Controller代碼以下:
@Controller public class DemoController { /** 測試redirect */ @GetMapping("/redirect") public String redirect(ModelMap modelMap){ modelMap.put("name","daliang"); modelMap.put("pass","111"); return "redirect:/demo"; } /** 測試forward */ @GetMapping("/forward") public String forward(ModelMap modelMap){ modelMap.put("name","daliang"); modelMap.put("pass","111"); return "forward:/demo"; } @GetMapping("/demo") public String demo(){ return "test"; } }
啓動應用,在瀏覽器地址欄輸入http://localhost:8088/redirect,回車後以下:
可見參數拼接到了url後面。
此解析器與UrlBasedViewResolver差很少,更改下配置文件中的類全路徑便可。
本章介紹了AbstractCachingViewResolver、UrlBasedViewResolver以及InternalResourceViewResolver三個視圖解析器。這部份內容有點兒多,我會盡快結束。但願能幫到你們,謝謝!
最後建立了qq羣方便你們交流,可掃描加入,同時也可加我qq:276420284,共同窗習、共同進步,謝謝!