一、引言
在后端开发的世界里,当我们构建一个 Web 应用时,常常需要对进入系统的请求进行各种处理。比如,在处理请求之前,我们可能需要验证用户的身份,确保只有合法用户才能访问特定资源;又或者我们想要记录所有请求的日志,以便后续分析系统的运行情况。在响应返回给客户端之前,我们可能还需要对响应数据进行一些处理,比如添加自定义的响应头信息。
而 Filter(过滤器)和 Interceptor(拦截器)就是我们在后端开发中用于处理这些任务的重要工具 。它们就像是 Web 应用的 “门卫”,在请求到达目标资源之前,以及响应返回客户端之前,对请求和响应进行预处理和后处理,为我们的 Web 应用提供了强大的功能扩展能力。
常见的过滤拦截的技术有:
| 对比 | 过滤器Filter | 拦截器Interceptor |
|---|---|---|
| 技术来源 | 是JavaEE的基础规范之一,只要是JavaWeb开发就能用 | SpringMVC框架提供的(SpringBootWeb) |
| 使用范围 | 所有JavaWeb项目都能用 | 只有使用了SpringMVC框架的项目才能用 |
| 拦截范围 | 能拦截客户端对一切资源的请求 | 只能拦截被SpringMVC管理的资源,主要是Controller |
| 实现规范 | 要实现javax.servlet.Filter接口 | 要实现HandlerInterceptor接口 |
接下来,就让我们一起深入了解 Filter 和 Interceptor 的奥秘吧!
二、Filter 入门
(一)Filter 是什么
在 Java Web 开发中,Filter(过滤器)是一种特殊的组件,它能够对 Servlet 容器调用 Servlet 的过程进行拦截 。(就好比生活中的污水净化设备,能对水源进行过滤净化一样,在程序里,Filter 可以对请求和响应信息进行过滤。)
当用户通过浏览器访问服务器中的目标资源时,请求首先会被 Filter 拦截,在 Filter 中进行预处理操作后,才会被转发给目标资源。而服务器处理完响应后,响应结果也需要经过 Filter 处理,才会发送给客户端 。通过 Filter,我们可以在请求到达目标资源之前,以及响应返回客户端之前,对请求和响应进行各种处理,比如身份验证、日志记录、字符编码转换等 。
(二)工作原理
Filter 的工作原理基于 Servlet 规范中的责任链模式 。当一个请求到达服务器时,服务器会根据配置找到对应的 Filter 链。每个 Filter 都有一个doFilter方法,在这个方法中,我们可以对请求和响应进行处理。如果需要将请求传递给下一个 Filter 或 Servlet,则调用FilterChain对象的doFilter方法。这样,请求就会依次经过 Filter 链中的每个 Filter,直到最后到达目标 Servlet 。例如,在一个 Web 应用中,我们可能配置了一个用于身份验证的 Filter 和一个用于日志记录的 Filter。当用户发送请求时,首先会经过身份验证 Filter,如果验证通过,才会继续经过日志记录 Filter,最后到达目标 Servlet 。
(三)生命周期
Filter 的生命周期包括初始化、拦截请求和销毁三个阶段 。
- 初始化阶段:当 Web 应用启动时,容器会读取配置信息,并创建 Filter 实例,然后调用 Filter 的init方法进行初始化。在init方法中,我们可以进行一些初始化操作,比如读取配置文件、建立数据库连接等 。这个阶段只会在 Web 应用启动时执行一次 。
- 拦截请求阶段:在初始化完成后,Filter 进入拦截请求阶段。当有请求到达时,容器会调用 Filter 的doFilter方法对请求进行处理。在doFilter方法中,我们可以对请求进行过滤、修改或拦截 。这个阶段会在每个请求到达时被执行 。
- 销毁阶段:当 Web 应用关闭或重启时,容器会调用 Filter 的destroy方法进行销毁。在destroy方法中,我们可以进行一些资源的释放和清理操作,比如关闭数据库连接、释放内存等 。这个阶段只会在 Web 应用关闭或重启时执行一次 。
(四) 过滤器详解
2.2.1 执行原理(执行过程)
如果一次请求有多个过滤器可以进行过滤,任意一个过滤器不放行,整个请求就不可能到达目标资源
2.2.2 拦截范围
在过滤器上使用注解@WebFilter时,可以使用注解的urlPatterns或value属性,配置过滤器的拦截范围。
@WebFilter(urlPatterns = "拦截范围")@WebFilter(value = "拦截范围")@WebFilter("拦截范围")
拦截范围的写法,常用的有:
| 拦截范围 | 语法示例 | 作用 |
|---|---|---|
| 精确拦截 | /emps | 只拦截客户端对/emps的请求 |
| 范围拦截 | /emps/*(必须以/开头以*结尾) | 拦截客户端对/emps下的资源请求 比如/emps,/emps/1, /emps/xx/yy等等 |
示例:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/emps/*")
public class Demo01Filter implements Filter {
/**
* 这个方法就是过滤器的过滤方法。客户端的每次请求,只要在此过滤器的过滤范围内,这个方法就必定执行
* @param request 请求对象。它封装了本次http请求的所有数据
* @param response 响应对象。我们向这个对象里设置数据,这些数据最终会被转换成HTTP响应,返回给客户端
* @param chain 过滤器链对象。用于放行请求的
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//在目标方法执行之前的预处理
System.out.println("A");
//可以放行。只有过滤器放行了,本次请求才可能到达目标资源(Controller里的方法)
chain.doFilter(request, response);
//在目标方法执行之后的后处理
System.out.println("B");
}
}
2.2.3 多过滤器的过滤顺序(过滤器链)
按照过滤器的全限定类名进行排序。哪个过滤器排序靠前,哪个过滤器就先执行
(五)简单示例
下面通过一个简单的 Filter 代码示例,来演示如何实现登录校验 。假设我们有一个 Web 应用,只有登录后的用户才能访问某些资源。我们可以创建一个 Filter 来验证用户是否已经登录 。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作,比如读取配置文件等
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession(false);
// 判断用户是否已经登录,这里通过判断session中是否存在用户信息来确定
if (session != null && session.getAttribute("user") != null) {
// 用户已登录,放行请求
filterChain.doFilter(request, response);
} else {
// 用户未登录,重定向到登录页面
response.sendRedirect("login.jsp");
}
}
@Override
public void destroy() {
// 销毁操作,比如关闭数据库连接等
}
}
在这个示例中,我们创建了一个LoginFilter类,实现了Filter接口 。在init方法中,我们可以进行一些初始化操作,这里暂时为空 。在doFilter方法中,我们首先获取当前请求的HttpSession,然后判断session中是否存在user属性 。如果存在,说明用户已经登录,调用filterChain.doFilter(request, response)放行请求;如果不存在,说明用户未登录,调用response.sendRedirect("login.jsp")将用户重定向到登录页面 。在destroy方法中,我们可以进行一些销毁操作,这里也暂时为空 。通过这个示例,我们可以看到 Filter 在实现登录校验方面的简单应用 。
三、Interceptor 全面解析
(一)Interceptor 概念
在 Spring 框架中,Interceptor(拦截器)是一种特殊的组件,它可以对 Controller 层的请求进行拦截和处理 。Interceptor 主要用于在请求处理的不同阶段插入自定义逻辑,比如在请求到达 Controller 之前进行权限验证,在请求处理完成之后进行日志记录等 。它是 Spring MVC 框架的重要组成部分,为我们提供了一种非常灵活的方式来处理请求和响应 。与 Filter 不同,Interceptor 只对 Spring MVC 的 Controller 层的请求起作用,它可以访问 Spring 容器中的各种资源,比如 Bean、ModelAndView 等 。通过使用 Interceptor,我们可以将一些通用的处理逻辑从 Controller 中分离出来,提高代码的可维护性和可扩展性 。
(二)核心原理
Interceptor 基于 Spring 的 AOP(面向切面编程)思想和代理机制实现 。当一个请求到达 Spring MVC 的 DispatcherServlet 时,DispatcherServlet 会根据配置找到对应的 HandlerMapping,HandlerMapping 会将请求映射到具体的 Handler(通常是 Controller 中的方法) 。
在这个过程中,DispatcherServlet 会创建一个 HandlerExecutionChain 对象,该对象包含了 Handler 和一系列的 Interceptor 。然后,DispatcherServlet 会依次调用 Interceptor 的preHandle方法,如果所有的preHandle方法都返回true,则继续调用 Handler 来处理请求 。在 Handler 处理请求完成之后,DispatcherServlet 会依次调用 Interceptor 的postHandle方法,最后调用afterCompletion方法 。如果在preHandle方法中返回false,则会中断请求处理,不会继续调用后续的 Interceptor 和 Handler 。通过这种方式,Interceptor 可以在请求处理的不同阶段对请求进行拦截和处理 。
(三)三个关键方法
- preHandle:这个方法在请求到达 Controller 之前被调用 。它的返回值是一个布尔类型,如果返回true,则表示请求可以继续处理,DispatcherServlet 会继续调用下一个 Interceptor 的preHandle方法或者直接调用 Handler 来处理请求;如果返回false,则表示请求被拦截,DispatcherServlet 会直接返回响应,不再继续处理请求 。在preHandle方法中,我们通常可以进行一些前置处理,比如权限验证、日志记录、参数校验等 。
- postHandle:这个方法在 Controller 处理请求完成之后,视图渲染之前被调用 。在这个方法中,我们可以对 ModelAndView 进行一些修改,比如添加额外的模型数据、修改视图名称等 。需要注意的是,只有当preHandle方法返回true时,postHandle方法才会被调用 。
- afterCompletion:这个方法在整个请求处理完成之后,包括视图渲染结束之后被调用 。无论请求处理过程中是否发生异常,afterCompletion方法都会被调用 。在这个方法中,我们通常可以进行一些资源清理工作,比如关闭数据库连接、释放线程资源等 。同样,只有当preHandle方法返回true时,afterCompletion方法才会被调用 。
(四)使用步骤
-
创建拦截器类:类里只编写拦截过滤的逻辑,不用关心拦截范围
创建Java类,实现
HandlerInterceptor接口,重写接口的方法(需要用哪个,就重写哪个):preHandle方法:在目标方法执行前的预处理方法postHandle方法:在目标方法执行成功后的后处理方法afterCompletion方法:在服务端给客户端返回响应之前执行的最终方法
-
配置拦截器:仅配置拦截的范围,不用关心拦截的具体逻辑
-
创建Java类,作为Spring的配置类
添加注解
@Configuration,把类标记为Spring的配置类实现接口
WebMvcConfigurer,作为Web层框架SpringMVC的配置类,必须实现此接口 -
重写接口的
addInterceptors方法在方法里配置拦截器的拦截范围
-
3.1.2 使用示例
创建拦截器类
创建com.itheima.interceptor包,在包里创建拦截器类
public class Demo01Interceptor implements HandlerInterceptor {
/**
* 预处理方法:在目标方法执行之前先执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Demo01Interceptor.preHandle.....");
//如果方法返回true,表示放行;如果返回false,表示不放行
return true;
}
/**
* 后处理方法:在目标方法执行之后再执行(目标方法执行成功以后,才会执行)
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Demo01Interceptor.postHandle.....");
}
/**
* 最终处理方法:在给客户端返回响应之前,最终执行(无论目标方法有没有异常,都必定会执行)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Demo01Interceptor.afterCompletion.....");
}
}
配置类WebConfig
创建com.itheima.config包,在包里创建配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,并设置拦截范围为/**,表示拦截所有请求
registry.addInterceptor(new Demo01Interceptor()).addPathPatterns("/**");
}
}
(五) 拦截器详解
拦截范围
| 写法 | 示例 | 作用 |
|---|---|---|
| 拦截一级匹配路径 | /emp/* | 只拦截一级目录。 拦截/emp/1, /emp/2;不拦截/emp/xx/yy |
| 拦截任意级匹配路径 | /emp/** | 拦截任意级目录。 拦截/emp,/emp/1, /emp/2,/emp/xx/yy, /emp/xx/yy/zz |
多过滤器的拦截顺序
在配置类里,先配置哪个拦截器,哪个拦截器就先拦截
-
先执行所有拦截器的
preHandle方法。如果任何一个拦截器的preHandle方法返回false,就拦截不放行了
-
再执行目标方法
-
再执行所有拦截器的
postHandle方法。只有在目标方法执行后,没有异常时才会执行的
执行的顺序,和
preHandle的顺序是倒着的 -
再执行所有拦截器的
afterCompletion方法无论目标方法有没有异常,都必定会执行的
执行的顺序,和
preHandle的顺序是倒着的
四、Filter 与 Interceptor 深度对比
(一)实现方式
Filter 基于 Servlet 规范实现,它是由 Servlet 容器管理的 。我们通过实现javax.servlet.Filter接口,并在web.xml文件中进行配置,或者使用注解进行配置,来定义对请求的过滤逻辑 。例如,在 Spring Boot 项目中,我们可以使用@WebFilter注解来配置 Filter ,或者通过创建FilterRegistrationBean来注册 Filter 。而 Interceptor 基于 Spring 框架实现,由 Spring 容器管理 。我们通过实现org.springframework.web.servlet.HandlerInterceptor接口,并在 Spring 的配置文件中进行配置,来定义对请求的拦截逻辑 。在 Spring Boot 项目中,我们通常创建一个配置类,实现WebMvcConfigurer接口,重写addInterceptors方法来注册 Interceptor 。从实现方式上看,Filter 更依赖于 Servlet 容器,而 Interceptor 则与 Spring 框架紧密结合 。
(二)作用范围
Filter 的作用范围更广,可以作用于所有的请求,包括静态资源(如图片、CSS 文件、JavaScript 文件等)和动态资源(如 Servlet、JSP 页面等) 。它可以对整个 Web 应用的请求进行全局的过滤,比如设置字符编码、压缩响应等 。而 Interceptor 主要作用于 Spring 管理的控制器方法的请求 。它可以对特定的请求进行拦截,例如进行权限验证、日志记录等 。Interceptor 可以更加精细地控制对请求的处理,只对需要的 Controller 方法进行拦截,而不会影响其他请求 。例如,在一个电商项目中,我们可以使用 Filter 对所有请求进行统一的字符编码设置,而使用 Interceptor 对用户下单、支付等关键操作进行权限验证和日志记录 。
(三)执行顺序
Filter 的执行顺序是在请求到达 Servlet 之前和响应返回客户端之后 。当有请求到达时,会先依次执行 Filter 链中的各个 Filter 的doFilter方法,在请求处理完成后,再按照相反的顺序依次执行各个 Filter 的doFilter方法的后续部分 。多个过滤器可以按照它们在web.xml文件中的配置顺序依次执行 。而 Interceptor 的执行顺序是在请求到达控制器方法之前和响应返回给客户端之后 。在请求到达 Controller 之前,会依次执行各个 Interceptor 的preHandle方法,如果所有preHandle方法都返回true,则继续调用 Controller 方法;在 Controller 方法处理完成之后,会依次执行各个 Interceptor 的postHandle方法;在整个请求处理完成之后,包括视图渲染结束之后,会依次执行各个 Interceptor 的afterCompletion方法 。多个拦截器可以按照它们在 Spring 配置文件中的配置顺序依次执行,也可以通过实现org.springframework.core.Ordered接口或使用@Order注解来指定执行顺序 。
(四)与业务耦合度
Filter 与业务逻辑的耦合度较低,它主要关注对请求和响应的通用处理,不涉及具体的业务逻辑 。Filter 可以在不修改业务代码的情况下,对请求进行过滤和处理,具有较好的通用性和可移植性 。例如,我们可以在一个项目中添加一个 Filter 来进行日志记录,而不需要修改业务代码 。而 Interceptor 与业务逻辑的耦合度相对较高,它可以在请求到达控制器方法之前和之后执行一些与业务相关的逻辑,例如权限验证、日志记录等 。Interceptor 可以通过获取请求和响应的信息,与业务逻辑进行交互 。在一个用户管理系统中,我们可以使用 Interceptor 来验证用户的登录状态和权限,根据业务规则决定是否允许用户访问某些功能 。
五、实际应用场景剖析
(一)Filter 的应用场景
- 日志记录:在一个电商系统中,我们可以使用 Filter 记录所有用户的请求信息,包括请求的 URL、请求方法、请求参数等 。通过分析这些日志,我们可以了解用户的行为习惯,统计热门商品页面的访问量,以便进行商品推荐和营销活动 。例如,创建一个LoggingFilter,在doFilter方法中使用日志框架(如 Log4j、SLF4J 等)记录请求信息 。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter("/*")
public class LoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
logger.info("Request URL: {}", request.getRequestURI());
logger.info("Request Method: {}", request.getMethod());
// 记录请求参数
request.getParameterMap().forEach((key, values) -> {
for (String value : values) {
logger.info("Parameter {}: {}", key, value);
}
});
filterChain.doFilter(request, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
2. 字符编码处理:在一个多语言的 Web 应用中,为了确保所有请求和响应都使用统一的字符编码(如 UTF-8),避免出现乱码问题,可以使用 Filter 来设置字符编码 。在 Spring Boot 项目中,可以创建一个CharacterEncodingFilter,在doFilter方法中调用request.setCharacterEncoding("UTF-8")和response.setCharacterEncoding("UTF-8")来设置请求和响应的字符编码 。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
filterChain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
3. 请求体和响应体修改:在某些情况下,我们可能需要对请求体和响应体进行修改 。在一个安全要求较高的应用中,为了防止 SQL 注入攻击,可以使用 Filter 对请求体中的参数进行过滤和转义 。在响应返回客户端之前,我们可能还需要对响应体进行加密处理,以保护敏感信息 。例如,创建一个SecurityFilter,在doFilter方法中对请求体进行过滤,对响应体进行加密 。这里假设我们有一个RequestFilterUtil工具类用于过滤请求体,一个ResponseEncryptUtil工具类用于加密响应体 。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter("/*")
public class SecurityFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 过滤请求体
BufferedReader reader = request.getReader();
String requestBody = reader.readLine();
String filteredRequestBody = RequestFilterUtil.filterRequestBody(requestBody);
// 这里可以将过滤后的请求体重新包装成一个新的请求对象,然后传递给下一个过滤器或Servlet,这里省略具体实现
// 创建一个自定义的响应包装类,用于捕获响应体
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ServletOutputStream sos = new ServletOutputStream() {
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
};
PrintWriter pw = new PrintWriter(sos);
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response) {
@Override
public ServletOutputStream getOutputStream() throws IOException {
return sos;
}
@Override
public PrintWriter getWriter() {
return pw;
}
};
filterChain.doFilter(request, responseWrapper);
// 获取响应体并进行加密
String responseBody = bos.toString();
String encryptedResponseBody = ResponseEncryptUtil.encryptResponseBody(responseBody);
// 将加密后的响应体写回客户端
response.getOutputStream().write(encryptedResponseBody.getBytes());
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
(二)Interceptor 的应用场景
- 权限验证:在一个企业级的管理系统中,不同的用户角色具有不同的权限 。管理员可以进行所有操作,而普通用户只能进行部分操作 。我们可以使用 Interceptor 来验证用户的权限 。在preHandle方法中,获取当前用户的角色信息,判断用户是否有权限访问当前请求的资源 。如果有权限,则返回true,放行请求;如果没有权限,则返回false,并返回错误响应 。例如,创建一个PermissionInterceptor,并在 Spring 配置文件中注册 。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求中获取用户角色信息,这里假设用户角色信息存储在session中
String role = (String) request.getSession().getAttribute("role");
// 判断用户是否有权限访问当前请求的资源,这里假设请求的URL为/admin/**需要管理员权限
if (request.getRequestURI().startsWith("/admin/") &&!"admin".equals(role)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
2. 性能监控:在一个高并发的系统中,我们需要监控每个接口的性能,找出性能瓶颈 。可以使用 Interceptor 在preHandle方法中记录请求开始时间,在afterCompletion方法中记录请求结束时间,计算请求处理时间,并将这些信息记录到日志中 。例如,创建一个PerformanceInterceptor,并在 Spring 配置文件中注册 。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(PerformanceInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startTime = (long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info("Request URL: {}, Execution Time: {} ms", request.getRequestURI(), executionTime);
}
}
3. 统一异常处理:在一个大型的分布式系统中,不同的模块可能会抛出各种异常 。为了统一处理这些异常,给用户提供友好的错误提示,可以使用 Interceptor 在afterCompletion方法中捕获异常,并进行统一的处理 。例如,创建一个ExceptionInterceptor,并在 Spring 配置文件中注册 。这里假设我们有一个ExceptionHandlerUtil工具类用于处理异常 。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class ExceptionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) {
ExceptionHandlerUtil.handleException(ex, request, response);
}
}
}
六、项目中两者的协同使用
(一)先后顺序
在一个同时包含 Filter 和 Interceptor 的项目中,Filter 总是先于 Interceptor 执行 。这是因为 Filter 是由 Servlet 容器管理的,在请求到达 Servlet 之前就开始发挥作用 。当一个请求进入 Web 应用时,Servlet 容器会首先根据配置检查是否有过滤器需要应用于该请求 。如果有,就按照过滤器的配置顺序依次执行过滤器的逻辑 。而 Interceptor 是由 Spring 框架管理的,Spring 框架是建立在 Servlet 规范之上的应用框架 。在请求经过 Servlet 容器的初步处理后,才会进入到 Spring 框架的处理流程中,此时才会触发 Interceptor 的执行 。例如,在一个 Spring Boot 项目中,当用户发送一个请求时,首先会经过 Servlet 容器中的 Filter 进行处理,然后才会进入 Spring 框架,由 Interceptor 对请求进行拦截和处理 。
(二)协同案例
假设我们正在开发一个电商项目,需要对用户的请求进行全面的处理 。我们可以结合 Filter 和 Interceptor 来完成这个任务 。首先,我们使用 Filter 来进行全局的日志记录和字符编码设置 。创建一个LoggingFilter用于记录日志,一个CharacterEncodingFilter用于设置字符编码 。
// LoggingFilter.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter("/*")
public class LoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
logger.info("Request URL: {}", request.getRequestURI());
logger.info("Request Method: {}", request.getMethod());
filterChain.doFilter(request, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
// CharacterEncodingFilter.java
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
filterChain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
然后,我们使用 Interceptor 来进行权限验证和性能监控 。创建一个PermissionInterceptor用于权限验证,一个PerformanceInterceptor用于性能监控 。
// PermissionInterceptor.java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求中获取用户角色信息,这里假设用户角色信息存储在session中
String role = (String) request.getSession().getAttribute("role");
// 判断用户是否有权限访问当前请求的资源,这里假设请求的URL为/admin/**需要管理员权限
if (request.getRequestURI().startsWith("/admin/") &&!"admin".equals(role)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
// PerformanceInterceptor.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(PerformanceInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startTime = (long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info("Request URL: {}, Execution Time: {} ms", request.getRequestURI(), executionTime);
}
}
在 Spring 的配置文件中注册 Interceptor 。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private PermissionInterceptor permissionInterceptor;
@Resource
private PerformanceInterceptor performanceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor)
.addPathPatterns("/admin/**");
registry.addInterceptor(performanceInterceptor)
.addPathPatterns("/**");
}
}
在这个案例中,当用户发送请求时,首先会经过LoggingFilter记录日志,然后经过CharacterEncodingFilter设置字符编码 。接着,请求进入 Spring 框架,PermissionInterceptor会对请求进行权限验证,如果用户有权限,PerformanceInterceptor会记录请求的开始时间 。在 Controller 处理完请求后,PerformanceInterceptor会记录请求的结束时间,并计算请求处理时间 。最后,响应会再次经过LoggingFilter,完成整个请求处理流程 。通过这样的协同使用,Filter 和 Interceptor 可以有效地对用户的请求进行全面的处理,提高系统的安全性、性能和可维护性 。
七、总结
(一)总结
在后端开发中,Filter 和 Interceptor 是两个非常重要的概念 。Filter 基于 Servlet 规范,作用于整个 Web 应用,能对所有请求进行过滤,包括静态资源和动态资源 。它主要用于实现一些通用的请求处理逻辑,如日志记录、字符编码处理、请求体和响应体修改等 。
Interceptor 则是 Spring 框架的一部分,主要作用于 Controller 层的请求 。它通过实现HandlerInterceptor接口,提供了preHandle、postHandle和afterCompletion三个方法,让我们可以在请求到达 Controller 之前、Controller 处理请求之后以及整个请求处理完成之后执行自定义逻辑 。Interceptor 常用于权限验证、性能监控、统一异常处理等与业务逻辑紧密相关的场景 。Filter 和 Interceptor 在执行顺序、作用范围和与业务耦合度等方面存在差异 。在实际项目中,我们常常会根据具体需求将它们协同使用,以实现对请求的全面处理 。