1.快速入门
拦截器是什么?简单来说,它是一种动态拦截方法调用的机制,和过滤器有点像,但它是Spring框架自带的,专门用来动态拦截控制器方法的执行。
那拦截器到底能做什么?最常见的用途就是拦截请求,在指定方法执行前后,根据业务需求插入一些预先写好的逻辑。比如登录校验——我们可以把校验逻辑全塞进拦截器里,前端发请求时,拦截器先检查用户是否已登录(比如携带了合法的JWT令牌),如果合格就放行,让请求继续访问Spring资源;如果没登录或者令牌有问题,就直接返回未登录的错误信息,省得每个接口都写一遍校验代码。
下面通过一个快速入门程序,来感受一下拦截器的基本用法。步骤和过滤器差不多,就两步:
定义拦截器
注册配置拦截器
自定义拦截器:需要实现 HandlerInterceptor 接口,并重写里面的三个方法。
//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //true表示放行
}
//目标资源方法执行后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ... ");
}
//视图渲染完毕后执行,最后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion .... ");
}
}
注意一下三个方法各自的作用:
preHandle:目标方法执行前触发,返回true放行,false不放行。postHandle:目标方法执行后触发。afterCompletion:视图渲染完成后触发,它是最后一个执行的。
注册配置拦截器:实现 WebMvcConfigurer 接口,重写 addInterceptors 方法,把自定义的拦截器注册进去。
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}
重启SpringBoot服务,打开Postman测试一下:

接下来再做一个测试:把拦截器中 preHandle 的返回值改成 false。再次发送请求,会发现没有响应数据,说明请求被拦截了,没有放行。

2.Interceptor详解
入门程序跑通后,咱们再深入看看拦截器的使用细节。主要关注两个点:
拦截路径的配置
执行流程
2.1 拦截路径
先看拦截路径的配置。在注册拦截器时,通过 addPathPatterns("要拦截路径") 指定要拦截哪些资源。入门程序里配的是 /**,表示拦截所有资源。除了指定拦截哪些路径,还可以用 excludePathPatterns("不拦截路径") 排除掉不需要拦截的路径,比如登录接口。
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
.excludePathPatterns("/login");//设置不拦截的请求路径
}
}
除了 /**,还有一些常见的路径模式:
| 拦截路径 | 含义 | 举例 |
|---|---|---|
| /* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
| /** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
| /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
| /depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
来演示一下 /** 和 /* 的区别:把拦截路径改成 /*,然后测试 http://localhost:8080/emps/1。
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/*")
.excludePathPatterns("/login");//设置不拦截的请求路径
}
}
用Postman发送请求,控制台没有输出拦截器的日志,说明 /* 没有匹配到 /emps/1 这个二级路径。


2.2 执行流程
了解完路径配置,再来看拦截器的执行流程。通过这个流程,大家就能清楚过滤器和拦截器各自在什么时候执行了。
- 当浏览器发起请求,首先经过过滤器(如果配置了的话)。过滤器先执行放行前的逻辑,然后放行。由于是SpringBoot项目,放行后进入Spring环境,最终访问到Controller中的接口方法。
- Tomcat不认识Controller,但认识Servlet。Spring提供了一个核心Servlet——
DispatcherServlet(前端控制器),所有请求先到它这里,再由它转发给Controller。 - 如果定义了拦截器,请求会在执行Controller方法之前被拦截,执行
preHandle()。这个方法返回true才放行,返回false就直接终止,Controller方法也不会执行。 - Controller方法执行完后,会依次执行
postHandle()和afterCompletion(),然后返回给DispatcherServlet,最后再执行过滤器放行后的逻辑,最终响应给浏览器。
下面演示一下过滤器和拦截器同时存在时的执行顺序:
开启
LoginCheckInterceptor拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //true表示放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ... ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion .... ");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")//拦截所有请求
.excludePathPatterns("/login");//不拦截登录请求
}
}
开启
DemoFilter过滤器
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("DemoFilter 放行前逻辑.....");
//放行请求
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("DemoFilter 放行后逻辑.....");
}
}
重启服务,清空日志,用Postman测试查询部门接口:


通过执行流程可以看出,过滤器和拦截器主要有两点区别:
- 接口规范不同:过滤器实现
Filter接口,拦截器实现HandlerInterceptor接口。 - 拦截范围不同:过滤器会拦截所有资源(包括静态资源),而拦截器只拦截Spring环境中的资源(比如Controller)。
3.登录校验- Interceptor
登录校验的逻辑和之前用过滤器时完全一样,只是把技术方案换成拦截器。下面直接上代码。
登录校验拦截器
//自定义拦截器
@Component //当前拦截器对象由Spring创建和管理
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
//前置方式
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
//1.获取请求url
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
//3.获取请求头中的令牌(token)
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;//不放行
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(token);
}catch (Exception e){
log.info("令牌解析失败!");
//创建响应结果对象
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
String json = JSONObject.toJSONString(responseResult);
//设置响应头
response.setContentType("application/json;charset=utf-8");
//响应
response.getWriter().write(json);
return false;
}
//6.放行
return true;
}
注册配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
JWT工具类
public class JwtUtils {
private static String signKey = "itheima";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map claims){
String jwt = Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}