SpringBoot(13)过滤器+拦截器+监听器

2023-10-30

文章目录


一、过滤器

1.过滤器介绍

过滤器的英文名称为Filter,是Servlet技术中最实用的技术。如同它的名字一样,过滤器是处于客户端与服务器资源文件之间的一道过滤网,帮助我们过滤一些不符合要求的请求。通常它被用作 Session校验,判断用户权限,如果不符合设定条件,就会被拦截到特殊的地址或者给予特殊的响应。

2.Filter生命周期

使用过滤器很简单,只需要实现Filter类,然后重写它的3个方法即可。

  • init方法:程序启动调用Filter的init()方法(永远只调用一次);在容器中创建当前过滤器的时候自动调用这个方法。
  • destory方法:程序停止调用Filter的destroy()方法(永远只调用一次);在容器中销毁当前过滤器的时候自动调用这个方法。
  • doFilter方法:doFilter()方法每次的访问请求如果符合拦截条件都会调用(程序第一次运行,会在servlet调用init()方法以后调用;不管第几次,都在调用doGet(),doPost()方法之前)。这个方法有3个参数,分别是ServletRequest、ServletResponse和FilterChain可以从参数中获取HttpServletReguest和HttpServletResponse对象进行相应的处理操作。

4、注解方式实现过滤器(@WebFilter)

@WebFilter

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 )
在这里插入图片描述

@Order(1)

标识当前过滤器的执行顺序,值越大越靠前执行;

@ServletComponentScan

只有在springboot 启动类添加该注解时@WebFilter注解才会生效
SpringBootApplication 上使用 @ServletComponentScan 注解后

  • Servlet可以直接通过@WebServlet注解自动注册
  • Filter可以直接通过@WebFilter注解自动注册
  • Listener可以直接通过@WebListener 注解自动注册

启动类代码

@SpringBootApplication
@ServletComponentScan
public class ApplicationStarter {
  public static void main(String[] args) {
      SpringApplication.run(ApplicationStarter.class,args);
  }
}

Filter代码


package com.buba.filter;


import com.alibaba.fastjson.JSONObject;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author qlx
 */
@WebFilter(urlPatterns = "/test/*", filterName = "testFilter")
@Order(1)
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("token");
        System.out.println("token");
        //该方法执行后直接运行至下一个过滤器
        if(token!=null){
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = servletResponse.getWriter();
            JSONObject res = new JSONObject();
            res.put("msg", "错误");
            res.put("success", "false");
            out.append(res.toString());
        }
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

5、直接注入到spring中

Filter代码

package com.buba.filter;


import com.alibaba.fastjson.JSONObject;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author qlx
 */
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("token");
        System.out.println("token");
        //该方法执行后直接运行至下一个过滤器
        if(token!=null){
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = servletResponse.getWriter();
            JSONObject res = new JSONObject();
            res.put("msg", "错误");
            res.put("success", "false");
            out.append(res.toString());
        }
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

Filter配置类代码

package com.buba.config;

