1. 程式人生 > >SpringMVC工作原理之:HandlerMapping和HandlerAdapter

SpringMVC工作原理之:HandlerMapping和HandlerAdapter

一、HandlerMapping

作用是根據當前請求的找到對應的 Handler,並將 Handler(執行程式)與一堆 HandlerInterceptor(攔截器)封裝到 HandlerExecutionChain 物件中。在 HandlerMapping 介面的內部只有一個方法,如下:

  • HandlerExecutionChain getHandler(HttpServletRequest request);

HandlerMapping 是由 DispatcherServlet 呼叫,DispatcherServlet 會從容器中取出所有 HandlerMapping 例項並遍歷,讓 HandlerMapping 例項根據自己實現類的方式去嘗試查詢 Handler,而 HandlerMapping 具體有哪些實現類下面就會詳細分析。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 這些 HandlerMapping 在容器初始化時建立,在 initHandlerMappings 時放入集合中
    for (HandlerMapping hm : this.handlerMappings) {
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

另外上面說到的 Handler 有可能是一個 HandlerMethod(封裝了 Controller 中的方法)物件,也有可能是一個 Controller 物件、 HttpRequestHandler 物件或 Servlet 物件,而這個 Handler 具體是什麼物件,也是與所使用的 HandlerMapping 實現類有關。如下圖所示,可以看到 HandlerMapping 實現類有兩個分支,分別繼承自 AbstractHandlerMethodMapping(得到 HandlerMethod)和 AbstractUrlHandlerMapping(得到 HttpRequestHandler、Controller 或 Servlet),它們又統一繼承於 AbstractHandlerMapping。

先來看一下 AbstractHandlerMapping,它實現了 HandlerMapping 介面中的 getHandler() 方法,原始碼如下所示

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 根據請求獲取執行程式,具體的獲取方式由子類決定,getHandlerInternal() 是抽象方法
    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);
    }
    // 將 Handler 與一堆攔截器包裝到 HandlerExecutionChain 物件中
    return getHandlerExecutionChain(handler, request);
}

 

可以看到在這個方法中又呼叫了 getHandlerInternal() 方法獲取到了 Handler 物件,而 Handler 物件具體內容是由它的子類去定義的。下面就來一看下 AbstractHandlerMapping 的兩個分支子類

1 AbstractUrlHandlerMapping

AbstractUrlHandlerMapping 這個分支獲取的 Handler 的型別實際就是一個 Controller 類,所以一個 Controller 只能對應一個請求(或者像 Struts2 那樣定位到方法,使同一個業務的方法放在同一個類裡),原始碼如下所示

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    // 根據當前請求獲取“查詢路徑”
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 根據路徑獲取 Handler(即Controller),先嚐試直接匹配,再嘗試模式匹配
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
       // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if ("/".equals(lookupPath)) {
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    return handler;
}

1.1 AbstractUrlHandlerMapping 實現類及使用

1) ControllerClassNameHandlerMapping:根據類名訪問 Controller。

<!-- 註冊 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.ControllerClassNameHandlerMapping" />
<!-- 註冊 Handler -->
<bean class="com.controller.TestController" />

2) ControllerBeanNameHandlerMapping:根據 Bean 名訪問 Controller,與 BeanNameUrlHandlerMapping 類似,但是bean名稱不用遵循URL公約。

<!-- 註冊 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.ControllerBeanNameHandlerMapping" />
<!-- 註冊 Handler -->
<bean id="test" class="com.controller.TestController" />

3) BeanNameUrlHandlerMapping:利用 BeanName 來作為 URL 使用。

<!-- 註冊 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- 註冊 Handler -->
<bean id="/test.do" class="com.controller.TestController" />

4) SimpleUrlHandlerMapping:可以將 URL 與處理器的定義分離,還可以對 URL 進行統一的對映管理。

<!-- 註冊 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/test.do">testController</prop>
            <prop key="/hello.do">testController</prop>
        </props>
    </property>
</bean>
<!-- 註冊 Handler -->
<bean id="testController" class="com.controller.TestController" />

1.2 Controller 類

使用 AbstractUrlHandlerMapping 的實現類時,需要讓控制層的類實現 Controller 介面(一般繼承 AbstractController 即可),另外還有一些已經實現了的 Controller 類,如下圖所示。但是不論是自己實現 Controller 介面還是使用系統已經實現的類,都只能處理一個請求(除了 MultiActionController 可以通過引數的方式讓一個類可以處理多個請求)。

