Spring Boot 使用过滤器、拦截器、监听器

前言

作用

  • 过滤器(Filter):当有一堆请求,只希望符合预期的请求进来。
  • 拦截器(Interceptor):想要干涉预期的请求。
  • 监听器(Listener):想要监听这些请求具体做了什么。

区别

过滤器是在请求进入容器后,但还没有进入 Servlet 之前进行预处理的。如下图所示。

拦截器是在请求进入控制器(Controller) 之前进行预处理的。

虚线内就是过滤器和拦截器的作用范围:

过滤器依赖于 Servlet 容器,而拦截器依赖于 Spring 的 IoC 容器,因此可以通过注入的方式获取容器当中的对象。

监听器用于监听 Web 应用中某些对象的创建、销毁、增加、修改、删除等动作,然后做出相应的处理。

过滤器

  • 过滤敏感词汇(防止sql注入)
  • 设置字符编码
  • URL级别的权限访问控制
  • 压缩响应信息

过滤器的创建和销毁都由 Web 服务器负责,Web 应用程序启动的时候,创建过滤器对象,为后续的请求过滤做好准备。

过滤器可以有很多个,一个个过滤器组合起来就成了 FilterChain,也就是过滤器链。

在 Spring 中,过滤器都默认继承了 OncePerRequestFilter,顾名思义,OncePerRequestFilter 的作用就是确保一次请求只通过一次过滤器,而不重复执行。

接下来我们通过继承 OncePerRequestFilter 来实现 JWT 登录授权过滤。

 

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
	@Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        // 从客户端请求中获取 JWT
        String authHeader = request.getHeader(this.tokenHeader);
        // 该 JWT 是我们规定的格式,以 tokenHead 开头
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            // The part after "Bearer "
            String authToken = authHeader.substring(this.tokenHead.length());
            // 从 JWT 中获取用户名
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);

            // SecurityContextHolder 是 SpringSecurity 的一个工具类
            // 保存应用程序中当前使用人的安全上下文
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 根据用户名获取登录用户信息
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                // 验证 token 是否过期
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    // 将登录用户保存到安全上下文中
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                            null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

新建一个 Web 项目 codingmore-filter-interceptor-listener。

添加一个过滤器 MyFilter :

@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long start = System.currentTimeMillis();
        chain.doFilter(request,response);
        System.out.println("Execute cost="+(System.currentTimeMillis()-start));
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

@WebFilter 注解用于将一个类声明为过滤器,urlPatterns 属性用来指定过滤器的 URL 匹配模式,filterName 用来定义过滤器的名字。

MyFilter 过滤器的逻辑非常简单,重写了 Filter 的三个方法,在 doFilter 方法中加入了时间戳的记录。

然后我们在项目入口类上加上 @ServletComponentScan 注解,这样过滤器就会自动注册。

启动服务器,访问任意的 URL。

拦截器

  • 登录验证,判断用户是否登录
  • 权限验证,判断用户是否有权限访问资源,如校验token
  • 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量
  • 处理cookie、本地化、国际化、主题等
  • 性能监控,监控请求处理时长等

我们来写一个简单的拦截器 LoggerInterceptor:

@Slf4j
public class LoggerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("preHandle{} ...." ,request.getRequestURI());
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

}

一个拦截器必须实现 HandlerInterceptor 接口,preHandle 方法是 Controller 方法调用前执行,postHandle  是 Controller 方法正常返回后执行,afterCompletion 方法无论 Controller 方法是否抛异常都会执行。

只有 preHandle 返回 true 的话,其他两个方法才会执行。

如果 preHandle 返回 false 的话,表示不需要调用Controller方法继续处理了,通常在认证或者安全检查失败时直接返回错误响应。

再来一个 InterceptorConfig 对拦截器进行配置:

/**
 * @ClAssName InterceptorConfig
 * @Description TOOO
 * @Author soshi是神仙
 * @Date 2024/3/27 18:{MINUTE}
 */
@Configuration
public class InterceptorConfig  implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
    }
}

@Configuration 注解用于定义配置类,干掉了以往 Spring 繁琐的 xml 配置文件。

编写一个用于被拦截的控制器 MyInterceptorController:

@RestController
@RequestMapping("/myinterceptor")
public class MyInterceptorController {
    @RequestMapping("/hello")
    public  String hello(){
        return "测试拦截器的使用";
    }
}

