Spring之拦截器(HandlerInterceptor)

news2024/9/27 12:06:07

前言

在web开发中,拦截器是经常用到的功能,用于拦截请求进行预处理和后处理,一般用于以下场景:

  1. 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等等。

  2. 权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。

  3. 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

  4. 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

过滤器跟拦截器的区别

在说拦截器之前,不得不说一下过滤器,有时候往往被这两个词搞的头大。

其实我们最先接触的就是过滤器,还记得web.xml中配置的或者继承Filter接口的类吗~

你应该知道spring mvc的拦截器是只拦截controller而不拦截jsp,html 页面文件的,如果想要拦截那怎么办?

这就用到过滤器filter了,filter是在servlet前执行的,你也可以理解成过滤器中包含拦截器,一个请求过来 ,先进行过滤器处理,看程序是否受理该请求 。 过滤器放过后,程序中的拦截器进行处理 。

(1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)

(2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)。

HandlerInterceptor简介

HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。

拦截器是相对于Spring中来说的,它和过滤器不一样,过滤器的范围更广一些是相对于Tomcat容器来说的。拦截器可以对用户进行拦截过滤处理。

但是并不是说拦截器只对请求进入Controller控制器之前起作用,它也分为3个部分:

  • 请求进入Controller之前,通过拦截器执行代码逻辑
  • Controller执行之后(只是Controller执行完毕,视图还没有开始渲染),通过拦截器执行代码逻辑
  • Controller完全执行完毕(整个请求全部结束),通过拦截器执行代码逻辑,可用于清理资源等。

HandlerInterceptor和WebMvcConfigurer

想要自己配置一个拦截器,就必须用到HandlerInterceptorWebMvcConfigurer这两个接口。

HandlerInterceptor

作用:自定义拦截器

如何创建:自定义一个类实现HandlerInterceptor接口**并将它注入到Spring容器中。加上@Component注解或者在含有@Configuration注解的类中使用@Bean注入。

HandlerInterceptor源码如下:

package org.springframework.web.servlet;

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

这三个方法都是干什么的,有什么作用,什么时候调用,不同的拦截器之间是怎样的调用顺序呢?先补一张图:
在这里插入图片描述

  • preHandle:此方法的作用是在请求进入到Controller进行拦截,有返回值。(返回true则将请求放行进入Controller控制层,false则请求结束返回错误信息)。可用于登录验证(判断用户是否登录);权限验证:判断用户是否有权访问资源(校验token)等。

  • postHandle:该方法是在Controller控制器执行完成但是还没有返回模板进行渲染拦截。没有返回值。就是Controller----->拦截------>ModelAndView。因此我们可以将Controller层返回来的参数进行一些修改,它就包含在ModelAndView中,所以该方法多了一个ModelAndView参数。

  • afterCompletion:该方法是在ModelAndView返回给前端渲染后执行。例如登录的时候,我们经常把用户信息放到ThreadLocal中,为了防止内存泄漏,就需要将其remove掉,该操作就是在这里执行的。

DispatcherServlet的doDispatch方法封装了springMVC处理请求的整个过程。首先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器;然后在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。关于拦截器的处理到此为止,接下来看看triggerAfterCompletion做了什么。

triggerAfterCompletion做的事情就是从当前的拦截器开始逆向调用每个拦截器的afterCompletion方法,并且捕获它的异常,也就是说每个拦截器的afterCompletion方法都会调用。

根据以上的代码,分析一下不同拦截器及其方法的执行顺序。假设有5个拦截器编号分别为12345,若一切正常则方法的执行顺序是12345的preHandle,54321的postHandle,54321的afterCompletion。若编号3的拦截器的preHandle方法返回false或者抛出了异常,接下来会执行的是21的afterCompletion方法。这里要注意的地方是,我们在写一个拦截器的时候要谨慎的处理preHandle中的异常,因为这里一旦有异常抛出就不会再受到这个拦截器的控制。12345的preHandle的方法执行过之后,若handler出现了异常或者某个拦截器的postHandle方法出现了异常,则接下来都会执行54321的afterCompletion方法,因为只要12345的preHandle方法执行完,当前拦截器的拦截器就会记录成编号5的拦截器,而afterCompletion总是从当前的拦截器逆向的向前执行。

WebMvcConfigurer

作用:添加拦截规则

如何创建:自定义一个类,实现WebMvcConfigurer并将它注入到Spring容器中。根据需求实现里面方法。

WebMvcConfigurer源码如下**:**

package org.springframework.web.servlet.config.annotation;

public interface WebMvcConfigurer {
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }

    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }

    default void addFormatters(FormatterRegistry registry) {
    }

    default void addInterceptors(InterceptorRegistry registry) {
    }

    default void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    default void addCorsMappings(CorsRegistry registry) {
    }

    default void addViewControllers(ViewControllerRegistry registry) {
    }

    default void configureViewResolvers(ViewResolverRegistry registry) {
    }

    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    }

    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    }

    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

根据源码发现,这里面有很多方法,但是通常我们只用到了里面的两种方法如下:

addInterceptors:添加拦截器,拦截器需要拦截的路径和需要排除拦截的路径都需要在其中配置

addResourceHandlers:配置静态资源路径,即某些请求需要读取某个路径下的静态资源内容,需要配置该静态资源的路径,通过该方法可以统一给这些请求配置指定静态资源路径

拦截器实现流程

根据第二部分,我们知道了配置拦截器的准备工作。

1)自定义类实现HandlerInterceptor

下例是通过MDC给request中设置traceId。

@Component
public class TraceIdInterceptor implements HandlerInterceptor {
 
  	private static final String MDC_TRACE_ID_KEY = "traceId";
  
  	private static final String HEADER_TRACE_ID = "X-Trace-Id";
  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
      throws Exception {
      // MDC中已经有traceId了,那就不再重复设置
       if (StringUtils.hasText(MDC.get(MDC_TRACE_ID_KEY))) { 
           return true;
       }

      // 如果外面过来的请求头中包含traceId,则直接使用它的
      String traceId = request.getHeader(HEADER_TRACE_ID);
      if (StringUtils.hasText(traceId)) {
          MDC.put(MDC_TRACE_ID_KEY, traceId); 
          return true;
      }

      // 如果外面过来的请求中没有requestId,自己生成一个
      MDC.put(MDC_TRACE_ID_KEY, generateTraceId()); 
      return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
      @Nullable ModelAndView modelAndView) {
        // 请求处理结束后,删掉traceId
        MDC.remove(MDC_TRACE_ID_KEY);
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, 				Exception ex) throws Exception {
        System.out.println("进入拦截器=======执行后,暂不增加业务逻辑处理========");
    }
}

	// 生成traceId
  private String generateTraceId() {
      String uuid = UUID.randomUUID().toString().replace("-", "");
      long time = System.currentTimeMillis();
      return uuid + "." + time;
  }

2)自定义类实现WebMvcConfigurer

