找回密码
 立即注册
首页 业界区 业界 MVC中的拦截器实现案例

MVC中的拦截器实现案例

灼巾 14 小时前
MVC 拦截器

Spring MVC 拦截器对应HandlerInterctor接口,该接口位于org.springframework.web.servlet的包中,定义了三个方法,若要实现该接口,就要实现其三个方法:

  • 前置处理(preHandle()方法):该方法在执行控制器方法之前执行。返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行(如果后面没有其他Interceptor,就会执行controller方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理。
  • 后置处理(postHandle()方法):该方法在执行控制器方法调用之后,且在返回ModelAndView之前执行。由于该方法会在DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改。
  • 已完成处理(afterCompletion()方法):该方法在执行完控制器之后执行,由于是在Controller方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作。
可以通过拦截器进行权限检验,参数校验,记录日志等操作
拦截器 (Interceptor) 实现案例

拦截器(Interceptor)依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用。
就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作)。
也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。
但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
自定义拦截器实现
  1. import org.springframework.stereotype.Component;
  2. import org.springframework.web.servlet.HandlerInterceptor;
  3. import org.springframework.web.servlet.ModelAndView;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.util.Arrays;
  7. import java.util.Map;
  8. /**
  9. * 自定义拦截器 - 实现HandlerInterceptor接口
  10. * 适用于业务相关的预处理和后处理
  11. */
  12. @Component
  13. public class CustomInterceptor implements HandlerInterceptor {
  14.    
  15.     private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
  16.    
  17.     /**
  18.      * 预处理回调方法 - 在控制器执行之前调用
  19.      * 返回true表示继续执行,false表示中断执行
  20.      */
  21.     @Override
  22.     public boolean preHandle(HttpServletRequest request,
  23.                            HttpServletResponse response,
  24.                            Object handler) throws Exception {
  25.         
  26.         // 记录请求开始时间
  27.         startTimeThreadLocal.set(System.currentTimeMillis());
  28.         
  29.         // 获取请求信息
  30.         String requestURI = request.getRequestURI();
  31.         String method = request.getMethod();
  32.         String clientIP = getClientIP(request);
  33.         
  34.         System.out.println("【拦截器-preHandle】请求开始: " + method + " " + requestURI);
  35.         System.out.println("【拦截器-preHandle】客户端IP: " + clientIP);
  36.         
  37.         // 1. 权限验证示例
  38.         if (requiresAuth(requestURI) && !isAuthenticated(request)) {
  39.             response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未授权访问");
  40.             return false; // 中断请求
  41.         }
  42.         
  43.         // 2. 参数预处理示例
  44.         Map<String, String[]> params = request.getParameterMap();
  45.         if (!params.isEmpty()) {
  46.             System.out.println("【拦截器-preHandle】请求参数: " + paramsToString(params));
  47.         }
  48.         
  49.         // 3. 设置请求属性,供控制器使用
  50.         request.setAttribute("requestStartTime", System.currentTimeMillis());
  51.         
  52.         return true; // 继续执行后续拦截器和控制器
  53.     }
  54.    
  55.     /**
  56.      * 后处理回调方法 - 在控制器执行之后,视图渲染之前调用(返回ModelAndView之前)
  57.      */
  58.     @Override
  59.     public void postHandle(HttpServletRequest request,
  60.                           HttpServletResponse response,
  61.                           Object handler,
  62.                           ModelAndView modelAndView) throws Exception {
  63.         
  64.         Long startTime = startTimeThreadLocal.get();
  65.         if (startTime != null) {
  66.             long costTime = System.currentTimeMillis() - startTime;
  67.             System.out.println("【拦截器-postHandle】请求处理耗时: " + costTime + "ms");
  68.             
  69.             // 可以修改ModelAndView
  70.             if (modelAndView != null) {
  71.                 modelAndView.addObject("processTime", costTime);
  72.                 modelAndView.addObject("interceptorMessage", "经过拦截器处理");
  73.             }
  74.         }
  75.     }
  76.    
  77.     /**
  78.      * 完成回调方法 - 在整个请求完成之后调用(视图渲染完成后)
  79.      * 适合进行资源清理
  80.      */
  81.     @Override
  82.     public void afterCompletion(HttpServletRequest request,
  83.                                HttpServletResponse response,
  84.                                Object handler,
  85.                                Exception ex) throws Exception {
  86.         
  87.         Long startTime = startTimeThreadLocal.get();
  88.         if (startTime != null) {
  89.             long totalTime = System.currentTimeMillis() - startTime;
  90.             int status = response.getStatus();
  91.             
  92.             System.out.println("【拦截器-afterCompletion】请求完成: " +
  93.                 request.getRequestURI() + " 状态: " + status + " 总耗时: " + totalTime + "ms");
  94.             
  95.             if (ex != null) {
  96.                 System.err.println("【拦截器-afterCompletion】异常信息: " + ex.getMessage());
  97.             }
  98.             
  99.             // 清理ThreadLocal,防止内存泄漏
  100.             startTimeThreadLocal.remove();
  101.         }
  102.     }
  103.    
  104.     // ========== 工具方法 ==========
  105.    
  106.     private String getClientIP(HttpServletRequest request) {
  107.         String ip = request.getHeader("X-Forwarded-For");
  108.         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  109.             ip = request.getHeader("Proxy-Client-IP");
  110.         }
  111.         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  112.             ip = request.getHeader("WL-Proxy-Client-IP");
  113.         }
  114.         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
  115.             ip = request.getRemoteAddr();
  116.         }
  117.         return ip;
  118.     }
  119.    
  120.     private boolean requiresAuth(String uri) {
  121.         // 需要认证的路径规则
  122.         return uri.startsWith("/api/admin") || uri.startsWith("/api/user");
  123.     }
  124.    
  125.     private boolean isAuthenticated(HttpServletRequest request) {
  126.         // 简单的认证检查
  127.         String token = request.getHeader("Authorization");
  128.         return token != null && token.startsWith("Bearer ");
  129.     }
  130.    
  131.     private String paramsToString(Map<String, String[]> params) {
  132.         StringBuilder sb = new StringBuilder();
  133.         for (Map.Entry<String, String[]> entry : params.entrySet()) {
  134.             sb.append(entry.getKey()).append("=")
  135.               .append(Arrays.toString(entry.getValue())).append("; ");
  136.         }
  137.         return sb.toString();
  138.     }
  139. }