@RestController 注解相当于 @Controller + @ResponseBody 注解,@ResponseBody 注解用于将 Controller 方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。

启动服务器,访问 http://localhost:8080/myinterceptor/hello

在控制台可以看到拦截器中的日志信息

无论是过滤器还是拦截器,都属于AOP(面向切面编程)思想的具体实现。除了这两种实现之外,还有另一种更灵活的AOP实现技术,即 Aspect,在实战项目里,你可以看到 Aspect 具体实现。

比如说统一日志切面 WebLogAspect,就是用来记录请求信息的。

@Aspect
@Component
@Order(1)
public class WebLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * com.codingmore.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        logMap.put("parameter",webLog.getParameter());
        logMap.put("spendTime",webLog.getSpendTime());
        logMap.put("description",webLog.getDescription());
        LOGGER.info("{}", JSONUtil.parse(webLog));
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
    }
}

监听器

根据监听对象可以把监听器分为 3 类:

  • ServletContext:对应应用 application,整个 Web 服务器中只有一个,Web 服务器关闭时销毁。可用于数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
  • HttpSession: 对应会话 session,在会话建立时创建,一端会话关闭时销毁。可用于获取在线用户数量。
  • ServletRequest:对应 request,客户端发送请求时创建,一同创建的还有 response,用于封装请求数据,在一次请求处理完成时销毁。可用于封装用户信息。

新建一个 MyListener:

@WebListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContextListener.super.contextInitialized(sce);

        System.out.println("上下文创建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

        System.out.println("上下文销毁");
    }

@WebListener 注解用于将一个类声明为监听器,同样干掉了 web.xml 文件。

ServletContextListener 能够监听整个 Web 应用程序的生命周期。当 Web 应用启动时触发 contextInitialized 方法,关闭时触发 contextDestroyed 方法。

在 Intellij IDEA 中重启服务的时候,可以在控制台看到如下信息:

不过需要注意的是,在 Intellij IDEA 中直接关闭进程无法看到 contextDestroyed 被调用的消息。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue 与 React:前端框架对比分析

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

docker网段冲突导致主机连接不上

前提:windows电脑链接liunx服务器,liunx服务器里面起了docker。 场景:在liunx服务器里面,用docker-compose up -d启动容器过程中,终止了windows服务器连接liunx服务器 可能原因:1.docker自身的网卡网段与连…

AMEYA360代理 | 江苏长晶科技FST2.0高性能 IGBT产品介绍

江苏长晶科技股份有限公司是一家专业从事半导体产品研发、生产和销售的企业。自2019年起,连续4年被中国半导体行业协会评为 “功率器件十强企业”。2021年开始自主研发有着“工业CPU”之称的IGBT,截至2023年Q3在家电/工业/新能源等行业实现8款产品市场应…

HCIP-Datacom(H12-821)题库补充(3/27)

最新 HCIP-Datacom(H12-821)完整题库请扫描上方二维码访问,持续更新中。 运行OSPF协议的路由器,所有接口必须属于同一个区域。 A:正确 B:错误 答案:B 解析:OSPF的邻居关系是基于…

HarmonyOS NEXT应用开发之ArkWeb同层渲染

介绍 该方案展示了ArkWeb同层渲染:将系统原生组件直接渲染到前端H5页面上,原生组件不仅可以提供H5组件无法实现的一些功能,还能提升用户体验的流畅度 效果图预览 使用说明 进入页面即可看到同层渲染效果,Text,searc…

3-iperf3 使用什么工具可以检测网络带宽、延迟和数据包丢失率等网络性能参数呢?

(1)iperf3简介 1.iperf3简介 2.用途(特点) 3.下载iperf3地址 (2)实战 1.iperf3参数 (1)通用参数(客户端和服务器端都是适用的) (2)客户端参数 实验1&…

基于springboot+vue+Mysql的网上图书商城

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

Python+Selenium+Unittest 之Unittest2

上次简单串了下unittest的使用,这次详细说明下Unittest一些使用细节。 目录 一、TestCase(测试用例) 二、Test Fixture(脚手架) 三、执行顺序 一、TestCase(测试用例) 首先…

使用LangChain LCEL生成RAG应用、使用LangChain TruLens对抗RAG幻觉