import com.buba.filter.TestFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public TestFilter myFilter() {
        return new TestFilter();
    }

    @Bean
    public FilterRegistrationBean getFilterRegistrationBean(TestFilter myFilter) {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(myFilter);
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/test/*");
        filterRegistrationBean.setName("myFilter");
        return filterRegistrationBean;
    }
}

二、拦截器

1.拦截器介绍

拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。

你可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置…

在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个)。

Spring Interceptor是一个非常类似于Servlet Filter 的概念 。

Java中的拦截器是动态拦截 action调用的对象,然后提供了可以在action执行前后增加一些
操作,也可以在action执行前停止操作。其实拦截器也可以做和过滤器同样的操作,以下是拦截器的常用场景。

  1. 登录认证:在一些简单应用中,可能会通过拦截器来验证用户的登录状态,如果没有登录或者登录失效,就会给用户一个友好的提示或者返回登录页面。
  2. 记录系统日志:在Web应用中,通常需要记录用户的请求信息,比如请求的IP、方法执行时
    常等,通过这些记录可以监控系统的状况,以便于对系统进行信息监控、信息统计、计算PV (PageView)和性能调优等。
  3. 通用处理:在应用程序中可能存在所有方法都要返回的信息,这时可以使用拦截器来实现 省去每个方法冗余重复的代码实现。
  4. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  5. 权限检查:如登录检测,进入处理器检测是否登录;
  6. 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache
    也可以自动记录)
  7. 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。

2.拦截器生命周期

这里以使用Spring拦截器为例,在类上需要实现HandlerInterceptor类,并且重写类中的3个方法,分别是:

  • opreHandle在业务处理器处理请求之前被调用,方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行,用来进行一些前置初始化操作或是对当前请求做预处理,也可以进行一些判断来决定请求是否要继续进行下去。该方法的返回至是 Boolean 类型,当它返回 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当它返回为 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。

  • postHandle在业务处理器处理请求执行完成后、生成视图前执行。方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。

  • afterCompletion在DispatcherServlet完全处理请求后被调用,通常用于记录消耗时间,也可
    以进行一些资源处理操作。方法需要在当前对应的 Interceptor 类的 postHandler 方法返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。

3.自定义拦截器案例——性能监控

自定义拦截器有一下两种方式:

  • 实现 org.springframework.web.servlet.HandlerInterceptor接口
  • 继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类

如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如 apache 都具有这个功能,但此处我们演示一下使用拦截器怎么实现。

3.1实现分析:

1、在进入处理器之前记录开始时间,即在拦截器的 preHandle 记录开始时间;

2、在结束请求处理之后记录结束时间,即在拦截器的 afterCompletion 记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
3、在测试时需要把 stopWatchHandlerInterceptor 放在拦截器链的第一个,这样得到的时间才是比较准确的。

3.2问题:

我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?

3.3解决方案

解决方案是使用 ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个 ThreadLocal,A线程的ThreadLocal 只能看到A线程的 ThreadLocal,不能看到B线程的 ThreadLocal)。

3.4拦截器代码实现:

package com.buba.intercepter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
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;

/**
 * @author qlx
 */
public class MyIntercepter implements HandlerInterceptor {
    //NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");
    private Logger logger = LoggerFactory.getLogger(MyIntercepter.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
        return true;//继续流程
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long endTime = System.currentTimeMillis();//2、结束时间
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
        long consumeTime = endTime - beginTime;//3、消耗的时间
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
            //TODO 记录到日志文件
            logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        } else {
            // 测试的时候由于请求时间未超过500,所以启用该代码
            logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }
    }
}

3.5拦截器配置类代码实现

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//        registry.addInterceptor();
        registry.addInterceptor(new MyIntercepter()).addPathPatterns("/test/*");
    }
}

三、拦截器和过滤器的区别

1、触发时机

过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:
在这里插入图片描述

2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行

拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。
在这里插入图片描述
其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的.

3、底层实现原理

过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。

4、何时使用拦截器?何时使用过滤器?

如果是非spring项目,那么拦截器不能用,只能使用过滤器。
如果是处理controller前后,既可以使用拦截器也可以使用过滤器。

四、监听器

1.监听器介绍

监听器通常用于监听Web应用中对象的创建、销毁等动作的发生,同时对监听的情况做出相应的处理,最常用于统计网站的在线人数、访问量等信息。
监听器大致分为以下几种。

  1. ServletContextListener:用来监听ServletContext属性的操作,比如新增、修改、删除。
  2. HttpSessionListener:用来监听Web应用中的Session对象,通常用于统计在线情况。
  3. ServletRequestListener:用来监听Request对象的属性操作。

2.使用监听器案例1——

使用监听器的话,只需要在类中实现对应功能的监听器对象,如本文使用的 HttpSessionListener。下面以监听Session信息为例统计在线人数。新建一个MyHttpSessionListener类,实现HttpSessionListener类,在类中定义一个全局变量online,当创建Session时,online的数量加1;当销毁Session时,online的数量减1。MyHttpSessionListener完整内容如下:

3.使用监听器案例2——自定义事件触发监听器

在实际项目中,我们往往需要自定义一些事件和监听器来满足业务场景,比如在微服务中会有这样的场景:微服务 A 在处理完某个逻辑之后,需要通知微服务 B 去处理另一个逻辑,或者微服务 A 处理完某个逻辑之后,需要将数据同步到微服务 B。这种场景非常普遍,这时我们可以自定义事件以及监听器来监听,一旦监听到微服务 A 中的某事件发生,就去通知微服务 B 处理对应的逻辑。

3.1自定义事件

自定义事件需要继承 ApplicationEvent 对象,在事件中定义一个 User 对象来模拟数据,构造方法中将 User 对象传进来初始化。如下:

package com.buba.event;

import com.buba.bean.User;
import lombok.Data;
import org.springframework.context.ApplicationEvent;

/**
 * @author qlx
 */
public class UserEvent extends ApplicationEvent {
    private User user;
    
    public UserEvent(Object source, User user) {
        super(source);
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

3.2自定义监听器

package com.buba.listener;

import com.buba.bean.User;
import com.buba.event.UserEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @author qlx
 */
@Component
public class MyEventListener implements ApplicationListener<UserEvent> {
    @Override
    public void onApplicationEvent(UserEvent event) {
        // 把事件中的信息获取到
        User user = event.getUser();
        // 处理事件,实际项目中可以通知别的微服务或者处理其他逻辑等
        System.out.println("用户名:" + user.getName());
        System.out.println("age:" + user.getAge());
    }
}

3.3触发逻辑

    @Resource
    private ApplicationContext applicationContext;
    
    @GetMapping("/listener")
    public void getUser2() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        // 发布事件
        UserEvent event = new UserEvent(this, user);
        applicationContext.publishEvent(event);
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot(13)过滤器+拦截器+监听器 的相关文章

随机推荐

  • CTF.show:萌新:web9

    题目要求我们利用 system exec highlight才能执行ev
  • 使用Chrony的Raspberry Pi

    chrony是网络时间协议 NTP 的实现 它替代了ntpd 后者是NTP 的参考实现 与NTPsec不同 chrony是从零开始实现的 它被设计为即使在诸如间歇性网络连接 例如笔记本电脑 和拥塞网络之类的困难条件下也可以同步时间 与ntp
  • 数据挖掘——无监督学习

    一 概述 无监督学习算法 让计算机自己学习 没有属性或者标签 有监督学习算法 每个样本都已经被标明 我们已经被告知了学习的答案 无监督学习的典型算法是聚类算法和降维 二 聚类算法 1 概念 聚类分析 将数据对象分组成为多个蔟 同一簇中的对象
  • [VMware]解决"已将该虚拟机配置为使用 64 位客户机操作系统。但是,无法执行 64 位操作"的问题

    创建好空的虚拟机后使用ISO镜像文件后重启进行安装系统 出现下图问题 原因 是BIOS未开启虚拟化技术 之前个人用的电脑是默认开启了 现在换到公司联想的电脑上安装虚拟机发现了这个问题 解决方式 虚拟化技术目前主要依赖于电脑的CPU型号及BI
  • wireshark 本地连接无数据 抓不到网卡解决方法

    问题描述 环境 win7 wireshark 3 4 6 不知道我之前做了啥 突然wireshark抓不到数据了 过滤的网卡选择哪个接口都不行 捕获选项如下图 做过以下尝试都不行 在cmd中打开抓包服务 net start npf 软件卸载
  • arm64-v8a编译

    环境 Ubuntu64和android ndk r11c 其他不支持arm64 v8a架构 重点 依赖库要使用android ndk r11c编译成arm64 v8a 其中ffmpeg最复杂 编译方法如下 1 ffmpeg编译 目录建立 创
  • 线程池源码(一)

    一 ThreadPoolExecutor执行流程 二 ThreadPoolExecutor状态 线程池中核心属性 ctl ctl本质就是一个int类型的数值 private final AtomicInteger ctl new Atomi
  • 【Python VTK】读取二维序列医学图像分割结果并进行三维重建

    一 问题描述 最近在开发过程中遇到了这样的问题 在医学图像开发过程中 我们将医学图像通过深度学习算法进行分割 现在想要通过这一套二维图像进行三维重构 以下是分割结果 图一 前列腺核磁图像分割结果 图一 前列腺核磁图像分割结果 图一 前列腺核
  • 73-C语言-计算闹钟的时间

    问题 已知现在的小时和分钟 即几点几分 并想要睡觉的时间 单位为min 求闹钟应在几点响 思路 先输入现在准备睡觉的几点几分 以及睡觉的总时长 在看问题最后求的是什么 是到时候 闹钟几点响 而闹钟的设置 先看范围 小时不能超过24小时 分钟
  • Counterfactual Zero-Shot and Open-Set Visual Recognition (CVPR2021)

    这是南洋理工张含望老师组的作品 这篇文章从因果推理出发 来根据反事实推断设计模型 出发点非常的新颖 但不容易懂 因果推理理论是一个非常不错的帮助人换角度看问题的理论工具 多多学习 文章全名叫 Counterfactual Zero Shot
  • Linux的视窗系统总结:初认识《一》

    X window Wayland DirectFB linuxfb的区别 在QT中 真正关心硬件操作的是图形引擎层 图形引擎实现方式在linux上有多个 1 通过X11 2 DFB directFB 3 LinuxFB 其中桌面PC的QT版
  • Django中的Cookie与Session

    Cookie 设置cookie 使用response对象set cookie 获取cookie 用request对象 测试 成功获取到了上一步所添加的cookie Session 一般做项目的时候Session会存到redis中 djang
  • matlab实现简单清浊音检测

    清浊音检测原理公式 清浊音检测是在一段语音信号中区分出清音段 浊音段和静音段 原理框图如下所示 1 过零率 2 对数能量 3 单位延迟自相关系数归一化 4 lpc系数 使用Leveson Durbin算法求得 取 a2 a3 aM的值即为L
  • Java基础——根类与String类

    转眼间我们已经工作完一周了 这周开始阿Q要不定时更新知识了 先让我们来学习一下java中的Object与string类吧 Object类 API Application Programming Interface 应用程序编程接口 Java
  • 给定一个整数,判断它能否被3,5,7整除,并输出以下信息:1、能同时被3,5,7整除(直接输出3 5 7,每个数中间一个空格);2、只能被其中两个数整除(输出两个数,小的在前,大的在后。

    24 int a 25 printf 请输入一个数 26 scanf d a 27 if a 3 0 28 printf 3 29 if a 5 0 30 printf 5 31 if a 7 0 32 printf 7 33 else 3
  • 模拟客户端和服务端

    import org junit jupiter api Test import java io import java net InetAddress import java net ServerSocket import java ne
  • spirng-Alibaba的介绍和导入

    3 spirng Alibaba 3 1spirng Alibaba概述和导入依赖 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img 4hqZgDfQ 1637066858419 https static01 im
  • 使用Python、OpenCV,ImageMagick工具箱根据原始视频制作GIF动画

    使用Python OpenCV ImageMagick工具箱根据原始视频制作GIF动画 python读取原始视频流每一帧 并生成照片保存到本地文件夹 读取本地文件夹图片 根据图片名排序 支持根据帧率过滤 指定最大多少帧照片去生成gif 调用
  • 剑指 Offer 62. 圆圈中最后剩下的数字 <约瑟夫环>

    看了诸多大神的解题还是有点不明白 故记录一下 如题 0 1 n 1这n个数字排成一个圆圈 从数字0开始 每次从这个圆圈里删除第m个数字 删除后从下一个数字开始计数 求出这个圆圈里剩下的最后一个数字 方法一 递归 数学 递归 class So
  • SpringBoot(13)过滤器+拦截器+监听器

    文章目录 一 过滤器 1 过滤器介绍 2 Filter生命周期 4 注解方式实现过滤器 WebFilter WebFilter Order 1 ServletComponentScan 启动类代码 Filter代码 5 直接注入到sprin