复制代码
拦截器配置
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  5. import java.util.Arrays;
  6. import java.util.List;
  7. @Configuration
  8. public class InterceptorConfig implements WebMvcConfigurer {
  9.    
  10.     @Autowired
  11.     private CustomInterceptor customInterceptor;
  12.    
  13.     // 排除路径
  14.     private static final List<String> EXCLUDE_PATHS = Arrays.asList(
  15.         "/api/public/**",
  16.         "/static/**",
  17.         "/error",
  18.         "/login",
  19.         "/logout"
  20.     );
  21.    
  22.     @Override
  23.     public void addInterceptors(InterceptorRegistry registry) {
  24.         // 注册自定义拦截器
  25.         registry.addInterceptor(customInterceptor)
  26.                 .addPathPatterns("/**")                    // 拦截所有路径
  27.                 .excludePathPatterns(EXCLUDE_PATHS);        // 排除特定路径
  28.         
  29.         // 可以注册多个拦截器,按添加顺序执行
  30.         registry.addInterceptor(new LoggingInterceptor())
  31.                 .addPathPatterns("/api/**")
  32.                 .order(1); // 设置执行顺序,数字越小优先级越高
  33.     }
  34. }
  35. /**
  36. * 另一个日志拦截器示例
  37. */
  38. class LoggingInterceptor implements HandlerInterceptor {
  39.    
  40.     @Override
  41.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
  42.         System.out.println("【日志拦截器】请求路径: " + request.getRequestURI());
  43.         return true;
  44.     }
  45. }
复制代码
过滤器 (Filter) 实现案例

过滤器 (Filter) 依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。
使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据。
比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。
通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器))。
如:过滤低俗文字、危险字符等。
自定义过滤器实现
  1. import org.springframework.core.annotation.Order;
  2. import org.springframework.stereotype.Component;
  3. import javax.servlet.*;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. /**
  8. * 自定义过滤器 - 实现Filter接口
  9. * 适用于通用的请求/响应处理
  10. */
  11. @Component
  12. @Order(1) // 执行顺序,数字越小优先级越高
  13. public class CustomFilter implements Filter {
  14.    
  15.     /**
  16.      * 初始化方法
  17.      */
  18.     @Override
  19.     public void init(FilterConfig filterConfig) throws ServletException {
  20.         System.out.println("【过滤器】CustomFilter初始化完成");
  21.     }
  22.    
  23.     /**
  24.      * 过滤处理方法
  25.      */
  26.     @Override
  27.     public void doFilter(ServletRequest request, ServletResponse response,
  28.                         FilterChain chain) throws IOException, ServletException {
  29.         
  30.         HttpServletRequest httpRequest = (HttpServletRequest) request;
  31.         HttpServletResponse httpResponse = (HttpServletResponse) response;
  32.         
  33.         long startTime = System.currentTimeMillis();
  34.         System.out.println("【过滤器-doFilter】请求进入: " + httpRequest.getRequestURI());
  35.         
  36.         // 1. 设置字符编码
  37.         httpRequest.setCharacterEncoding("UTF-8");
  38.         httpResponse.setCharacterEncoding("UTF-8");
  39.         httpResponse.setContentType("text/html;charset=UTF-8");
  40.         
  41.         // 2. 设置CORS头(跨域支持)
  42.         httpResponse.setHeader("Access-Control-Allow-Origin", "*");
  43.         httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  44.         httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  45.         
  46.         // 3. 预处理逻辑
  47.         if (isOptionsRequest(httpRequest)) {
  48.             // 处理预检请求
  49.             httpResponse.setStatus(HttpServletResponse.SC_OK);
  50.             return;
  51.         }
  52.         
  53.         // 4. 请求包装(可以修改请求参数)
  54.         CustomHttpServletRequestWrapper wrappedRequest =
  55.             new CustomHttpServletRequestWrapper(httpRequest);
  56.         
  57.         // 5. 响应包装(可以修改响应内容)
  58.         CustomHttpServletResponseWrapper wrappedResponse =
  59.             new CustomHttpServletResponseWrapper(httpResponse);
  60.         
  61.         try {
  62.             // 继续执行过滤器链
  63.             chain.doFilter(wrappedRequest, wrappedResponse);
  64.             
  65.             // 后处理逻辑
  66.             long costTime = System.currentTimeMillis() - startTime;
  67.             System.out.println("【过滤器-doFilter】请求完成: " +
  68.                 httpRequest.getRequestURI() + " 耗时: " + costTime + "ms");
  69.             
  70.             // 可以在这里修改响应内容
  71.             byte[] responseData = wrappedResponse.getData();
  72.             if (responseData.length > 0) {
  73.                 // 对响应数据进行处理
  74.                 String modifiedResponse = modifyResponse(new String(responseData));
  75.                 response.getOutputStream().write(modifiedResponse.getBytes());
  76.             }
  77.             
  78.         } catch (Exception e) {
  79.             // 异常处理
  80.             System.err.println("【过滤器-doFilter】处理异常: " + e.getMessage());
  81.             httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器内部错误");
  82.         }
  83.     }
  84.    
  85.     /**
  86.      * 销毁方法
  87.      */
  88.     @Override
  89.     public void destroy() {
  90.         System.out.println("【过滤器】CustomFilter销毁");
  91.     }
  92.    
  93.     // ========== 工具方法 ==========
  94.    
  95.     private boolean isOptionsRequest(HttpServletRequest request) {
  96.         return "OPTIONS".equalsIgnoreCase(request.getMethod());
  97.     }
  98.    
  99.     private String modifyResponse(String originalResponse) {
  100.         // 简单的响应内容修改示例
  101.         return originalResponse.replace("旧内容", "新内容");
  102.     }
  103. }
  104. /**
  105. * 自定义请求包装器 - 用于修改请求参数
  106. */
  107. class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
  108.    
  109.     public CustomHttpServletRequestWrapper(HttpServletRequest request) {
  110.         super(request);
  111.     }
  112.    
  113.     @Override
  114.     public String getParameter(String name) {
  115.         // 可以在这里修改请求参数
  116.         String value = super.getParameter(name);
  117.         if ("username".equals(name) && value != null) {
  118.             return value.trim(); // 去除用户名前后空格
  119.         }
  120.         return value;
  121.     }
  122. }
  123. /**
  124. * 自定义响应包装器 - 用于捕获和修改响应内容
  125. */
  126. class CustomHttpServletResponseWrapper extends HttpServletResponseWrapper {
  127.    
  128.     private final CustomServletOutputStream outputStream;
  129.     private final PrintWriter printWriter;
  130.    
  131.     public CustomHttpServletResponseWrapper(HttpServletResponse response) {
  132.         super(response);
  133.         this.outputStream = new CustomServletOutputStream();
  134.         this.printWriter = new PrintWriter(outputStream);
  135.     }
  136.    
  137.     @Override
  138.     public ServletOutputStream getOutputStream() {
  139.         return outputStream;
  140.     }
  141.    
  142.     @Override
  143.     public PrintWriter getWriter() {
  144.         return printWriter;
  145.     }
  146.    
  147.     public byte[] getData() {
  148.         try {
  149.             printWriter.flush();
  150.             return outputStream.toByteArray();
  151.         } catch (IOException e) {
  152.             return new byte[0];
  153.         }
  154.     }
  155. }
  156. /**
  157. * 自定义Servlet输出流
  158. */
  159. class CustomServletOutputStream extends ServletOutputStream {
  160.    
  161.     private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  162.    
  163.     @Override
  164.     public void write(int b) {
  165.         buffer.write(b);
  166.     }
  167.    
  168.     @Override
  169.     public boolean isReady() {
  170.         return true;
  171.     }
  172.    
  173.     @Override
  174.     public void setWriteListener(WriteListener listener) {
  175.         // 不需要实现
  176.     }
  177.    
  178.     public byte[] toByteArray() {
  179.         return buffer.toByteArray();
  180.     }
  181. }
