SpringMVC系列-3 拦截器

news2025/1/9 1:33:55

背景

本文作为 SpringMVC系列 的第三篇,以SpringMVC系列-2 HTTP请求调用链为基础,介绍Spring MVC的拦截器。

1.拦截器

SpringMVC的核心实现是DispatcherServlet,本质是一个Servlet实现类,拦截器位于DispatcherServlet逻辑中;Filter是Servlet规范,且过滤器和拦截器发挥作用的时机不同,需要注意不要将二者混淆。一个HTTP请求的执行路径可以表示为:
在这里插入图片描述
请求依次经过各Filter,然后进入DispatcherServlet。在DispatcherServlet中先经过拦截器逻辑再调用Controller目标方法,即拦截器逻辑发在请求被服务器真实处理前后,因此常用于进行操作日志记录、鉴权等。

2.使用方式

2.1自定义HandlerInterceptor接口的实现类:

@Slf4j
public class MyHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LOGGER.info("-------[Interceptor] preHandle-------");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LOGGER.info("-------[Interceptor] postHandle-------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LOGGER.info("-------[Interceptor] afterCompletion-------");
    }
}

HandlerInterceptor中的声明的preHandle/postHandle/afterCompletion为default类型的接口,可以随意选择是否实现。

preHandle接口若返回false, 会跳过Controller方法的执行,流程可参考SpringMVC系列-2 HTTP请求调用链

2.2 通过WebMvcConfigurer将拦截器注册到框架中:

注册拦截器的时候,可以选择将其注册为HandlerInterceptor或MappedInterceptor,二者注册方式和生效场景略有不同。

HandlerInterceptor对所有请求生效,MappedInterceptor对URL满足匹配要求的请求生效。

注册为HandlerInterceptor

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyHandlerInterceptor());
    }
}

此时HandlerInterceptor对所有的HTTP请求生效。

注册为MappedInterceptor

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyHandlerInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**");
    }
}

在注册拦截器时,如果通过addPathPatterns添加白名单或者excludePathPatterns添加黑名单时,框架会自动将该拦截器包装成MappedInterceptor。
匹配过程涉及三个变量:匹配器、路径黑名单、路径白名单;
【1】匹配器
可通过registry.pathMatcher(new MyAntPathMatcher());方法注册匹配器。
一般不会直接自定义匹配器,而是使用既有的匹配器,按照其匹配规则配置UR L的黑/白名单(如上述案例所示)。
框架默认的配置器AntPathMatcher,该匹配器实现的匹配规则如下:

?匹配一个字符
* 匹配0个或多个字符
** 匹配0个或多个目录

如:"/**"表示匹配所有的Http请求路径,"/abc/api/**"表示匹配以/abc/api/开头的URL。
【2】路径黑名单、白名单

public boolean matches(String lookupPath, PathMatcher pathMatcher) {
	PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
	if (!ObjectUtils.isEmpty(this.excludePatterns)) {
		for (String pattern : this.excludePatterns) {
			if (pathMatcherToUse.match(pattern, lookupPath)) {
				return false;
			}
		}
	}
	if (ObjectUtils.isEmpty(this.includePatterns)) {
		return true;
	}
	for (String pattern : this.includePatterns) {
		if (pathMatcherToUse.match(pattern, lookupPath)) {
			return true;
		}
	}
	return false;
}

优先匹配黑名单:
step1: 如果路径被任一黑名单匹配,则返回false—表示匹配失败;
step2: 如果匹配任一白名单(或白名单为空),则返回true—表示匹配成功;
step3: 如果所有白名单均不匹配,则返回false—表示失败。

这种黑白名单规则一般也适用于业务场景,需求设计时也可参考。

另外,对于MappedInterceptor类型的拦截器除了使用WebMvcConfigurer方式将其注册到框架外,还可通过向IOC容器中注册Bean对象的方式实现,如下所示:

