5、處理方法對映(Handler Mapping)

行一發表於2016-09-28

  這一部分的例子見這個專案的 mvc 分支下的 TimeBasedAccessInterceptor.java


  Spring 在之前的版本中,使用者要在 Web 應用程式上下文中定義一個或多個 HandlerMapping Bean 用於把進來的 Web 請求對映到合適的處理方法。在引進註解控制器之後,你通常不需要這麼做,因為 RequestMappingHandlerMapping 自動在所有控制器中尋找 @RequestMapping 註解。但是要記住,所有繼承自 AbstractHandlerMapping 的 HandlerMapping 類有下面的屬性,你可以用它們來自定義行為:

  • interceptors——攔截器使用的列表。有關 HandlerInterceptors 的討論在“使用 HandlerInterceptor 攔截請求

  • defaultHandler——在處理方法對映沒有找到匹配時使用的預設處理方法

  • order——基於 order 屬性值(見介面 org.springframework.core.Ordered ),Spring 對上下文中可用的處理方法對映進行排序,並應用第一個匹配。

  • alwaysUseFullPath——如果是 true,在當前 Servlet 上下文中 Spring 使用全路徑來尋找合適的處理方法。如果是 false(預設值),就使用當前 Servlet 對映中的路徑。例如,如果一個 Servlet 對映使用“/testing/*”同時 alwaysUseFullPath 屬性為 true,那麼就使用“/testing/viewPage.html”;如果是 false,就使用“/viewPage.html

  • urlDecode——從 Spring 2.5 開始,預設值為 true。如果你喜歡比較被編碼的路徑,就把這個標記設定為 false 吧。然而,HttpServletRequest 一直都是用編碼後的形式來暴露 Servlet 路徑。注意,在比較被編碼的路徑時,Servlet 路徑不會匹配。

  下面的例子展示了怎樣配置一個攔截器:

<beans>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <bean class="example.MyInterceptor"/>
        </property>
    </bean>
</beans>

使用 HandlerInterceptor 攔截請求

  Spring 的處理方法對映機制包括處理方法攔截器,這在你需要為指定請求應用特定功能時很有用,比如 checking for a principal(什麼意思?)

  位於處理方法對映中的攔截器必須實現 org.springframework.web.servlet 包中的
HandlerInterceptor 介面。這個介面定義了三個方法:

  • preHandle(..)     在處理方法執行前呼叫;

  • postHandle(..)    在處理方法執行後呼叫;

  • afterCompletion(..)  在完成請求結束後呼叫。

這三個方法為預處理和後處理提供了足夠的靈活性。

  方法 preHandle(..) 返回一個布林值。你可以使用這個方法來中斷或者繼續執行鏈的處理。這個方法返回 true 時,處理方法的執行鏈會繼續;返回 false 時,DispatcherServlet 就嘉定攔截器自己已經處理好了請求(比如渲染一個適當的檢視),並不再繼續執行其他攔截器和實際存在於執行鏈的中的處理方法。

  攔截器可以使用 interceptors 屬性來配置,這適用於所有繼承自 AbstractHandlerMapping 的 HandlerMapping 類。下面是一個例子:

<beans>
    <bean id="handlerMapping" 
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
</beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter 
{
    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) { this.openingTime = openingTime; }

    public void setClosingTime(int closingTime) { this.closingTime = closingTime; }

    public boolean preHandle(
            HttpServletRequest request, 
            HttpServletResponse response, 
            Object handler)  throws Exception 
    {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) return true;
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

  任何被這個對映處理的請求都會被 TimeBasedAccessInterceptor 攔截。如果當前時間不是上班時間,使用者就被重定向到一個靜態 HTML 頁面中去,這個頁面告訴使用者,比如,你只能在上班時間訪問網站。

  在使用 RequestMappingHandlerMapping 的時候,實際的處理方法是一個 HandlerMethod 例項,它識別要被呼叫的指定的控制器方法。

  就像你看到的那樣,Spring 的介面卡類 HandlerInterceptorAdapter 使擴充套件介面 HandlerInterceptor 變得更加容易。

  在上面的例子中,配置的攔截器會應用於所有被註解的控制器方法處理的請求。你想窄化攔截器攔截的 URL 路徑,你可以使用 MVC 名稱空間或者 MVC Java 配置,或者宣告一個 MappedInterceptor 型別的 Bean 例項來這麼做。詳見“啟用 MVC Java 配置或者 MVC XML 名稱空間配置

  注意,介面 HandlerInterceptor 的 postHandle 方法並不總是完美的適用於使用 @ResponseBody 和 ResponseEntity 的方法。在這種情況下,在 postHandle 方法呼叫之前,HttpMessageConverter 已經寫並且提交了響應,這樣就不再能夠改變響應了,比如新增響應頭。替代方案是,應用程式可以實現 ResponseBodyAdvice 介面,同時要麼宣告它為一個 @ControllerAdvice Bean,要麼直接把它配置在 RequestMappingHandlerAdapter上。

相關文章