Spring 的前几个版本,开发者都需要在 web 应用的上下文定义(多个) HandlerMapping bean,用来将 web 请求映射到指定的 handler。可当引入注解控制器时,开发者基本不再需要这样配置。因为 RequestMappingHandlerMapping 会自动寻找所有 @Controller bean 中的 @RequestMapping 注解。另外提醒一下,所有从 AbstractHandlerMapping 继承过来的 HandlerMapping 类,都以通过设置以下属性来自定义其行为:

  • interceptors 拦截器链。 HandlerInterceptors 会在 Section 16.4.1, “使用 HandlerInterceptor 拦截请求” 谈论。
  • defaultHandler 默认 handler。此 handler 不影响其他 handler 的使用。
  • order order 属性 (可查看 org.springframework.core.Ordered 接口), Spring会对可匹配的 handler 进行排序,并应用第一个匹配到 handler。
  • alwaysUseFullPath 当此属性为 true 时,Spring 会使用当前 Servlet 上下文的全路径去寻找合适的 handler。当为 false 时(默认值),Spring 会使用相对路径来寻找合适的 handler。举个例子,当 某个 Servlet 映射 /testing/* 请求时,若 alwaysUseFullPath 属性为 true,会使用 /testing/viewPage.html;反之,使用 /viewPage.html
  • urlDecode 从 Spring 2.5 开始,此属性默认为 true。如果你更需要编码路基路径,可将此属性设置为 true。然而,HttpServletRequest 总会暴露解码后的 Servlet 路径。另外注意的是,当比较编码后的路径时,Servlet 路径是不会再匹配的。

如下例子,演示了如何配置一个拦截器:

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

16.4.1 使用 HandlerInterceptor 拦截请求

Spring 的 handler 映射机制包含了 handler 拦截器。使用handler 拦截器,可以在某些的请求中应用的特殊的功能,比如说,检查权限。

handler 映射的拦截器必须实现 HandlerInterceptor 接口(此节接口位于 org.springframework .web.servlet 包中)。这个接口定义了三个方法:preHandle(..)在 handler 执行前调用;postHandle(..) 在handler 执行后调用;afterCompletion(..) 在整一个请求完成后调用。这三个方法基本足够应对各种预处理和后处理的状况。

preHandle(..) 方法返回一个 boolean 值。你可以使用这个方法来中断或继续处理 handler 执行链。当此方法返回 true 时,hadler 执行链会继续执行;反之,DispatcherServlet 会认为此拦截器已处理完成该请求(和渲染一个视图),之后不再执行余下的拦截器,也不在执行 handler 执行链。

可以使用 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;
    }
}

通过这样的配置,所有请求 handler 都会被 TimeBasedAccessInterceptor 拦截。如果当前时间是下班时间,用户会重定向到一个静态页面,换句话说就是,你只能在上班时间访问该网站。

[提示]

当使用 RequestMappingHandlerMapping 时,真实的 handler 是一个HandlerMethod 实例,该实例指定了会被调用的控制器方法。

如你所见,Spring 的适配器类 HandlerInterceptorAdapter,使继承 HandlerInterceptor 接口变得更加简单。


在上述例子中,所配置的拦截器会应用到所有带注解的请求处理器。如果需要缩窄拦截器的拦截 url 路径范围,可以使用 MVC 命名空间或 MVC Java 配置,或声明 MappedInterceptor 类型的 bean 来缩窄拦截器的拦截范围,详情可参考 Section 16.16.1, “启用 MVC Java 配置或 MVC XML 命名空间”

注意,HandlerInterceptor 的 postHandle 方法不一定适用于@ResponseBodyResponseEntity方法。在这种情况下,HttpMessageConverter 实例会在 postHandle 方法执行之前就将数据写到 response 并提交 response,所以 postHandle 方法不可能再处理 response(如添加一个 Header)。相反,应用程序可以实现 ResponseBodyAdvice ,将其声明为 @ControllerAdvice bean 或将其直接在 RequestMappingHandlerAdapter 中配置它。