@Configuration
public class MyInterceptorConfig {
    @Bean
    public MappedInterceptor myMappedInterceptor() {
        MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[]{"/**"}, new String[]{"/static/**"}, new MyHandlerInterceptor());
        mappedInterceptor.setPathMatcher(new AntPathMatcher());
        return mappedInterceptor;
    }
}

2.3 Controller测试案例:

@Slf4j
@RestController
@RequestMapping("/api/info")
public class MyController {
    @GetMapping("/name")
    public String getName() {
        LOGGER.info("[Controller] getName has called.");
        return "success";
    }
}

2.4 执行结果

"/api/info/name"被调用时,程序日志如下:
在这里插入图片描述

3.实现原理

本章节以SpringBoot+SpringMVC为框架背景,结合SpringMVC框架源码介绍项目启动过程拦截器的注册和Controller被调用过程拦截器的收集和执行原理。

3.1 启动过程

SpringBoot自动装配机制在启动时向容器注册WebMvcAutoConfiguration这个配置类, 其内部引入的EnableWebMvcConfiguration配置类中(以及其父类WebMvcConfigurationSupport)使用@Bean注解方式向IOC容器中注册了RequestMappingHandlerMapping、ViewControllerHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping、ResourceHandlerMapping、WelcomePageHandlerMapping这些Bean对象, 这些Bean对象注册到IOC容器前,调用getInterceptors方法获取拦截器数组并对其进行了属性设置。
获取拦截器的逻辑如下:

protected final Object[] getInterceptors(FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
	if (this.interceptors == null) {
		InterceptorRegistry registry = new InterceptorRegistry();
		addInterceptors(registry);
		registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
		registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
		this.interceptors = registry.getInterceptors();
	}
	return this.interceptors.toArray();
}

其中用户自定义的拦截器通过addInterceptors(registry);逻辑获取:

protected void addInterceptors(InterceptorRegistry registry) {
	this.configurers.addInterceptors(registry);
}

public void addInterceptors(InterceptorRegistry registry) {
	for (WebMvcConfigurer delegate : this.delegates) {
		delegate.addInterceptors(registry);
	}
}

其中,delegate包含了IOC容器中WebMvcConfigurer类型的对象,即包含了章节2中的MyInterceptorConfig。

EnableWebMvcConfiguration(DelegatingWebMvcConfiguration的子类)对象在属性设置阶段会获取IOC中所有WebMvcConfigurer类型的Bean对象作为如参,并调用setConfigurers方法,:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}
}

根据调用链:

public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
	if (!CollectionUtils.isEmpty(configurers)) {
		this.delegates.addAll(configurers);
	}
}

因此,delegate包含了IOC容器中WebMvcConfigurer类型的对象。
当将各类型(包括RequestMappingHandlerMapping)的HandlerMapping注册到IOC容器时,其interceptors属性已包含了拦截器对象。

3.2 收集拦截器

如SpringMVC系列-2 HTTP请求调用链中所述,当调栈进入doDispatch方法时,会经历收集拦截器、调用拦截器preHandle方法、调用目标方法、调用拦截器postHandle方法、调用拦截器的afterCompletion方法。
其中收集拦截器的逻辑如下:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

拦截器数组来源于this.adaptedInterceptors属性,如果是MappedInterceptor类型,则判断请求的URL是否匹配,如果匹配则将拦截器加入到调用链中;如果是HandlerInterceptor类型,则直接加入到调用链中。
this.adaptedInterceptors属性来源于章节 3.1 中的interceptors属性,以及IOC容器中MappedInterceptor类型的Bean对象:

protected void initApplicationContext() throws BeansException {
	extendInterceptors(this.interceptors);
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}