复制代码
过滤器配置(多种方式)


  • 使用 @Component + @Order(推荐): 上面的示例已经使用这种方式
  • 使用 FilterRegistrationBean(更灵活)
  1. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. public class FilterConfig {
  6.    
  7.     @Bean
  8.     public FilterRegistrationBean<CustomFilter> customFilterRegistration() {
  9.         FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>();
  10.         registration.setFilter(new CustomFilter());
  11.         registration.addUrlPatterns("/*"); // 过滤所有请求
  12.         registration.setOrder(1); // 执行顺序
  13.         registration.setName("customFilter");
  14.         
  15.         // 可以设置初始化参数
  16.         registration.addInitParameter("param1", "value1");
  17.         
  18.         return registration;
  19.     }
  20.    
  21.     @Bean
  22.     public FilterRegistrationBean<LoggingFilter> loggingFilterRegistration() {
  23.         FilterRegistrationBean<LoggingFilter> registration = new FilterRegistrationBean<>();
  24.         registration.setFilter(new LoggingFilter());
  25.         registration.addUrlPatterns("/api/*");
  26.         registration.setOrder(2);
  27.         return registration;
  28.     }
  29. }
  30. /**
  31. * 日志过滤器
  32. */
  33. class LoggingFilter implements Filter {
  34.    
  35.     @Override
  36.     public void doFilter(ServletRequest request, ServletResponse response,
  37.                         FilterChain chain) throws IOException, ServletException {
  38.         
  39.         HttpServletRequest httpRequest = (HttpServletRequest) request;
  40.         System.out.println("【日志过滤器】请求: " + httpRequest.getMethod() + " " + httpRequest.getRequestURI());
  41.         
  42.         chain.doFilter(request, response);
  43.     }
  44. }
复制代码

  • 使用 @WebFilter 注解
  1. import javax.servlet.annotation.WebFilter;
  2. import javax.servlet.annotation.WebInitParam;
  3. @WebFilter(
  4.     urlPatterns = "/*",
  5.     initParams = {
  6.         @WebInitParam(name = "encoding", value = "UTF-8"),
  7.         @WebInitParam(name = "enableCors", value = "true")
  8.     }
  9. )
  10. public class AnnotationFilter implements Filter {
  11.     // 实现同上
  12. }
复制代码
需要在启动类添加 @ServletComponentScan:
  1. @SpringBootApplication
  2. @ServletComponentScan // 扫描@WebFilter、@WebServlet等注解
  3. public class Application {
  4.     public static void main(String[] args) {
  5.         SpringApplication.run(Application.class, args);
  6.     }
  7. }