/**
 * 此处也可以继承WebMvcConfigurationSupport
 */
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
 
    @Resource
    private TraceIdInterceptor traceIdInterceptor;
 
    /**
     * 添加拦截规则, registry可以添加多个拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
 
        List<String> patterns = new ArrayList<>();
 
        patterns.add("/login");
 
        registry.addInterceptor(traceIdInterceptor)
                .addPathPatterns("/**") //所有的请求都要拦截。
                .excludePathPatterns(patterns); //将不需要拦截的接口请求排除在外
    }
 
    /**
     * 下面代码意思是:配置一个拦截器,如果访问路径是 addResourceHandler 中的这个路径(这里/**表示所有的路径),
     * 那么就映射到访问本地的addResourceLocations这个路径上,这样就可以看到该路径上的资源了
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        
        registry.addResourceHandler("/**") //配置需要添加静态资源请求的url
                .addResourceLocations("classpath:/resources/static/"); //配置静态资源路径
    }
}

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

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

相关文章

C++ 继承(二)

目录 1. 实现一个不能被继承的类 2. 友元与继承 3.继承与静态成员 4.多继承及其菱形继承问题 (1). 继承模型 (2). 虚继承 (2.1)虚继承解决数据冗余和二义性的原理 (3). 多继承中指针偏移问题 (4). IO库中的菱形虚拟继承 5. 继承和组合 1. 实现一个不能被继承的类 方法1…

内蒙古众壹集团:引领蒙东财税服务行业,成就企业发展新高度

内蒙古众壹企业管理集团有限公司自2019年成立以来&#xff0c;凭借卓越的服务和专业的团队&#xff0c;迅速成长为蒙东地区财税服务行业的先锋企业。 公司在成立初期&#xff0c;通过加盟慧算账平台&#xff0c;快速进入市场&#xff0c;并设立了多个分公司&#xff0c;逐步扩展…

Daily2:字体描边

有一个小的需求,需要对字体进行描边,一开始理解错了需求,以为要对字体镂空处理,然后尝试了许多做错了许多 后来发现是一个简单的描边处理,直接chatgpt就可以得出来一个简单的实现代码, class BorderTextView JvmOverloads constructor(context: Context, attrs: AttributeSet?…

读懂以太坊源码(3)-详细解析genesis.json

要想搞懂以太坊的源代码逻辑&#xff0c;必须要了解以太坊创世区块配置文件(genesis.json)的结构&#xff0c;以及每个配置参数的意义&#xff0c;创世配置文件&#xff0c;主要作用是设置链的ID&#xff0c;指定以太坊网络中硬分叉发生的区块高度&#xff0c;以及初始ETH数量的…

【系统分析师】-软件测试

目录 1、测试的类型 1.1、动态测试 1.1.1、黑盒法 1.1.2、白盒法 1.1.3、灰盒法 1.2、静态测试 2、测试阶段 2.1、单元测试 2.2、集成测试 2.3、确认测试 2.4、系统测试 3、性能测试 3.1、性能测试的目的 3.2、性能测试的类型 3.3、性能测试的步骤 5、测试设计…

【操作系统存储篇】Linux文件基本操作

目录 一、Linux目录 二、Linux文件的常用操作 三、Linux文件类型 一、Linux目录 Linux有很多目录&#xff0c;Linux一切皆是文件&#xff0c;包括进程、设备等。 相对路径&#xff1a;相对于当前的操作目录&#xff0c;文件位于哪个目录。 绝对路径 &#xff1a;从根目录开…

面对AI时代快车,你没必要跟车赛跑,而是应该先去考个驾照!

在当今人工智能领域&#xff0c;代码生成和编辑工具成为了开发者们手中的利器。昨天&#xff0c;零一万物公司再次展示了他们在开源社区中的领导地位&#xff0c;开源了 Yi-Coder 系列编程助手模型&#xff0c;这一举措不仅标志着该公司继今年5月开源Yi-1.5系列模型后的又一重大…

智能提醒助理系列-服务号静默登录

本系列文章记录“智能提醒助理”wx公众号 建设历程&#xff0c;记录实践经验、巩固知识点、锻炼总结能力。 本文介绍&#xff0c;如何让用户进入公众号之后就锁定用户&#xff0c;使用既注册&#xff0c;进入既可使用功能&#xff0c;去掉繁琐的登录认证流程。 一、需求出发点 …

告别单调,Xmind思维导图之后还有这三款神器,让学习工作更愉快

这年头信息量爆炸&#xff0c;我们得想办法把事情想清楚、把活儿排排好、学点新玩意儿。思维导图这东西&#xff0c;因为它画出来一目了然&#xff0c;用起来也简单&#xff0c;所以特别受学生们和上班的人的欢迎。在这么多画思维导图的软件里&#xff0c;Xmind因为功能全、界面…

02【SQL sever 2005数据库安装教程】

一、安装须知 1.安装数据库版本&#xff1a;SQL sever 2005 2.适用系统&#xff08;目前发现&#xff09;&#xff1a;Windows server 2008 R2 3.安装程序目录&#xff1a;SQL2005\SQL Server x64\Servers\setup.exe 二、安装步骤 1&#xff0e;双击setup.exe&#xff0c;以…

c++编程(25)——unordered_map模拟实现

欢迎来到博主的专栏&#xff1a;c编程 博主ID&#xff1a;代码小豪 文章目录 unorder_map的底层insert迭代器成员访问函数operatoroperator-\- unordered_map是STL中的关联式容器之一&#xff0c;与常规的map有两点不同 &#xff08;1&#xff09;unordered是无序的意思&#x…

AI与我同创诗:尝试让ai(智谱清言)参与我的诗创活动

ai伴学越久&#xff0c;契合度愈高&#xff0c;“泛滥”之诗情&#xff0c;幸得学伴共雕琢。让ai伴学久了&#xff0c;不知觉的&#xff0c;写诗也让ai帮衬了。此文收录“我共ai”的自创文稿&#xff0c;亦可作“ai诗集”。&#x1f60b; (笔记模板由python脚本于2024年09月03日…

企业微信中嵌套的h5应用调用微信扫码功能

企业微信官方文档 1.登录企业微信后台,管理员可操作,打开应用配置应用可信域名(必须配置,否则无法调用jsapi,可信域名必须有ICP备案且在管理端验证域名归属) 配置部署后的前台域名地址 配置可信域名,部署后的服务器域名(需备案认证) 当域名权限不够时需下载文件效验,将文件放…

网站开发:HTML + CSS - CSS选择器

1. 前言 CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是一种用于控制 HTML 文档样式和布局的语言。它为 Web 页面提供了许多功能&#xff0c;使开发者能够创建美观且功能丰富的用户界面。 提供了丰富的功能来控制网页的外观和布局&#xff0c;增…

掌握SQLAlchemy:Python数据库的魔法师

文章目录 掌握SQLAlchemy&#xff1a;Python数据库的魔法师背景&#xff1a;为什么选择SQLAlchemy&#xff1f;SQLAlchemy是什么&#xff1f;如何安装SQLAlchemy&#xff1f;五个简单的库函数使用方法1. 创建引擎2. 定义模型3. 创建会话4. 添加数据5. 查询数据 场景应用1. 多表…

大模型构建合作性的Agent,多代理框架MetaGpt

大模型构建合作性的Agent,多代理框架MetaGpt 前言 MetaGPT 框架将标准的操作程序(SOP)与基于大模型的多智能体相结合,使用标准操作程序来编码提示,确保协调结构化和模块化输出。 MetaGPT 允许 Agent 在类似流水线的范式中扮演多中角色,通过结构化的 Agent 协作和强化领…

Android Studio 加载多个FLutter项目

按顺序操作即可 选择工程 选择Modules, 导入 module 选中创建module 选择要导入的目录&#xff0c;只选择主目录&#xff0c;下面的文件不要选 添加完成&#xff0c;点击ok后&#xff0c;会进行导入 最终导入成功

三星称霸全球市场,但它在中国市场再受打击,将进一步收缩业务

韩国媒体报道指三星已计划进一步收缩中国业务&#xff0c;将中国的销售和生产部门人员减少一部分&#xff0c;其中销售人员计划减少8%左右&#xff0c;显示出它在中国市场继续面临打击&#xff0c;对于这家在全球市场领先地位仍然稳固的企业来说&#xff0c;它在中国市场无疑又…

OpenCV结构分析与形状描述符(8)点集凸包计算函数convexHull()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 查找一个点集的凸包。 函数 cv::convexHull 使用斯克拉斯基算法&#xff08;Sklansky’s algorithm&#xff09;来查找一个二维点集的凸包&#…

Wyn 商业智能V8.0 新版本来袭,解锁“智造”的无限可能

Wyn商业智能V8.0 版本全新发布&#xff0c;聚焦制造业数字化升级痛点&#xff0c;深度赋能制造业数字化转型升级之路&#xff0c;从无缝集成物联网海量数据&#xff0c;到构建可视化实时分析、监控与预警大屏&#xff0c;全面打通生产制造全生命周期的数据脉络&#xff0c;为您…