Spring3.X @MVC - (三)Spring3中的拦截器
前言:
一、总共有10节,也就是10篇博客来讲述Spring的MVC,几乎涵盖了所有Spring MVC中的内容。
二、我创建的例子是一个球场预订系统,例子我已经测试调试通过,是一个Maven的project,包含一个Parent project:wsheng-spring-base和一个子Module:wsheng-spring-mvc.
三、在Eclipse中直接import maven的project即可,会同时引入上诉两个project的。
四、如果你没有耐心,可以不必往下学习,因为网上有很多例子,但是都是讲的Spring MVC很少的面,而且你可以快速的上手,但如果你想真正了解Spring MVC中的很多细节,就可以慢慢的去看博客(从第一节到第十节),如果有什么问题,欢迎信息告诉我。
五、学习的方法是你可以先将源码导入到eclipse中,然后根据博客上的内容,对照源码,慢慢消化,这是个漫长的过程,但是会帮助你了解很多Spring MVC的细节。
===================================================================================
用Spring处理程序拦截器拦截请求
在Spring3.X @MVC - (二)So Easy的注解功能的学习基础上,
学过Java Web编程的开发人员都知道,Servlet API中有Servlet过滤器,该过滤器可以可以在Servlet进行Web请求先后进行相关的处理。其实Spring中也可以实现类似而且更加强大的功能。可以在Spring Web上下文中配置和过滤器相似的功能部件,这样就可以使用Spring强大的容器特性。
SpringMVC中可以使用拦截器(Handler Interceptors)拦截web请求进行预先和事后的处理。每个程序拦截器都必须实现HandlerInterceptor接口,实现该接口必须实现3个方法:preHandler(), postHandle(),afterCompletion()
preHandle(): 处理Request之前执行。
postHandler():处理Request方法之后但是还没有返回View之前执行,并允许处理ModelAndView对象。
afterCompletion():在所有请求处理完成之后(也就是显示视图之后)调用。
实战和原理:
需求: 测试每个Web请求的处理时间,并由视图将这个时间显示给用户。
package com.wsheng.spring.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
*
* @author Wang Sheng(Josh)
*
*/
public class MeasurementInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
//Model model) throws Exception {
ModelAndView modelAndView) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
request.removeAttribute("startTime");
long endTime = System.currentTimeMillis();
//model.addAttribute("handlingTime", endTime - startTime);
System.out.println("handlingTime:" + (endTime - startTime));
modelAndView.addObject("handlingTime", endTime - startTime);
}
}
你可能会注意到,这个地方MeasurementInterceptor继承了HandlerInterceptorAdapter,而不是实现了HandlerInterceptor接口,这是因为这个地方如果实现了HandlerInterceptor接口,那么就需要实现该接口中的所有方法,而这个地方我们只是简单的记录一下处理Request的时间,是不需要重写afterCompletion()方法的,最简单的方法是让在程序中让该方法体为空。还有另一种更好的方法是继承抽象类HandlerInterceptorAdapter ,实际上HandlerInterceptorAdpater是实现了HandlerInterceptor接口的,并重写了preHandle(), postHandle()和AfterCompletion()方法,而且这些方法都是具体的public方法,只不过preHandle()方法是返回true,postHandle()和afterCompletion()是空实现。所以用户在继承HandlerInterceptorAdapter后,可以根据自己的需要去进行重写。
接着需要将自定义的拦截器注册到DefaultAnnotationHandlerMapping bean中。这个bean的作用是将拦截器应用到所有以@Controller注解标注的类,意思是说所有的控制器都要受到拦截器的控制。(可以在数组类型的interceptors属性中指定多个拦截器)
<!-- Annotation handlers (Applied by default to ALL @controllers -->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="1"/>
<!-- Interceptors are applied to all annotated controllers -->
<property name="interceptors">
<list>
<ref bean="measurementInterceptor" />
</list>
</property>
</bean>
然后可以在welcome.jsp中显示这个页面:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title><spring:message code="welcome.title" text="Welcome" /></title>
</head>
<body>
<h2><spring:message code="welcome.message" text="Welcome to Court Reservation System" /></h2>
Today is <fmt:formatDate value="${today}" pattern="yyyy-MM-dd" />.
<hr />
Handling time : ${handlingTime} ms
<br />
Locale : ${pageContext.response.locale}
</body>
</html>
DefaultAnnoationHandlerMapping的缺点:
如上所述,DefaultAnnoationHandlerMapping会将其中定义的所有的拦截器分配给所有的@Controller注解定义的类,如果想将不同的拦截器应用于不同的@Controller控制器上,DefaultAnnoationHandlerMapping显然不能完成。幸运的是这是一个常见的需求,所以Scott Murphy的Spring-plugins项目就可以完成该功能,该项目允许你使用URL在控制器的基础上应用拦截器。
你可以在http://code.goole.com/p/springplugins/downloads/list中下载该项目,并将相关的jar配置到maven-repository中,但是很不幸的是,截止我发布这篇博客的时候,springplugins.jar还是没有在maven官方的repository中,最简单的方法是将其jar放在应用的WEB-INF/lib中,但是这个显然忽视了Maven强大的功能,比较好的解决办法是将其加到本地的maven repository中。可参照我的另一篇博客:
将jar包加到项目中后,接下来只需要配置一个springplugin中称作SelectedAnnotationHandlerMapping的一个类,并且将其配置和DefaultAnnoationHandlerMapping bean的配置放在一起就可以了。如:
<!-- Interceptors -->
<bean id="measurementInterceptor"
class="com.wsheng.spring.web.MeasurementInterceptor" />
<bean id="summaryReportInterceptor"
class="com.wsheng.spring.web.ExtensionInterceptor" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="order" value="1"/>
<!-- Interceptors are applied to all annotated controllers -->
<property name="interceptors">
<list>
<ref bean="measurementInterceptor" />
</list>
</property>
</bean>
<bean id="publicMapper" class="org.springplugins.web.SelectedAnnotationHandlerMapping">
<property name="order" value="0" />
<property name="urls">
<list>
<value>/reservationSummary*</value>
</list>
</property>
<property name="interceptors">
<list>
<ref bean="summaryReportInterceptor" />
</list>
</property>
</bean>
上面的配置会产生如下的效果:measurementInterceptor拦截器应用到所有以@Controller注解的控制器,而summaryReportInterceptor拦截器只用到用@Controller注解的,映射到/reservationSummary*URL的那些控制器。order值越小的,处理优先级越高。order值越大的,处理优先级越低。为处理程序拦截器分配order值的过程与当初在web.xml中分配给servlet的启动时加载的属性类似。