另外下面所有的 Controller 均採用 SimpleUrlHandlerMapping 方式的。

1) UrlFilenameViewController:用於跳轉介面,控制器根據請求的URL直接解析出檢視名,省去了自己實現 Ccntroller 跳轉頁面。

<bean id="indexController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />

2) ParameterizableViewController:同樣用於介面跳轉,控制器根據配置的引數來跳轉介面,使用方式如下

<bean id="indexController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
     <property name="viewName" value="/index.jsp" />
</bean>

3) ServletForwardingController:將請求轉發到 Servlet,使用方式如下

<bean id="indexController" class="org.springframework.web.servlet.mvc.ServletForwardingController">    
    <property name="servletName" value="indexServlet" />    
</bean> 

另外還要在 web.xml 中配置要轉發到的 Servlet

<servlet>    
    <servlet-name>indexServlet</servlet-name>    
    <servlet-class>com.servlet.ServletForwarding</servlet-class>    
</servlet>   

4) ServletWrappingController:將某個 Servlet 包裝為 Controller,所有到 ServletWrappingController 的請求實際上是由它內部所包裝的這個 Servlet 例項來處理的,這樣可以將這個 Servlet 隱藏起來

5) MultiActionController:一個 Controller 可以寫多個方法,分別對應不同的請求,使同一業務的方法可以放在一起了。在使用時讓自己的 Controller 類繼承 MultiActionController 類,使用方式如下

public class IndexController extends MultiActionController {  
    public ModelAndView add(HttpServletRequest request,HttpServletResponse response) {  
        ModelAndView mv = new ModelAndView();   
        mv.addObject("message","add");   
        mv.setViewName("add");   
        return mv;   
    }  
    public ModelAndView delete(HttpServletRequest request,HttpServletResponse response) {  
        ModelAndView mv = new ModelAndView();   
        mv.addObject("message","delete");   
        mv.setViewName("delete");   
        return mv;   
    }  
}  

配置自己的 Controller 時要配置一個方法名解析器(預設是 InternalPathMethodNameResolver )

<bean id="indexController" class="com.controller.IndexController">  
      <property name="methodNameResolver">  
        <!-- InternalPathMethodNameResolver 根據請求路徑解析執行方法名
             ParameterMethodNameResolver 根據引數解析執行方法名
             PropertiesMethodNameResolver 根據 key/value 列表解析執行方法名 -->
        <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">  
           <!-- 指定引數名為action -->  
           <property name="paramName" value="action" />  
        </bean>  
      </property>  
</bean>  

當我們訪問 http://localhost:8080/***/indexAction.do?action=add 時,進入 add() 方法;

當我們訪問 http://localhost:8080/***/indexAction.do?action=delete 時,進入 delete() 方法。

 

2 AbstractHandlerMethodMapping

AbstractHandlerMethodMapping 這個分支獲取的 Handler 的型別是 HandlerMethod,即這個 Handler 是一個方法,它儲存了方法的資訊(如Method),這樣一個 Controller 就可以處理多個請求了,原始碼如下所示

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 根據當前請求獲取“查詢路徑”
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // 獲取當前請求最佳匹配的處理方法(即Controller類的方法中)
    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}

上述程式碼中 lookupHandlerMethod() 方法主要工作是在 Map<T, HandlerMethod> handlerMethods 中找到 HandlerMethod,這裡的 T 是 HandlerMappingInfo,它封裝了 @RequestMapping 註解中的資訊。那 HandlerMethod 是怎麼建立的(即怎麼把 Controller 的方法變成了它),繼續看一下原始碼找到 initHandlerMethods() 方法,這個方法是在這個類建立後呼叫的,如下所示是它的原始碼