其中:detectMappedInterceptors方法逻辑如下

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(
			BeanFactoryUtils.beansOfTypeIncludingAncestors(
					obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}

从IOC容器中获取所有MappedInterceptor类型的Bean对象。

initInterceptors方法将interceptors属性集合包含的拦截器添加到this.adaptedInterceptors属性中:

protected void initInterceptors() {
	if (!this.interceptors.isEmpty()) {
		for (int i = 0; i < this.interceptors.size(); i++) {
			Object interceptor = this.interceptors.get(i);
			if (interceptor == null) {
				throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
			}
			this.adaptedInterceptors.add(adaptInterceptor(interceptor));
		}
	}
}

3.3 调用拦截器

参考SpringMVC系列-2 HTTP请求调用链文章。

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

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

相关文章

MySQL进阶SQL语句2之表连接

目录 1.连接查询 1.1inner&#xff08;内连接&#xff09; 1.2left join&#xff08;左连接&#xff09; 1.3right join&#xff08;右连接&#xff09; 1.4直接查询两个表相同的字段值的数据 2. VIEW&#xff08;视图&#xff09; 2.1create view&#xff08;创建视图…

设计模式之迭代器模式笔记

设计模式之迭代器模式笔记 说明Iterator(迭代器)目录迭代器模式示例类图学生类抽象迭代器角色接口具体迭代器角色类抽象聚合角色接口具体聚合角色类测试类 说明 记录下学习设计模式-迭代器模式的写法。JDK使用版本为1.8版本。 Iterator(迭代器) 意图:提供一种方法顺序访问一…

Python2、3下载安装、环境配置和Python2、3版本共存配置

一、python 版本简介 python 包括 python2、python3 两个大版本&#xff0c;其中 python3 改进了 python2 的一些不足&#xff0c;但由于以前很多应用是用 python2 开发的&#xff0c;维护这些应用还需用到 python2&#xff0c;故 python2 尚未被完全淘汰。 北京时间 2020 年 4…

近期参与开源的心得体会

引言 最近随着Kepler项目加入CNCF sandbox&#xff0c;写一篇blog来记录下参与这个项目半年的发展的心得体会。 运营 项目的运营最好还是专注于项目自身的发展&#xff0c;围绕项目的特点&#xff0c;创新点入手&#xff0c;为大家提供价值&#xff0c;从而自然而然的扩大自…

【计算机网络】计算机网络期末自测题(一)答案

2019-2020 学年第 2 学期自测题答案及评分标准 (卷 1) 计算机网络 一、 填空题&#xff1a; 参考答案&#xff1a; 1 、 01000101 、11100111 3 、 100Mbps、双绞线、基带、全双工 [10Mbps 要求单位] 4 、 报文 5 、 ICMP 6 、 虚电路 7 、 距离矢量、链路状态 …

什么是网络安全?

文章目录 一、概述1.1 网络安全的指标1.2 网络安全的特征 二、网络安全威胁2.1 黑客能破坏的2.2 Internet安全手段2.2.1 端口扫描2.2.2 分组嗅探sniffing2.2.3 IP欺骗Spoofing 2.3 Internet安全威胁2.3.1 DOS拒绝服务 三、密码学3.1 对称加密算法3.1.1 传统加密3.1.2 现代加密技…

Redis(七):Redis基础入门

Redis基础入门 Redis用途Redis优缺点docker运行RedisRedis常用命令String命令Hash命令List命令Set命令ZSet命令全局命令 Redis事务Redis持久化机制RDBAOFRDBAOF&#xff08;默认&#xff09; Redis内存淘汰机制Redis对过期Key的处理 Redis用途 Redis是一种开源的NoSQL内存数据库…

【MySql】多版本并发控制MVCC前置知识——隐藏字段、undo日志与Read View

文章目录 3个记录隐藏列字段undo日志模拟 MVCCRead View 数据库并发的场景有三种&#xff1a; 读-读 &#xff1a;不存在任何问题&#xff0c;也不需要并发控制 读-写 &#xff1a;有线程安全问题&#xff0c;可能会造成事务隔离性问题&#xff0c;可能遇到脏读&#xff0c;幻读…

UOS系统下搭建qtcreator编译环境

文章目录 前言一、依赖包说明二、No valid kits found 问题现象三、No valid kits found 问题解决1.查找qt安装路径2.设置Qt Versions3.构建套件&#xff08;kit&#xff09;下选择Qt版本4.重新添加工程 前言 本文记录了在UOS系统下如何安装qtcreator以及涉及的依赖包安装&…

冷静期or跌落神坛:净水市场纠结,“易开得”们路在何方?

文丨琥珀消研社 作者丨余二 1986年11月1日&#xff0c;一场火灾拉开了世界三大水污染——莱茵河水污染的序幕。 是夜&#xff0c;位于瑞士巴塞尔市的桑多兹化学公司的一个化学品仓库发生火灾&#xff0c;装有约1250吨剧毒农药的钢罐爆炸&#xff0c;大火持续了4个多小时&…

SpringBoot 线上服务假死,CPU 内存正常,什么情况?

背景 开发小伙伴都知道线上服务挂掉&#xff0c;基本都是因为cpu或者内存不足&#xff0c;出现GC频繁OOM之类的情况。本篇文章区别以上的情况给小伙伴们带来不一样的服务挂掉。 还记得哔哩哔哩713事故中那场诡计多端的0吗&#xff1f; 图片 对就是这个0&#xff0c;和本次事…

团体程序设计天梯赛-练习集L2篇③

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

【golang中的变量 全局/局部/4中声明】

目录 变量变量的分析1.变量的创建的四种形式1.1总结1.2第一种 var a int 声明1.3 第二种 var a string "XXXX" 初始化1.4第三种 var a "XXXX"1.5第四种 a : XXXX 2.一次性声明多个变量3.一次初始化多个变量3.1交换值 4.全局变量--局部变量5. 声明和初始化…

Kafka生产调优源码

一、Kafka硬件配置选择 1.1 场景说明 100 万日活&#xff0c;每人每天 100 条日志&#xff0c;每天总共的日志条数是 100 万 * 100 条 1 亿条。 1 亿/24 小时/60 分/60 秒 1150 条/每秒钟。 每条日志大小&#xff1a;0.5k - 2k&#xff08;取 1k&#xff09;。 1150 条/…

算法------排序算法------冒泡排序法

介绍 冒泡排序法又称交换排序法&#xff0c;原理是从第一个元素开始&#xff0c;比较相邻元素的大小&#xff0c;如大小顺序有误&#xff0c;则对调后再进行下一个元素的比较&#xff0c;一次扫描之后可以确保最后一个元素位于正确的位置。接下来进行的第二次扫描&#xff0c;…

SSMP整合案例(5) Spring Boot整合MyBatis-Plus实现条件查询

讲完条件查询 那么 我们整个数据层的代码就写完了 可以看到 我们之前的代码 查询语句都有一个 参数 QueryWrapper 这个就是查询条件 其实 我们可以直接这样写 QueryWrapper<book> Query new QueryWrapper<>(); bookDao.selectList(Query);QueryWrapper类需要手…

Spring加载后初始化的9种方式

本文来聊一下在spring中&#xff0c;当spring 容器启动后&#xff0c;我们有几种初始化操作的方式。 目录 Spring加载后初始化的几种方式 Component和Service加构造方法 ContextRefreshedEvent事件 代码如下&#xff1a; 输出结果&#xff1a; PostConstruct 注解 代码如…

基于骨骼关键点的动作识别(OpenMMlab学习笔记,附PYSKL相关代码演示)

一、骨骼动作识别 骨骼动作识别是视频理解领域的一项任务 1.1 视频数据的多种模态 RGB&#xff1a;使用最广&#xff0c;包含信息最多&#xff0c;从RGB可以得到Flow、Skeleton。但是处理需要较大的计算量 Flow&#xff1a;光流&#xff0c;主要包含运动信息&#xff0c;处理…

面向对象分析与设计 UML2.0 学习笔记

一、认识UML UML-Unified Modeling Language 统一建模语言&#xff0c;又称标准建模语言。是用来对软件密集系统进行可视化建模的一种语言。UML的定义包括UML语义和UML表示法两个元素。 UML是在开发阶段&#xff0c;说明、可视化、构建和书写一个面向对象软件密集系统的制品的…