SpringMVC之原始碼分析--ViewResolver(五)

weixin_33840661發表於2018-06-07

概述

通過上幾篇的學習,我們分析了並試驗了ViewResolverComposite、BeanNameViewResolver和ContentNegotiatingViewResolver,這三個類都直接實現ViewResolver介面。Spring MVC提供了很多的ViewResolver實現,本章我們繼續分析比較常用的幾個檢視解析器。

本系列文章是基於Spring5.0.5RELEASE。

AbstractCachingViewResolver

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的核心程式碼。簡單說,本類就是實現了檢視解析的快取功能。

UrlBasedViewResolver

該類是ViewResolver介面的一種實現,並繼承了AbstractCachingViewResolver抽象類,通過指定prefix字首和suffix字尾,然後拼接邏輯檢視名稱加上字首和字尾的方式確定檢視URL。

UrlBasedViewResolver支援返回檢視名稱中包括redirect:字首,以支援在客戶端的跳轉。比如當訪問一個url"/demo",該url對應的handler返回的邏輯檢視名為"redirect:/demo1",URLBasedViewResolver在建立檢視時(createView方法中),判斷邏輯檢視名稱的字首是"redirect:"開頭,接著裁剪掉"redirect:"字首後,建立RedirectView物件,RedirectView物件將把請求返回的模型資料組合成查詢引數形式拼接到redirect的URL後面,然後呼叫 HttpServletResponse 物件的 sendRedirect 方法進行重定向。(稍後我們實踐驗證)

同樣的,URLBasedViewResolver還支援"forword:"字首,然後封裝成一個 InternalResourceView 物件,伺服器端利用 RequestDispatcher 的 forword 方式跳轉到指定的地址。

說了這麼多,我們看下原始碼是如何實現的,如下:

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

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一樣。

實戰練習

  • UrlBaseViewResolver

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後面。

  • InternalResourceViewResolver

此解析器與UrlBasedViewResolver差不多,更改下配置檔案中的類全路徑即可。

總結

本章介紹了AbstractCachingViewResolver、UrlBasedViewResolver以及InternalResourceViewResolver三個檢視解析器。這部分內容有點兒多,我會盡快結束。希望能幫到大家,謝謝!

最後建立了qq群方便大家交流,可掃描加入,同時也可加我qq:276420284,共同學習、共同進步,謝謝!

相關文章