1. 程式人生 > >SpringMVC原始碼學習(四)九大元件上

SpringMVC原始碼學習(四)九大元件上

1. HandlerMapping

在dispatcherServlet,doDispatch方法中有呼叫getHandler,程式碼如下:
其中List< HandlerMapping> handlerMappings是dispatcherServlet的內部變數。
那該方法的內容就是遍歷handlerMappings,獲得符合條件的HandlerMapping,呼叫其getHandler方法,返回獲得的HandlerExecutionChain

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws
Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if
(handler != null) { return handler; } } return null; }

HandlerMapping是一個介面,內部只有一個方法和諾幹變數,它的作用是根據request找到對應的Handler。方法如下:
HandlerExecutionChain getHandler(HttpSevletRequest request) throws Exception
接下來看看一個該方法的實現,SimpleControllerHandlerAdapter,程式碼如下:

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }

另外一個要討論的就是順序問題,不同hander負責對映的條件可能有重複的,這時候就需要定義不同的HandlerMapping執行的順序,這裡的順序可以通過實現Order介面,通過Order屬性定義。order越小越先使用。如:

<bean class="com.excelib.TudouHandlerMapping"
    p:order="1"/>
<bean class="com.excelib.TudoupianHandlerMapping"
    p:order="0"/>

2.HandlerAdapter

在dispatcherServlet通過如下方法獲得HandlerAdapter,其中List< HandlerAdapter> handlerAdapters是dispatcherServlet的成員變數,可以看到它的邏輯是遍歷所有的Adapter,然後檢查哪個可以處理當前的Handler,找到第一個可以處理Handler的Adapter後停止查詢,返回。這裡的順序同樣是通過Order屬性設定的。

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler adapter [" + ha + "]");
            }
            if (ha.supports(handler)) {
                return ha;
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

上篇文章有介紹,HandlerAdapter的角色是使用工具(handler)的人。因為handler是Object型別,需要HandlerAdapter使用它來完成一定格式要求的任務。
它是一個介面,程式碼如下:

    public interface HandlerAdapter {   
        boolean supports(Object handler);   
        ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
       long getLastModified(HttpServletRequest request, Object handler);

接下來看一個spring自己實現的示例,程式碼如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }

}

可以看到這個Adapter比較簡單,它要求handler實現了Controller介面,方法的實現是通過處理器的handleRequest方法。

3. HandlerExceptionResolver

HandlerExceptionResolver是SpringMVC中專門負責處理異常的類。它負責:
根據異常設定ModelAndView
之後交給render方法進行渲染。render只負責將Model渲染成頁面。具體ModelAndView的來源render並不關心。

public interface HandlerExceptionResolver {
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

它的結構很簡單,只有一個方法,只需要從異常解析出ModelAndView就可以了。具體實現可以維護一個異常為Key、View為value的Map,解析時直接從Map裡獲取View。

4.ViewResolver

ViewResolver用來將String型別的檢視名(也叫邏輯檢視)和Locale解析為View型別的檢視,ViewResolver介面也很簡單,程式碼如下

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;

}

這裡可以看到引數是viewName和locale,不過一般我們只要根據檢視名找到檢視,然後渲染就可以,如果需要國際化支援也只要將顯示的內容或者主題使用國際化支援。
View是用來渲染頁面的,也就是將程式返回的引數填入模板中,生成html或其他格式的檔案。所以說它會解決兩個問題:
1. 填入哪個模板?
2. 如何填入?
它會找到渲染所用的模板和所用的技術(也就是檢視的型別)進行渲染,具體的渲染過程則交給不同的檢視自己完成。
我們最常用的UrlBasedViewResolver系列的解析器都是針對單一檢視型別進行解析的。比如InternalResourceViewResolver只針對jsp型別的檢視,FreeMarkerViewResolver只針對FreeMarker,VelocityViewResolver只針對Velocity。而ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver等解析器可以同時解析多種型別的檢視。
ResourceBundleViewResolver使用properties配置檔案來進行配置解析的檔案類和url、XmlViewResolver使用xml配置。BeanNameViewResolver是根據ViewName從ApplicationContext容器中查詢相應的bean做View的,它比較簡單,原始碼如下:

public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
    public void setOrder(int order) {
        this.order = order;
    }
    @Override
    public int getOrder() {
        return this.order;
    }
    @Override
    public View resolveViewName(String viewName, Locale locale) throws BeansException {
        ApplicationContext context = getApplicationContext();
        if (!context.containsBean(viewName)) {
            if (logger.isDebugEnabled()) {
                logger.debug("No matching bean found for view name '" + viewName + "'");
            }
            // Allow for ViewResolver chaining...
            return null;
        }
        if (!context.isTypeMatch(viewName, View.class)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found matching bean for view name '" + viewName +
                        "' - to be ignored since 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;
        }
        return context.getBean(viewName, View.class);
    }

}

可以看出原理就是根據viewName從spring容器中查詢Bean,if(找不到||不是view型別) return null; else return context.getBean(viewName, View.class);
ViewResolver的使用需要註冊到Spring MVC容器中,預設使用的是org.springframework.web.servlet.view.InternalResourceViewResolver。