Spring MVC組件之ViewResolver

Spring MVC組件之ViewResolver

ViewResolver概述

ViewResolver組件的作用主要根據視圖名和本地化的Locale,來解析出相應的視圖View對象。
在SpringMvc的源碼中,ViewResolver是一個接口。該接口主要定義了一個方法。
? View resolveViewName(String viewName, Locale locale)
接口方法中需要viewName 和locale兩個參數(shù),viewName是視圖名稱,locale是本地化的參數(shù)。一般情況下,我們只需要通過viewName來找到相應的視圖。在國際化的場景中,可以根據不同的locale來展示不同語言的視圖。

ViewResolver類圖

ViewResolver.png

從以上類圖中可以看出,AbstractCachingViewResolver,BeanNameViewResolver,ContentNegotiatingViewResolver,ViewResolverComposite這四個類直接繼承至 ViewResolver接口,都只有一層繼承結構。
只有AbstractCachingViewResolver類的繼承關系相對來說復雜一些,AbstractCachingViewResolver類可以緩存已經解析過的視圖。

AbstractCachingViewResolver系列

AbstractCachingViewResolver

AbstractCachingViewResolver類是一個抽象類。它提供了對View的緩存功能,當視圖解析過一次就被緩存起來, 直到緩存的視圖被刪除前,視圖解析都是自動從緩存中獲取的。
AbstractCachingViewResolver類繼承了WebApplicationObjectSupport父類,實現(xiàn)了ViewResolver接口。
AbstractCachingViewResolver類實現(xiàn)了ViewResolver接口的resolveViewName方法。

    @Override
    @Nullable
    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);
        }
    }

resolveViewName方法中主要做了以下幾件事情。

  1. 首先要判斷是否開啟緩存。如果沒有開啟緩存,就直接調用createView(viewName, locale)方法,來創(chuàng)建一個視圖。
  2. 如果有開啟緩存,首先獲取視圖緩存的key值。然后再通過key值,在viewAccessCache緩存中獲取視圖。如果視圖存在就直接返回。
  3. 如果viewAccessCache緩存中不存在該視圖,則到viewCreationCache緩存中去找這個視圖,找到就返回該視圖。
  4. 如果viewCreationCache緩存中也不存在該視圖,則再調用createView(viewName, locale)方法來創(chuàng)建一個視圖返回。并把創(chuàng)建的視圖放到viewAccessCache和viewCreationCache的緩存中。

在createView方法中,其實又調用了loadView方法。loadView方法在AbstractCachingViewResolver類中只是一個抽象方法,專門留給子類來實現(xiàn)自身的業(yè)務邏輯。

AbstractCachingViewResolver類的緩存
在AbstractCachingViewResolver類中,使用了viewAccessCache和viewCreationCache兩個HashMap來做緩存。
viewAccessCache是ConcurrentHashMap的類型。ConcurrentHashMap是一個支持高并發(fā),線程安全,高效的哈希表。
viewCreationCache是LinkedHashMap類型的。LinkedHashMap是HashMap和雙向鏈表的組合,它通過維護一個雙向鏈表保證了值是有序的。LinkedHashMap中有一個removeEldestEntry方法。該方法返回一個boolearn值,當返回值為true的時候,LinkedHashMap會自動刪除最前面的一個Map值。該方法是在put和putAll方法被調用是被觸發(fā)。
在AbstractCachingViewResolver類中,是這樣定義viewCreationCache的。viewCreationCache的重寫了父類的removeEldestEntry方法。在該方法中會判斷視圖的數(shù)量是否超過了限制。如果已超過限制,就會先在viewAccessCache緩存刪除掉最先緩存的視圖。然后返回true, viewCreationCache就會制動刪除這個視圖。這樣就將兩種類型的map各自優(yōu)勢結合起來了。

@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;
                    }
                }
            };

UrlBasedViewResolver