复制代码
MVC 的Interctor和 Filter 过滤器的区别


  • 功能相同:Interctor和 Filter 都能实现相应的功能
  • 容器不同:Interctor构建在 Spring MVC 体系中;Filter 构建在 Servlet 容器之上
  • 拦截内容不同:Filter对所有访问进行增强,Interctor仅对MVC访问进行增强
  • 使用便利性不同:Interctor提供了三个方法,分别在不同的时机执行;过滤器仅提供一个方法
使用场景区别


  • 使用拦截器的场景:

    • 业务逻辑处理:权限验证、日志记录、参数预处理
    • 需要访问Spring上下文:需要注入Spring Bean的业务
    • 控制器相关处理:只需要对Spring MVC管理的请求进行处理
    • 需要修改ModelAndView:在视图渲染前修改模型数据

  • 使用过滤器的场景:

    • 通用请求处理:字符编码、CORS支持、压缩处理
    • 静态资源处理:需要对所有请求(包括静态资源)进行处理
    • 底层请求处理:需要在DispatcherServlet之前执行的逻辑
    • 请求/响应包装:需要修改请求参数或响应内容

组合使用示例
  1. /**
  2. * 完整的请求处理链示例
  3. * 过滤器(通用处理) → 拦截器(业务处理) → 控制器
  4. */
  5. // 1. 过滤器:处理字符编码、CORS等通用逻辑
  6. @Component
  7. @Order(1)
  8. public class GlobalFilter implements Filter {
  9.     // 处理所有请求的通用逻辑
  10. }
  11. // 2. 拦截器:处理业务相关的逻辑
  12. @Component  
  13. public class AuthInterceptor implements HandlerInterceptor {
  14.     // 处理需要认证的业务逻辑
  15. }
  16. // 3. 控制器:处理具体业务
  17. @RestController
  18. public class BusinessController {
  19.     // 业务实现
  20. }
复制代码
过滤器和拦截器执行顺序

请求进入 → 过滤器预处理 → Spring MVC 核心处理 → 拦截器预处理 → 控制器执行 → 拦截器后处理 → 拦截器完成处理 → 过滤器后处理 → 响应返回
1.png

详细执行步骤分解:
第一阶段:过滤器预处理(Filter Pre-processing)

  • 进入过滤器:客户端请求首先到达 Servlet 容器
  • 执行 chain.doFilter(request, response)之前的逻辑

    • 对请求进行通用预处理
    • 常见操作:设置字符编码、添加 CORS 头部、安全检查、请求日志记录
    • 此时尚未进入 Spring MVC 框架

第二阶段:Spring MVC 核心分发

  • Servlet 的 service()方法:请求进入 Servlet 容器处理流程
  • Spring MVC 的 doService()方法:Spring MVC 框架开始接管请求
  • Spring MVC 请求分发方法:DispatcherServlet根据 URL 映射确定对应的处理器(Handler)
第三阶段:拦截器预处理

  • 进入拦截器:请求正式进入 Spring MVC 的拦截器链
  • 执行 Controller之前调用 preHandle()

    • 进行业务相关的预处理
    • 常见操作:用户认证、权限检查、参数验证、业务日志
    • 返回值控制:

      • true:继续执行后续拦截器和控制器
      • false:中断请求,直接返回


第四阶段:控制器执行

  • 执行具体的业务逻辑
  • 准备模型数据(Model)
  • 确定视图信息(View)
第五阶段:拦截器后处理

  • postHandle()方法执行

    • 时机:控制器逻辑执行完毕,但在返回 ModelAndView之前
    • 能力:可以查看和修改控制器返回的 ModelAndView对象
    • 常见操作:添加全局模型数据、记录处理结果、统一响应格式

第六阶段:拦截器完成处理,afterCompletion()方法执行

  • 时机:控制器已经返回 ModelAndView,但在过滤器将响应返回给客户端之前
  • 特点:无论请求成功还是异常,都会执行(类似 finally块)
  • 常见操作:清理 ThreadLocal 资源、记录最终处理状态、性能监控
第七阶段:过滤器后处理,FilterAfter逻辑执行

  • 时机:服务器端所有逻辑执行完成,准备将响应返回给客户端之前
  • 常见操作:响应压缩、添加安全头部、响应日志记录、编码最后确认
多拦截器执行顺序


  • 当配置多个拦截器时,会形成拦截器链
  • 拦截器的运行顺序参照拦截器添加顺序为准,即addInterctor的顺序(过滤器同理)
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器afterCompletion
流程解析看下图:
2.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册