这一部分的例子见这个项目的 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上。