在 Java Web 开发中,拦截器(Interceptor)和过滤器(Filter)都是用于在请求处理前后执行某些操作的机制。虽然它们的功能相似,但在实现方式、使用场景和灵活性方面有一些重要的区别。
选择使用过滤器还是拦截器,取决于你的具体需求和应用场景。如果你需要处理与业务逻辑无关的通用问题,过滤器是一个不错的选择。
如果你需要处理与业务逻辑相关的操作,特别是当你已经在使用 Spring 框架时,拦截器会更加灵活和强大。
实现了 HandlerInterceptor
接口的类,可以重写三个主要的方法:
preHandle
:在请求处理之前执行,Controller方法调用之前执行。返回值:返回 boolean 值。如果返回 true,则继续处理请求;如果返回 false,则中断请求处理,不再调用控制器方法和其他拦截器的 postHandle 和 afterCompletion 方法。postHandle
:在Controller方法处理之后,但在视图渲染之前执行(返回结果给前端之前执行)。afterCompletion
:在整个请求处理完成之后执行(前端已获取到响应结果之后执行)。执行顺序:preHandle、控制层方法、postHandle、视图渲染、afterCompletion
preHandle方法
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
postHandle方法
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
afterCompletion 方法
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
需要创建一个实现了 HandlerInterceptor
接口的类。可以重写三个主要的方法:preHandle、postHandle、afterCompletion。
1、创建拦截器类,并实现
HandlerInterceptor
接口,通过重写三大方法,对请求就行拦截
/**
* 用户拦截器,用于验证token
*/
@Component // 交给spring容器管理
@Slf4j // 日志
public class JwtTokenUserInterceptor implements HandlerInterceptor {
//重写preHandle, 在请求处理之前进行调用(Controller方法调用之前)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求的URL
String url = request.getRequestURL().toString();
log.info("请求的URL为:" + url);
//1、判断当前拦截到的是Controller的方法,还是其他资源或方法,HandlerMethod(控制器类中的方法)
if (!(handler instanceof HandlerMethod)) {
// 资源放行(拦截到的不是Controller中的方法,直接放行)
return true;
}
//2、获取token(从请求的请求头中获取到token,token由前端封装到请求头中的,字符串参数"token",与前端传来的要匹配)
String token = request.getHeader("token");
//判断请求头是否为空,为空就直接返回提示信息给前端;
//hasLength方法,验证字符串是否为空或仅为空白字符串(即只包含空格、制表符、换行符等空白字符)
if (!StringUtils.hasLength(token)) {
Result result = Result.error("未登录"); //
//将对象转为json格式字符串
String jsonStr = JSONObject.toJSONString(result);
//通过响应体里的输出流来将信息响应给前端
response.getWriter().write(jsonStr);
return false;
}
//3、判断token是否正确
try {
//解析token,只要不报错,就说明token是正确的
Claims claims=JwtUtil.parseToken(token);
//将token中的用ID提取出来
Long userId=Long.parseLong(claims.get("userId").toString());
//将用户id保存到线程局部变量中,方便后面使用(ThreadLocal)
BaseContext.setCurrentId(userId);
return true;
} catch (Exception e) {
Result result = Result.error("token无效");
//将对象转为json格式字符串
String jsonStr = JSONObject.toJSONString(result);
//通过响应体里的输出流来将信息响应给前端
response.getWriter().write(jsonStr);
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行的操作,但在视图渲染之前
System.out.println("postHandle已执行,在Controller执行之后");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在请求处理完成后执行的操作
System.out.println("afterCompletion已执行,在响应结果已返回给前端之后");
}
}
2、注册拦截器:创建配置类,继承
WebMvcConfigurationSupport
类,注入拦截器,重写addInterceptors
方法,添加拦截规则。
/**
* 配置类,注册web层相关组件,配置拦截器
*/
@Component
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor; // 注入拦截器
// 注册自定义拦截器,重写addInterceptors方法
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtTokenUserInterceptor) // 添加拦截器(注入进来的)
.addPathPatterns("/user/**") // 拦截路径(请求链接带有user的都拦截)
.excludePathPatterns("/user/login"); // 放行路径(请求链接是/user/login不拦截)
// registry.addInterceptor(jwtTokenAdminInterceptor) //如果还有其他拦截器的话,就继续在后面添加即可
// .addPathPatterns("/admin/**") // 拦截路径
// .excludePathPatterns("/admin/login"); // 放行路径
}
}
通过实现 Filter接口,即可实现三大方法
init方法
default void init(FilterConfig filterConfig) throws ServletException { }
doFilter方法
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
destroy 方法
default void destroy() {}
创建一个过滤器工具类,实现
Filter
接口,加上注解@WebFilter
配置过滤路径,加上注解@Component
将其交给Spring管理,最后重写方法。
@Component
@WebFilter(urlPatterns ="/*") // 拦截所有请求
public class FilterUtil implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//先将servletRequest 和 servletResponse对象强制转换为HttpServletRequest和HttpServletResponse对象(要获取链接和token)
HttpServletRequest request= (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
// 1. 获取请求url。
String url=request.getRequestURL().toString();
log.info("请求的URL为:"+url);
// 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("/login")){
filterChain.doFilter(request,response); // 放行
return; //放行后就不执行Filter的后面代码了
}
// 3. 获取请求头中的令牌( token)。
String token=request.getHeader("token");
// 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录),判断token是否有长度,有true。
if(!StringUtils.hasLength(token)){
//没有长度,响应前端错误信息
Result result=Result.error("Not Login");
//手动的将对象信息,转换为JSON字符串返回,使用阿里巴巴的fastJSON
/* com.alibaba fastjson 1.2.76 */
String errorMsg= JSONObject.toJSONString(result);
//通过响应体里的输出流来将信息响应给前端
response.getWriter().write(errorMsg);
return;
}
// 5. 解析token,如果解析失败。返回错误结果(未登录)。(校验成功不报错,校验失败会报错)
try {
//调用JWT工具类进行jwt校验(解析成功,得到用户数据)
Claims claims= JwtUtil.parseToken(token);
//将数据中的ID存储到ThreadLocal中(获取Map对象的属性,转为String,再转为Long)
BaseContext.setCurrentId(Long.valueOf(claims.get("id").toString()));
} catch (Exception e) {
//校验失败,返回错误信息
Result result=Result.error("Not Login");
//手动的将对象信息,转换为JSON字符串返回,使用阿里巴巴的fastJSON
String errorMsg=JSONObject.toJSONString(result);
//通过响应体里的输出流来将信息响应给前端
response.getWriter().write(errorMsg);
return;
}
// 6. 放行(到这里说明没有报错)。
filterChain.doFilter(request,response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化");
}
@Override
public void destroy() {
System.out.println("过滤器销毁");
}
}