protected void initHandlerMethods() {
    // 從容器中獲取所有 Bean 的名稱,detectHandlerMethodsInAncestorContexts 預設false,不從父容器中查詢
    //即預設只查詢 SpringMVC 的 IOC 容器,不查詢它的父容器 Spring 的 IOC 容器
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
        BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
        getApplicationContext().getBeanNamesForType(Object.class));
    for (String beanName : beanNames) {
        // 這裡的 isHandler()方法由子類實現,判斷是否擁有 @Controller 註解或 @RequestMapping 註解
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && isHandler(getApplicationContext().getType(beanName))){
            // 利用反射得到 Bean 中的 Method 幷包裝成 HandlerMethod,然後放入 Map 中
            detectHandlerMethods(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

看完上述程式碼後,可以知道是在 detectHandlerMethods() 方法中將 Bean 的方法轉換為 HandlerMethod 物件,具體實現如下

protected void detectHandlerMethods(final Object handler) {
    // 獲取這個 Bean 的 Class 物件
    Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
    // 避免重複呼叫 getMappingForMethod(),getMappingForMethod() 將重新構建 RequestMappingInfo 例項
    final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
    // 獲取被代理前的原始型別
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    // 獲取 Method  
    Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            // 根據 Method 和它的 @RequestMapping 註解,建立 RequestMappingInfo 物件。
            // 這裡的 T 就是 RequestMappingInfo,它封裝了 @RequestMapping 資訊
            T mapping = getMappingForMethod(method, userType);
            if (mapping != null) {
                mappings.put(method, mapping);
                return true;
            } else {
                return false;
            }
        }
    });
    for (Method method : methods) {
        // 註冊 Method 和它的對映,RequestMappingInfo 儲存著對映資訊
        registerHandlerMethod(handler, method, mappings.get(method));
    }
}

最後在 registerHandlerMethod() 方法中,將 RequestMappingInfo 作為 key,把 Method 包裝成 HandlerMethod 作為 value 新增到了 Map<T, HandlerMethod> handlerMethods 中。

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
    HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
    if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
        throw new IllegalStateException("");
    }
    this.handlerMethods.put(mapping, newHandlerMethod);
    Set<String> patterns = getMappingPathPatterns(mapping);
    for (String pattern : patterns) {
        if (!getPathMatcher().isPattern(pattern)) {
            this.urlMap.add(pattern, mapping);
        }
    }
}

1.1 AbstractHandlerMapping 實現類及使用

AbstractHandlerMapping 只有一個實現類 RequestMappingHandlerMapping

 

二、HandlerAdapter

根據 Handler 來找到支援它的 HandlerAdapter,通過 HandlerAdapter 執行這個 Handler 得到 ModelAndView 物件。HandlerAdapter 介面中的方法如下:

  • boolean supports(Object handler); // 當前 HandlerAdapter 是否支援這個 Handler
  • ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object handler); // 利用 Handler 處理請求
  • long getLastModified(HttpServletRequest request, Object handler);

1 RequestMappingHandlerAdapter

從上面的文章中可以知道,利用 RequestMappingHandlerMapping 獲取的 Handler 是 HadnlerMethod 型別,它代表 Controller 裡要執行的方法,而 RequestMappingHandlerAdapter 可以執行 HadnlerMethod 物件。

RequestMappingHandlerAdapter 的 handle() 方法是在它的父類 AbstractHandlerMethodAdapter 類中實現的,原始碼如下所示

@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
}

handleInternal() 方法是由 RequestMappingHandlerAdapter 自己來實現的,原始碼如下所示

@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // 是否通過 @SessionAttributes 註釋聲明瞭 session 屬性。
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
        checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
    } else {
        checkAndPrepare(request, response, true);
    }
    // 是否需要在 synchronize 塊中執行
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 執行 HandlerMethod
                return invokeHandleMethod(request, response, handlerMethod);
            }
        }
    }
    // 執行 HandlerMethod,得到 ModelAndView
    return invokeHandleMethod(request, response, handlerMethod);
}

繼續再來看一下如何得到 ModelAndView,invokeHandlerMethod() 方法如下

private ModelAndView invokeHandleMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // 
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    // 資料繫結
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    // 繫結引數,執行方法
    ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
    // 建立模型和檢視容器
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    // 設定FlasgMap中的值
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    // 初始化模型
    modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
    if (asyncManager.hasConcurrentResult()) {
        Object result = asyncManager.getConcurrentResult();
        mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
        asyncManager.clearConcurrentResult();
        requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
    }
    requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
        return null;
    }
    return getModelAndView(mavContainer, modelFactory, webRequest);
}

 

2 HttpRequestHandlerAdapter

HttpRequestHandlerAdapter 可以執行 HttpRequestHandler 型別的 Handler,原始碼如下

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ((HttpRequestHandler) handler).handleRequest(request, response);
    return null;
}

3 SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter 可以執行 Controller 型別的 Handler,原始碼如下

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

 

4 SimpleServletHandlerAdapter 

SimpleServletHandlerAdapter 可以執行 Servlet 型別的 Handler,原始碼如下

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

 

三、HandlerExceptionResolver

負責處理異常的類,負責根據異常來設定 ModelAndView,然後交由 render 渲染介面。HandlerExecptionResolver 介面中只有一個方法,如下:

  • ModelAndView resolveException(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex);