三要素

  • 拦截
  • 过滤
  • 放行

程序测试

1、准备工作

导入 jar 包

commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar

web.xml中配置前端控制器 DispatcherServlet 并指定 spring-mvc 的配置文件

创建 spring-mvc 的配置文件spring-mvc.xml,配置自动扫描的包并配置视图解析器

2、方法一

① 实现 HandlerInterceptor 接口

[1]preHandle()方法

签名:boolean preHandle(HttpServletRequest, HttpServletResponse, Object)

在执行目标 handler 方法之前执行,如果返回 true,则继续执行后续拦截器和目标 handler 方法;如果返回 false 则不执行。

注意:返回 false 时最好借助转发或重定向等方式为客户端提供一个响应页面。

[2]postHandle()方法

签名:void postHandle(HttpServletRequest, HttpServletResponse, Object, ModelAndView)

在执行目标 handler 方法之后、渲染视图之前执行。

[3]afterCompletion()方法

在渲染视图之后、返回响应之前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class PrivateInterceptor implements HandlerInterceptor {

// 在目标handler方法前执行
// Object handler是目标handler类的对象
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {

System.out.println("PrivateInterceptor preHandle()方法执行了");

// 过滤条件:检查当前请求是否登录(假设请求参数中携带了token=good表示登录)
String token = request.getParameter("token");
if ("good".equals(token)) {

// 已登录则放行
return true;
} else {

// 转发到message.jsp
request.getRequestDispatcher("/WEB-INF/pages/message.jsp").forward(request,response);

return false;
}

// 返回true:放行
// 返回false:不放行,此时浏览器得不到任何响应,需要由开发人员指定
// return false;
}

// 在目标handler方法之后、渲染视图之前执行
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("PrivateInterceptor postHandle()方法执行了");
}

// 渲染视图之后
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("PrivateInterceptor afterCompletion()方法执行了");
}
}

② 配置拦截器

spring-mvc.xml配置文件中配置拦截器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 完整的配置方式:使用mvc:interceptor标签 -->
<mvc:interceptor>
<!-- mvc:mapping标签:配置拦截器要拦截的资源 -->
<!-- path属性:指定要拦截的资源的路径。在这里如果要使用*号,那么一个星号只代表路径中的一级 -->
<!-- 使用**号才能匹配多级 -->
<!-- /shop/product/cellphone/iphone -->
<mvc:mapping path="/access/private/*"/>

<!-- 使用内部bean配置拦截器的类 -->
<bean class="com.atguigu.interceptor.interceptor.PrivateInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

3、方法二

上面方法一通过实现 HandlerInterceptor 接口来实现拦截,需要把所有抽象方法都实现,而继承 HandlerInterceptorAdapter 类则可以有选择的重写需要的方法,更加便捷,推荐使用。

① 继承 HandlerInterceptorAdapter 类

1
2
3
4
5
6
7
8
9
10
public class SecondInterceptor extends HandlerInterceptorAdapter {

// 创拦截器类时继承HandlerInterceptorAdapter类,就可以需要哪个方法重写哪个方法,让拦截器类更简洁
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

System.out.println("SecondInterceptor preHandle()");

return true;
}
}

② 配置拦截器

1
2
3
4
5
6
7
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 此处省略其他无关配置 -->

<!-- 简略的配置方式:直接配置bean,此时拦截所有请求(最大就是SpringMVC负责的范围) -->
<bean class="com.atguigu.interceptor.interceptor.SecondInterceptor"/>
</mvc:interceptors>

使用细节

1、拦截地址包含多层路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mvc:interceptor>
<!-- 用/shop/*表示拦截一层地址 -->
<!-- 注意:/shop地址和/shop/*不匹配 -->
<!-- /shop/product和/shop/*匹配 -->
<!-- /shop/product/cellphone和/shop/*不匹配 -->
<mvc:mapping path="/shop/*"/>
<bean class="com.atguigu.interceptor.interceptor.OneStarInterceptor" />
</mvc:interceptor>

<mvc:interceptor>
<!-- 用/shop/**表示拦截多层地址,或者完全没有层也可以匹配 -->
<!-- /shop和/shop/**匹配 -->
<!-- /shop/product和/shop/**匹配 -->
<!-- /shop/product/cellphone和/shop/**匹配 -->
<mvc:mapping path="/shop/**"/>
<bean class="com.atguigu.interceptor.interceptor.TwoStarInterceptor" />
</mvc:interceptor>

2、配置不拦截的地址

<mvc:mapping>配置了拦截的地址基础上,使用<mvc:exclude-mapping>配置不拦截的地址

1
2
3
4
5
6
7
8
9
10
11
<mvc:interceptor>
<!-- 用/shop/**表示拦截多层地址,或者完全没有层也可以匹配 -->
<!-- /shop匹配 -->
<!-- /shop/product匹配 -->
<!-- /shop/product/cellphone匹配 -->
<mvc:mapping path="/shop/**"/>

<!-- 配置不拦截的地址 -->
<mvc:exclude-mapping path="/shop/product"/>
<bean class="com.atguigu.interceptor.interceptor.TwoStarInterceptor" />
</mvc:interceptor>

3、多个拦截器的执行顺序

拦截相同资源的拦截器会构成一个链式结构,它们执行的顺序和配置的顺序有关

  • 按正序依次调用 preHandle()方法
  • 执行目标 handler 方法
  • 按反序调用 postHandle()方法
  • 按反序调用 afterCompletion()方法

正序:方法执行的顺序和配置的顺序相同

反序:方法执行的顺序和配置的顺序相反

4、各个方法执行的时机

  1. preHandle()方法
  2. 目标 handler 方法
  3. postHandle()方法
  4. 渲染视图
  5. afterCompletion()方法

5、拦截器和过滤器的异同

[1]相同点

三要素一致

[2]不一致

  • 工作平台不同
    • 拦截器:工作在 SpringMVC 的环境下
    • 过滤器:工作在 Servlet 容器中
  • 管理的范围不同
    • 拦截器:能够拦截的请求,最大范围不会超过 SpringMVC 管理的范围
    • 过滤器:能够拦截的请求,最大范围是整个 Web 应用范围
  • API 接口不同
    • 拦截器:实现HandlerInterceptor接口或继承HandlerInterceptorAdapter
    • 过滤器:实现javax.servlet.Filter接口
  • 配置方式不同
    • 拦截器:在 SpringMVC 配置文件中配置
    • 过滤器:web.xml 中配置
  • 应用场景
    • 拦截器:在 SpringMVC 环境下,能用拦截器实现的功能都用拦截器,因为它从 IOC 容器中获取 bean 方便
    • 过滤器:在没有 SpringMVC 环境或者有 SpringMVC 但拦截器无法实现的情况下,使用过滤器