八股文小记 Servlet 过滤器-Spring MVC 拦截器-Spring AOP 拦截器区别

您对执行机制的洞察非常准确!让我们深入分析这三种组件的调用机制及其与 AOP 节点的关系:

一、执行机制的本质区别

组件调用机制实现原理
Servlet 过滤器递归调用通过 FilterChain.doFilter() 显式递归调用下一个节点
Spring MVC 拦截器遍历调用HandlerExecutionChain 内部维护列表,顺序遍历执行
Spring AOP 拦截器链式递归调用通过 MethodInvocation.proceed() 递归调用下一个拦截器,目标方法作为终点节点

二、详细机制解析

1. Servlet 过滤器:显式递归调用

源码调用,通过递归调用

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (Globals.IS_SECURITY_ENABLED) {ServletRequest req = request;ServletResponse res = response;try {AccessController.doPrivileged(() -> {this.internalDoFilter(req, res); //调用过滤器链return null;});} catch (PrivilegedActionException var7) {Exception e = var7.getException();if (e instanceof ServletException) {throw (ServletException)e;}if (e instanceof IOException) {throw (IOException)e;}if (e instanceof RuntimeException) {throw (RuntimeException)e;}throw new ServletException(e.getMessage(), e);}} else {this.internalDoFilter(request, response);}}private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// 1. 还有过滤器就继续执行if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);  // 把 this(链本身)传回去,提供给后续过滤器使用,每次调用就会到这个过滤器链。return;}// 2. 没有过滤器了——终点:调 Servletservlet.service(request, response);
}
public class MyFilter implements Filter {public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {// 前置处理 (递归入口)System.out.println("Filter前处理");// ▶ 显式递归调用:必须手动触发下一个节点 ◀chain.doFilter(request, response);// 后置处理 (递归返回)System.out.println("Filter后处理");}
}

调用栈示例

chain.doFilter
chain.doFilter
chain.doFilter
后处理
后处理
返回
Servlet.service
2. MVC 拦截器:隐式遍历调用

源码调用所有拦截器

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {HandlerExecutionChain mappedHandler = getHandler(request); // 获取执行链// 1. 前置拦截if (!mappedHandler.applyPreHandle(request, response)) return; // 2. 执行业务ControllerModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 3. 后置拦截mappedHandler.applyPostHandle(request, response, mv);// 4. 渲染视图后触发afterCompletionmappedHandler.triggerAfterCompletion(request, response, null);
}
public class HandlerExecutionChain {private final List<HandlerInterceptor> interceptors = new ArrayList<>();boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {// ▶ 顺序遍历执行 preHandle ◀for (int i = 0; i < this.interceptors.size(); i++) {if (!interceptors.get(i).preHandle(request, response, this.handler)) {return false; // 中断}}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {// ▶ 逆序遍历执行 postHandle ◀for (int i = this.interceptors.size() - 1; i >= 0; i--) {interceptors.get(i).postHandle(request, response, this.handler, mv);}}
}
AOP增强,本质上也是拦截器MethodInterceptor
一、核心矛盾解析:为什么最后一个节点直接调用业务方法?

源码调用,每次调用将该拦截器链传入进去。ReflectiveMethodInvocation源码,外部使用这个出发拦截器链。

public Object proceed() throws Throwable {// 检查是否到达链末端if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint(); // 执行业务方法}// 获取下一个拦截器Object interceptorOrInterceptionAdvice =this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);// 递归调用下一个拦截器 ▼▼▼ 核心递归点 ▼▼▼return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
二、执行流程全景解析
proceed()方法拦截器1拦截器2业务方法调用invoke(this)执行前置逻辑调用proceed()调用invoke(this)执行前置逻辑调用proceed()执行原始业务方法返回结果返回结果执行后置逻辑返回结果执行后置逻辑返回结果proceed()方法拦截器1拦截器2业务方法

三、后置增强的逻辑

后置增强并非独立的拦截器节点,而是通过递归返回机制实现的:

1. 拦截器的标准结构
public class MyInterceptor implements MethodInterceptor {public Object invoke(MethodInvocation mi) throws Throwable {// 前置逻辑System.out.println("Before business method");// 关键:递归调用链Object result = mi.proceed();// 后置逻辑System.out.println("After business method");return result;}
}
2. 执行栈展开过程
// 伪代码表示执行栈
Stack:
1. MyInterceptor.invoke() -> 调用 mi.proceed()2. ReflectiveMethodInvocation.proceed() -> 调用下一个拦截器... 递归直到最后一个 ...N. ReflectiveMethodInvocation.proceed()-> 执行原始方法-> 返回结果// 栈开始展开
N-1. 上一个拦截器收到结果-> 执行后置逻辑-> 返回结果... 递归返回 ...1. 第一个拦截器收到结果-> 执行后置逻辑-> 返回最终结果

四、源码验证:Spring 内置拦截器实现

1. 后置返回通知 (AfterReturningAdviceInterceptor)
public Object invoke(MethodInvocation mi) throws Throwable {// 前置:无操作// 关键:先执行后续链(包含业务方法)Object retVal = mi.proceed();// 后置:在业务方法返回后执行this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;
}
2. 最终通知 (AspectJAfterAdvice)
public Object invoke(MethodInvocation mi) throws Throwable {try {// 关键:先执行后续链return mi.proceed();} finally {// 后置:在finally块中确保执行invokeAdviceMethod();}
}

执行顺序

遍历preHandle
Interceptor1.pre
Interceptor2.pre
Interceptor3.pre
Controller
逆序postHandle
Interceptor3.post
Interceptor2.post
Interceptor1.post

三、拦截器方法与 AOP 节点的关系

1. MVC 拦截器 vs AOP 拦截器
特性MVC 拦截器方法AOP 拦截节点
执行位置preHandle/postHandleMethodInterceptor.invoke()
调用方式遍历调用链式递归调用
目标对象Controller 方法任意 Spring Bean 方法
访问权限只能访问 HTTP 请求/响应能访问方法参数、返回值、目标对象
是否共享节点❌ 独立于 AOP 链✅ 是 AOP 链的一部分
2. 关键区别图示
graph TBsubgraph HTTP请求流程Filter[Servlet过滤器] --> Interceptor[MVC拦截器]Interceptor --> Dispatcher[DispatcherServlet]endsubgraph Spring处理流程Dispatcher --> Proxy[Controller代理对象]Proxy --> AOP[AOP拦截链]AOP --> Bean[原始Controller方法]endnote[重要:MVC拦截器在AOP代理之外执行]

四、自定义组件加入位置

1. Servlet 过滤器加入点
@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean<MyFilter> myFilter() {FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();bean.setFilter(new MyFilter());bean.setOrder(1); // ▶ 关键:通过order控制递归顺序 ◀bean.addUrlPatterns("/*");return bean;}
}
2. MVC 拦截器加入点
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// ▶ 加入遍历列表,按注册顺序执行 ◀registry.addInterceptor(new AuthInterceptor()).order(1) // 可选排序.addPathPatterns("/api/**");}
}

五、执行流程全景图

ClientFilter1Filter2DispatcherServletMVC InterceptorAOP InterceptorControllerHTTP Request前置处理chain.doFilter()前置处理chain.doFilter()preHandle()true/false进入代理拦截器1拦截器2目标方法结果拦截器后处理返回postHandle()直接返回alt[继续执行][中断]后置处理返回后置处理HTTP ResponseClientFilter1Filter2DispatcherServletMVC InterceptorAOP InterceptorController

六、技术栈分层架构

HTTP请求
Servlet容器层
Servlet过滤器
Spring Web层
MVC拦截器
Controller代理
Spring AOP层
业务拦截器
原始Bean方法

总结

  1. 调用机制本质

    • 过滤器:显式递归调用(必须调用 chain.doFilter()
    • MVC拦截器:隐式遍历调用(框架自动控制)
    • AOP拦截器:链式递归调用(通过 proceed() 传递)
  2. 与AOP节点的关系

    • MVC拦截器 不是 AOP链的一部分
    • Controller方法的执行会进入AOP代理链
    • MVC拦截器在AOP代理链 之前 执行
  3. 自定义组件加入

    • 过滤器:通过 FilterRegistrationBean 加入Servlet容器调用链
    • MVC拦截器:通过 WebMvcConfigurer 加入Spring MVC遍历列表
    • AOP拦截器:通过 @Aspect 加入Spring AOP代理链

理解这些差异对于构建高效、可维护的Web应用至关重要:

  • 使用过滤器处理底层HTTP问题(编码/安全)
  • 使用MVC拦截器处理请求生命周期(认证/日志)
  • 使用AOP拦截器处理业务逻辑(事务/性能监控)

这种分层设计让每个组件都能在最适合的层级发挥作用,既保持职责分离,又能协同完成复杂请求处理!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918926.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qml 实现数值键盘

import QtQuick 2.0import QtQuick.Layouts 1.12 import"../pad" // PasswordKeyboard.qml import QtQuick 2.12ColumnLayout {id: keyboardspacing: 8// 键盘标题Text {text: "安全输入"font.pixelSize: 16color: "#666"Layout.alignment: Qt.A…

PID控制算法

文章目录引言一、基本原理1.1.简介1.2.开环与闭环1.3.PID 的公式1.3.1.比例项&#xff08;Proportional&#xff09;1.3.2.积分项&#xff08;Integral&#xff09;1.3.3.微分项&#xff08;Differential&#xff09;1.4.连续形式与离散形式的 PID 公式1.4.1.连续形式1.4.2.离散…

MyBatis 动态数据源切换在 Spring Boot 环境下的实现方案

第一章 需求背景与技术选型1.1 多数据源场景概述在大型企业级应用中&#xff0c;单一数据库往往无法满足高并发和多业务线的需求&#xff0c;因此需要引入 多数据源 的架构设计。常见的多数据源场景包括&#xff1a;读写分离、多租户、分库分表以及数据源负载均衡等。读写分离&…

PCA降维理论详解

文章目录一、什么是PCA&#xff1f;二、为什么需要降维&#xff1f;三、PCA的数学原理与详细推导视角一&#xff1a;最大化投影方差&#xff08;Maximizing Variance&#xff09;视角二&#xff1a;最小化重构误差&#xff08;Minimizing Reconstruction Error&#xff09;四、…

Android RxJava变换操作符详解

RxJava作为响应式编程在Android开发中的利器&#xff0c;其强大的变换操作符能够帮助我们优雅地处理数据流。本文将深入讲解RxJava中最常用的变换操作符及其实际应用场景。一、RxJava变换操作符概述变换操作符(Transformation Operators)用于对Observable发射的数据序列进行变换…

开源数据发现平台:Amundsen 快速上手指南

Amundsen 是一个数据发现和元数据引擎&#xff0c;旨在提高数据分析师、数据科学家和工程师与数据交互时的生产力。目前&#xff0c;它通过索引数据资源&#xff08;表格、仪表板、数据流等&#xff09;并基于使用模式&#xff08;例如&#xff0c;查询频率高的表格会优先于查询…

【密码学实战】国密SM2算法介绍及加解密/签名代码实现示例

引言 在信息安全领域&#xff0c;密码算法是数据保护的核心基石。2010 年&#xff0c;中国国家密码管理局发布了 SM2 椭圆曲线公钥密码算法&#xff0c;作为国产密码标准的核心成员&#xff0c;它凭借高效安全的特性&#xff0c;逐步替代 RSA 等国际算法&#xff0c;广泛应用于…

QT开发中如何加载第三方dll文件

文章目录&#x1f527; 一、隐式加载&#xff08;静态链接&#xff09;操作步骤&#xff1a;⚙️ 二、显式加载&#xff08;动态链接&#xff0c;推荐使用QLibrary&#xff09;操作步骤&#xff1a;&#x1f4bb; 三、直接调用Windows API&#xff08;仅Windows&#xff09;⚠️…

后端学习资料 持续更新中

数据库&#xff1a; 该网址包含&#xff1a;图解MySql&#xff0c; 看明白谁也问不倒你~ 图解计算机网络、操作系统、计算机组成、MySQL、Redis&#xff0c;让天下没有难懂的八股文&#xff01;https://xiaolincoding.com/

《嵌入式Linux应用编程(六):并发编程基础:多进程exec函数族及多线程基础》

一、exec函数族在一个进程里面执行另一个文件本质&#xff1a;将文本区的指令代码替换成exec要执行的指令#include <unistd.h>参数&#xff1a;path:要执行的可执行文件的路径和名称arg:执行该可执行文件时需要传递的参数NULL&#xff1a;参数传递结束标志 返回值&#x…

【121页PPT】智慧方案智慧综合体智能化设计方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808859/91654007 资料解读&#xff1a;【121页PPT】智慧方案智慧综合体智能化设计方案 详细资料请看本解读文章的最后内容 一、项目概述与智能化总…

Linux网络基础(一)

目录 计算机网络背景 网络发展 初识 "协议" 网络协议初识 协议分层 软件分层的好处 打电话例子 OSI七层模型 TCP/IP五层(或四层)模型 参考资料 再识协议 为什么要有 TCP/IP 协议&#xff1f; 什么是 TCP/IP 协议&#xff1f; TCP/IP 协议与操作系统的关系(宏观上&…

MySQL多表查询案例

多表查询本文介绍了多表查询中的表关系概念和操作方法。主要内容包括&#xff1a;1.三种表关系类型&#xff08;一对多、多对多、一对一&#xff09;及其实现方式&#xff1b;2.多表查询的四种连接方式&#xff08;内连接、左外连接、右外连接、自连接&#xff09;及语法&#…

Dify 从入门到精通(第 36/100 篇):Dify 的插件生态扩展

Dify 从入门到精通&#xff08;第 36/100 篇&#xff09;&#xff1a;Dify 的插件生态扩展 Dify 入门到精通系列文章目录 第一篇《Dify 究竟是什么&#xff1f;真能开启低代码 AI 应用开发的未来&#xff1f;》介绍了 Dify 的定位与优势第二篇《Dify 的核心组件&#xff1a;从…

【已解决】在Spring Boot工程中,若未识别到resources/db文件夹下的SQL文件

在Spring Boot工程中&#xff0c;若未识别到resources/db文件夹下的SQL文件&#xff0c;通常与资源路径配置、构建工具设置或代码加载方式有关。以下是逐步排查和解决方案&#xff1a;​​1. 确认SQL文件存放路径​​Spring Boot默认从类路径&#xff08;classpath:&#xff09…

【Java】网络编程(4)

1. 再谈 UDP 报文长度&#xff1a;也是 2 个字节&#xff0c; 0 - 65535&#xff0c;也就是 64 kb。这表示一个 UDP 数据包一次最多只能传输 64 kb 的数据校验和&#xff1a;验证数据是否在传输过程中发生修改。数据在传输过程中可能受到信号干扰&#xff0c;发生 “比特翻转”…

QT(事件)

一、事件前言事件是QT的三大机制之一&#xff0c;一定程度上信号和槽也属于事件的一种 QT中的事件指哪些&#xff1a;窗口关闭&#xff0c;窗口显示&#xff0c;敲击键盘&#xff0c;点击鼠标左键、鼠标右键、鼠标滚轮&#xff0c;文件拖放等等1、事件循环QT中的所有事件&#…

基于 Vue2+Quill 的富文本编辑器全方案:功能实现与样式优化

在 Web 开发中&#xff0c;富文本编辑器是内容管理系统、博客平台等应用的核心组件。本文将详细介绍如何基于 Vue 和 Quill 构建一个功能完善、样式精美的富文本编辑器&#xff0c;重点解决字体字号选项冗长、样式不美观及功能完整性问题&#xff0c;提供可直接部署使用的完整方…

C#内嵌字符串格式化输出

内嵌字符串格式输出 double speedOfLight 299792.458;System.Globalization.CultureInfo.CurrentCulture System.Globalization.CultureInfo.GetCultureInfo("nl-NL"); string messageInCurrentCulture $"The speed of light is {speedOfLight:N3} km/s.&quo…

ThreeJS程序化生成城市大场景底座(性能测试)

一、简介基于矢量geojson数据构建建筑、植被、道路等&#xff0c;实现城市场景底座。涉及渲染的性能优化无非就是众所周知的那些事儿。视锥剔除、mesh合并、减少draw call、四叉树、八叉树、数据压缩、WebWorker、着色器优化等。下面是对东莞市数十万建筑以及海量3D树的渲染测试…