UrlBasedViewResolver直接繼承了AbstractCachingViewResolver類。它是對viewResolvers接口一個比較基本的實現(xiàn)。它提供了一種URL拼接的方式來解析視圖。
UrlBasedViewResolver類可以通過prefix屬性和suffix屬性,來分別設置一個前綴和后綴,最后視圖的URL就是前綴+邏輯視圖+后綴。
它主要重寫了父類的getCacheKey,createView,loadView三個方法。
? getCacheKey方法

@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName;
}

getCacheKey方法直接返回viewName,舍棄了locale這個參數(shù)??梢奤rlBasedViewResolver類是不支持Locale。

? createView

    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)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl,
                    isRedirectContextRelative(), isRedirectHttp10Compatible());
            String[] hosts = getRedirectHosts();
            if (hosts != null) {
                view.setHosts(hosts);
            }
            return applyLifecycleMethods(REDIRECT_URL_PREFIX, 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);
    }

createView方法主要做了以下四件事情。

  1. 在canHandle方法中,通過視圖名稱來檢查視圖是否能被解析。
  2. 判斷視圖名稱是否是以“redirect:”開頭,如果是,則創(chuàng)建一個RedirectView類型的視圖返回。RedirectView視圖是用于重定向跳轉。
  3. 再判斷視圖名稱是否是以“forward:”開頭,如果是,則創(chuàng)建一個InternalResourceView類型的forward視圖返回。forward視圖是用于轉發(fā)操作。
  4. 如果不是以上的3種情況,則調用父類的createView方法。父類的createView方法又會調用loadView方法。

? loadView方法

    @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);
    }

在loadView方法主要做了三件事情。

  1. 使用buildView方法創(chuàng)建一個視圖。在buildView方法中根據視圖的類型,使用了org.springframework.beans.BeanUtils.instantiateClass方法,創(chuàng)建了一個AbstractUrlBasedView類型的實例。AbstractUrlBasedView的實例創(chuàng)建好后,再對這個實例進行一些屬性的設置。
    視圖中的url屬性就是在這里設置的,url就是前綴加視圖名再加后綴組合而成的。
  2. 使用applyLifecycleMethods方法,對創(chuàng)建的視圖進行初始化的設置。
  3. 判斷視圖模板是否存在,如存在則返回,不存在就返回NULL值。

UrlBasedViewResolver實現(xiàn)了視圖解析的基本功能,作為UrlBasedViewResolver類的子類只需要重寫父類的兩個方法,便可實現(xiàn)自己的功能。
? 重寫了父類的requiredViewClass方法,在這個方法中返回自己的類型。
? 類重寫了父類的buildView方法,先調用父類的buildView方法得到一個View對象后,再對該對設置自己獨有的屬性。

InternalResourceViewResolver

InternalResourceViewResolver類繼承至UrlBasedViewResolver類。Spring MVC容器在初始化ViewResolver類型的組件時,初始化的就有InternalResourceViewResolver組件。在實際應用中InternalResourceViewResolver也是使用的最廣泛的一個視圖解析器。InternalResourceViewResolver會把返回的視圖名稱都解析為InternalResourceView對象。
InternalResourceViewResolver類重寫了父類的requiredViewClass方法,返回的是InternalResourceView類型。
InternalResourceViewResolver類重寫了父類的buildView方法,在buildView方法中,先調用父類的方法構建一個InternalResourceView對象。再設置對象InternalResourceView的alwaysInclude,preventDispatchLoop的屬性值。

ViewResolverComposite

ViewResolverComposite類繼承至ViewResolver接口。從ViewResolverComposite類名稱可以知道它是一系列ViewResolver的組合。
ViewResolverComposite類中有一個屬性是viewResolvers。它是一個ViewResolver類型的集合。我們可以對這個屬性值進行設置。
ViewResolverComposite類實現(xiàn)了ViewResolver接口的resolveViewName方法。在resolveViewName方法中,程序會循環(huán)遍歷viewResolvers集合,針對集合中的每一個ViewResolver的實例。調用每一個實例的resolveViewName方法,直到得到一個View為止。代碼如下所示:

    @Override
    @Nullable
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
        return null;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容