# 导入LangChain的库 from langchain import *# 加载数据源 loader WebBaseLoader() doc loader.load("https://xxx.html")# 分割文档对象 splitter RecursiveCharacterTextSplitter(max_length512) docs splitter.split(doc)# 转换文档对象为嵌入,并…

2024年目前阿里云服务器一个月收费价格表多少钱?

阿里云服务器一个月多少钱?最便宜5元1个月。阿里云轻量应用服务器2核2G3M配置61元一年,折合5元一个月,2核4G服务器30元3个月,2核2G3M带宽服务器99元12个月,轻量应用服务器2核4G4M带宽165元12个月,4核16G服务…

创建AI智能体

前言 灵境矩阵是百度推出的基于文心大模型的智能体(Agent)平台,支持广大开发者根据自身行业领域、应用场景,选取不同类型的开发方式,打造大模型时代的产品能力。开发者可以通过 prompt 编排的方式低成本开发智能体&am…

Spring 自定义 CustomQualifier

为什么写这篇文章 Spring 支持类型注入,并且可以通过Qualifier 或者Mate 调整类型注入的范围。但是通过自定义注解结合现有的 Qualifier 使用起来有种种困难。 将 Qualifier 融合在自定义注解中,在使用 AliasFor 遇到问题仅仅检查注解中的一部分内容是否…

Linux系统使用Docker部署Jupyter Notebook结合内网穿透实现公网访问本地笔记

文章目录 1. 选择与拉取镜像2. 创建容器3. 访问Jupyter工作台4. 远程访问Jupyter工作台4.1 内网穿透工具安装4.2 创建远程连接公网地址4.3 使用固定二级子域名地址远程访问 本文主要介绍如何在Ubuntu系统中使用Docker本地部署Jupyter Notebook,并结合cpolar内网穿透…

YOLOv9改进策略:IoU优化 | Wasserstein Distance Loss,助力小目标涨点

💡💡💡本文独家改进:基于Wasserstein距离的小目标检测评估方法 Wasserstein Distance Loss | 亲测在多个数据集能够实现涨点,对小目标、遮挡物性能提升明显 💡💡💡MS COCO和PASC…

【Linux】模拟实现shell(bash)

目录 常见的与shell互动场景 实现代码 全部代码 homepath()接口 const char *getUsername()接口 const char *getHostname()接口 const char *getCwd()接口 int getUserCommand(char *command, int num)接口 void commandSplit(char *in, char *out[])接口 int execut…

TCP重传机制详解——02SACK

文章目录 TCP重传机制详解——02 SACKSACK是什么?为什么要有SACK?实际场景抓包具体显示信息流程 实战抓包讲解SACK关闭场景下,三次重复ACK后会快速重传SACK打开但是不携带SACK块信息场景下,三次重复ACK也不会快速重传SACK打开并且…

P8649 [蓝桥杯 2017 省 B] k 倍区间:做题笔记

目录 思路 代码思路 代码 推荐 P8649 [蓝桥杯 2017 省 B] k 倍区间 思路 额嗯,这道题我刚上来是想到了前缀和,但是还要判断每个子序列,我就两层for嵌套,暴力解了题。就是我知道暴力肯定过不了但是写不出来其他的[留下了苦…

Transformer的前世今生 day09(Transformer的框架概述)

前情提要 编码器-解码器结构 如果将一个模型分为两块:编码器和解码器那么编码器-解码器结构为:编码器负责处理输入,解码器负责生成输出流程:我们先将输入送入编码器层,得到一个中间状态state,并送入解码器…

搜索与图论——Dijkstra

最短路算法 稠密图与稀疏图 n为点数,m为边数。m远小于n的平方为稀疏图,m接近n的平方为稠密图。 稀疏图用邻接表存,稠密图用邻接矩阵存 朴素版dijkstra时间复杂度为O(n^2),对于稠密图可以ac,但遇到稀疏图时会TLE。 dijkstra函数实…

核工业核级树脂的应用

#核工业核级树脂的应用 ​压水堆核电厂核能级树脂主要用于反应堆冷却剂净化系统、乏燃料池冷却水净化系统、蒸汽发生器排污水处理、硼回收系统、放射性废水排放系统等。 核级树脂是压水堆核电厂一回路及其辅助系统的主要水处理材料,用于净化运行过程中产生的裂变产物…