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请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
自定义拦截器实现
- 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;import java.util.Arrays;import java.util.Map;/** * 自定义拦截器 - 实现HandlerInterceptor接口 * 适用于业务相关的预处理和后处理 */@Componentpublic class CustomInterceptor implements HandlerInterceptor { private static final ThreadLocal startTimeThreadLocal = new ThreadLocal(); /** * 预处理回调方法 - 在控制器执行之前调用 * 返回true表示继续执行,false表示中断执行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 记录请求开始时间 startTimeThreadLocal.set(System.currentTimeMillis()); // 获取请求信息 String requestURI = request.getRequestURI(); String method = request.getMethod(); String clientIP = getClientIP(request); System.out.println("【拦截器-preHandle】请求开始: " + method + " " + requestURI); System.out.println("【拦截器-preHandle】客户端IP: " + clientIP); // 1. 权限验证示例 if (requiresAuth(requestURI) && !isAuthenticated(request)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未授权访问"); return false; // 中断请求 } // 2. 参数预处理示例 Map params = request.getParameterMap(); if (!params.isEmpty()) { System.out.println("【拦截器-preHandle】请求参数: " + paramsToString(params)); } // 3. 设置请求属性,供控制器使用 request.setAttribute("requestStartTime", System.currentTimeMillis()); return true; // 继续执行后续拦截器和控制器 } /** * 后处理回调方法 - 在控制器执行之后,视图渲染之前调用(返回ModelAndView之前) */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { Long startTime = startTimeThreadLocal.get(); if (startTime != null) { long costTime = System.currentTimeMillis() - startTime; System.out.println("【拦截器-postHandle】请求处理耗时: " + costTime + "ms"); // 可以修改ModelAndView if (modelAndView != null) { modelAndView.addObject("processTime", costTime); modelAndView.addObject("interceptorMessage", "经过拦截器处理"); } } } /** * 完成回调方法 - 在整个请求完成之后调用(视图渲染完成后) * 适合进行资源清理 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { Long startTime = startTimeThreadLocal.get(); if (startTime != null) { long totalTime = System.currentTimeMillis() - startTime; int status = response.getStatus(); System.out.println("【拦截器-afterCompletion】请求完成: " + request.getRequestURI() + " 状态: " + status + " 总耗时: " + totalTime + "ms"); if (ex != null) { System.err.println("【拦截器-afterCompletion】异常信息: " + ex.getMessage()); } // 清理ThreadLocal,防止内存泄漏 startTimeThreadLocal.remove(); } } // ========== 工具方法 ========== private String getClientIP(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } private boolean requiresAuth(String uri) { // 需要认证的路径规则 return uri.startsWith("/api/admin") || uri.startsWith("/api/user"); } private boolean isAuthenticated(HttpServletRequest request) { // 简单的认证检查 String token = request.getHeader("Authorization"); return token != null && token.startsWith("Bearer "); } private String paramsToString(Map params) { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : params.entrySet()) { sb.append(entry.getKey()).append("=") .append(Arrays.toString(entry.getValue())).append("; "); } return sb.toString(); }}
复制代码 拦截器配置
- import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.Arrays;import java.util.List;@Configurationpublic class InterceptorConfig implements WebMvcConfigurer { @Autowired private CustomInterceptor customInterceptor; // 排除路径 private static final List EXCLUDE_PATHS = Arrays.asList( "/api/public/**", "/static/**", "/error", "/login", "/logout" ); @Override public void addInterceptors(InterceptorRegistry registry) { // 注册自定义拦截器 registry.addInterceptor(customInterceptor) .addPathPatterns("/**") // 拦截所有路径 .excludePathPatterns(EXCLUDE_PATHS); // 排除特定路径 // 可以注册多个拦截器,按添加顺序执行 registry.addInterceptor(new LoggingInterceptor()) .addPathPatterns("/api/**") .order(1); // 设置执行顺序,数字越小优先级越高 }}/** * 另一个日志拦截器示例 */class LoggingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("【日志拦截器】请求路径: " + request.getRequestURI()); return true; }}
复制代码 过滤器 (Filter) 实现案例
过滤器 (Filter) 依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。
使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据。
比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。
通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器))。
如:过滤低俗文字、危险字符等。
自定义过滤器实现
- import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 自定义过滤器 - 实现Filter接口 * 适用于通用的请求/响应处理 */@Component@Order(1) // 执行顺序,数字越小优先级越高public class CustomFilter implements Filter { /** * 初始化方法 */ @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("【过滤器】CustomFilter初始化完成"); } /** * 过滤处理方法 */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; long startTime = System.currentTimeMillis(); System.out.println("【过滤器-doFilter】请求进入: " + httpRequest.getRequestURI()); // 1. 设置字符编码 httpRequest.setCharacterEncoding("UTF-8"); httpResponse.setCharacterEncoding("UTF-8"); httpResponse.setContentType("text/html;charset=UTF-8"); // 2. 设置CORS头(跨域支持) httpResponse.setHeader("Access-Control-Allow-Origin", "*"); httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); // 3. 预处理逻辑 if (isOptionsRequest(httpRequest)) { // 处理预检请求 httpResponse.setStatus(HttpServletResponse.SC_OK); return; } // 4. 请求包装(可以修改请求参数) CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper(httpRequest); // 5. 响应包装(可以修改响应内容) CustomHttpServletResponseWrapper wrappedResponse = new CustomHttpServletResponseWrapper(httpResponse); try { // 继续执行过滤器链 chain.doFilter(wrappedRequest, wrappedResponse); // 后处理逻辑 long costTime = System.currentTimeMillis() - startTime; System.out.println("【过滤器-doFilter】请求完成: " + httpRequest.getRequestURI() + " 耗时: " + costTime + "ms"); // 可以在这里修改响应内容 byte[] responseData = wrappedResponse.getData(); if (responseData.length > 0) { // 对响应数据进行处理 String modifiedResponse = modifyResponse(new String(responseData)); response.getOutputStream().write(modifiedResponse.getBytes()); } } catch (Exception e) { // 异常处理 System.err.println("【过滤器-doFilter】处理异常: " + e.getMessage()); httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器内部错误"); } } /** * 销毁方法 */ @Override public void destroy() { System.out.println("【过滤器】CustomFilter销毁"); } // ========== 工具方法 ========== private boolean isOptionsRequest(HttpServletRequest request) { return "OPTIONS".equalsIgnoreCase(request.getMethod()); } private String modifyResponse(String originalResponse) { // 简单的响应内容修改示例 return originalResponse.replace("旧内容", "新内容"); }}/** * 自定义请求包装器 - 用于修改请求参数 */class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { // 可以在这里修改请求参数 String value = super.getParameter(name); if ("username".equals(name) && value != null) { return value.trim(); // 去除用户名前后空格 } return value; }}/** * 自定义响应包装器 - 用于捕获和修改响应内容 */class CustomHttpServletResponseWrapper extends HttpServletResponseWrapper { private final CustomServletOutputStream outputStream; private final PrintWriter printWriter; public CustomHttpServletResponseWrapper(HttpServletResponse response) { super(response); this.outputStream = new CustomServletOutputStream(); this.printWriter = new PrintWriter(outputStream); } @Override public ServletOutputStream getOutputStream() { return outputStream; } @Override public PrintWriter getWriter() { return printWriter; } public byte[] getData() { try { printWriter.flush(); return outputStream.toByteArray(); } catch (IOException e) { return new byte[0]; } }}/** * 自定义Servlet输出流 */class CustomServletOutputStream extends ServletOutputStream { private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @Override public void write(int b) { buffer.write(b); } @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener listener) { // 不需要实现 } public byte[] toByteArray() { return buffer.toByteArray(); }}
复制代码 过滤器配置(多种方式)
- 使用 @Component + @Order(推荐): 上面的示例已经使用这种方式
- 使用 FilterRegistrationBean(更灵活)
- import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class FilterConfig { @Bean public FilterRegistrationBean customFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CustomFilter()); registration.addUrlPatterns("/*"); // 过滤所有请求 registration.setOrder(1); // 执行顺序 registration.setName("customFilter"); // 可以设置初始化参数 registration.addInitParameter("param1", "value1"); return registration; } @Bean public FilterRegistrationBean loggingFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new LoggingFilter()); registration.addUrlPatterns("/api/*"); registration.setOrder(2); return registration; }}/** * 日志过滤器 */class LoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; System.out.println("【日志过滤器】请求: " + httpRequest.getMethod() + " " + httpRequest.getRequestURI()); chain.doFilter(request, response); }}
复制代码- import javax.servlet.annotation.WebFilter;import javax.servlet.annotation.WebInitParam;@WebFilter( urlPatterns = "/*", initParams = { @WebInitParam(name = "encoding", value = "UTF-8"), @WebInitParam(name = "enableCors", value = "true") })public class AnnotationFilter implements Filter { // 实现同上}
复制代码 需要在启动类添加 @ServletComponentScan:- @SpringBootApplication@ServletComponentScan // 扫描@WebFilter、@WebServlet等注解public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
复制代码 MVC 的Interctor和 Filter 过滤器的区别
- 功能相同:Interctor和 Filter 都能实现相应的功能
- 容器不同:Interctor构建在 Spring MVC 体系中;Filter 构建在 Servlet 容器之上
- 拦截内容不同:Filter对所有访问进行增强,Interctor仅对MVC访问进行增强
- 使用便利性不同:Interctor提供了三个方法,分别在不同的时机执行;过滤器仅提供一个方法
使用场景区别
- 使用拦截器的场景:
- 业务逻辑处理:权限验证、日志记录、参数预处理
- 需要访问Spring上下文:需要注入Spring Bean的业务
- 控制器相关处理:只需要对Spring MVC管理的请求进行处理
- 需要修改ModelAndView:在视图渲染前修改模型数据
- 使用过滤器的场景:
- 通用请求处理:字符编码、CORS支持、压缩处理
- 静态资源处理:需要对所有请求(包括静态资源)进行处理
- 底层请求处理:需要在DispatcherServlet之前执行的逻辑
- 请求/响应包装:需要修改请求参数或响应内容
组合使用示例
- /** * 完整的请求处理链示例 * 过滤器(通用处理) → 拦截器(业务处理) → 控制器 */// 1. 过滤器:处理字符编码、CORS等通用逻辑@Component@Order(1)public class GlobalFilter implements Filter { // 处理所有请求的通用逻辑}// 2. 拦截器:处理业务相关的逻辑@Component public class AuthInterceptor implements HandlerInterceptor { // 处理需要认证的业务逻辑}// 3. 控制器:处理具体业务@RestControllerpublic class BusinessController { // 业务实现}
复制代码 过滤器和拦截器执行顺序
请求进入 → 过滤器预处理 → Spring MVC 核心处理 → 拦截器预处理 → 控制器执行 → 拦截器后处理 → 拦截器完成处理 → 过滤器后处理 → 响应返回
详细执行步骤分解:
第一阶段:过滤器预处理(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
流程解析看下图:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |