Spring 源码解析 -- SpringWeb过滤器Filter解析

springboot webflux 过滤器(WebFilter) springbootwebflux过滤器(WebFilter) 阅读详情

简介

在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如Filter。本篇探索Filter初始化、请求处理等相关代码。

前言

说先简单的定义相关的测试代码:

启动类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class SpringExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringExampleApplication.class, args);
    }
}

Controller相关代码:

import com.example.springexample.vo.User;
import org.springframework.web.bind.annotation.*;

@RestController
public class HelloWorld {

    @GetMapping("/")
    public String helloWorld(@RequestParam(value = "id") Integer id,
                             @RequestParam(value = "name") String name) {
        return "Hello world:" + id;
    }

    @GetMapping("/test1")
    public String helloWorld1(@RequestParam(value = "id") Integer id) {
        return "Hello world:" + id;
    }

    @PostMapping("/test2")
    public String helloWorld2(@RequestBody User user) {
        return "Hello world:" + user.toString();
    }
}

Filter相关代码:

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@Slf4j
@Order(1)
@WebFilter(filterName = "MyFilter1", urlPatterns = "/test1")
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 1");
        chain.doFilter(request, response);
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@Slf4j
@Order(2)
@WebFilter(filterName = "MyFilter2", urlPatterns = "/test2")
public class MyFilter2 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 2");
        chain.doFilter(request, response);
    }
}

核心的代码如上,相关的请求Filter处理如下:

  • / : 两个Filter都不触发
  • /test1 : 触发MyFilter1
  • /test2 : 触发MyFilter2

符合我们的使用预期,接下来我们到源码中探索:

  • 1.Filter是如何初始化的
  • 2.Filter是如何对应相关的URL请求的

源码解析

探索时是直接在MyFilter类打上断点,一步步探索堆栈,得到的相关源码如下

Filter初始化

首先是找到Filter相关类获取并遍历的相关代码

下面的函数中,遍历获得了系统内置的和我们自己定义的Filter(如何获取的细节先不深究,在这里能得到所有的Filter)

    # ServletWebServerApplicationContext.class
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        this.prepareWebApplicationContext(servletContext);
        this.registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
	// 得到了所有的Filter,遍历处理
        Iterator var2 = this.getServletContextInitializerBeans().iterator();
        while(var2.hasNext()) {
            ServletContextInitializer beans = (ServletContextInitializer)var2.next();
            beans.onStartup(servletContext);
        }

    }

接下来来到添加注册Filter相关的代码部分

    # AbstractFilterRegistrationBean.class
    protected void configure(Dynamic registration) {
        super.configure(registration);
        EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
        if (dispatcherTypes == null) {
            T filter = this.getFilter();
            if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter", filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
                dispatcherTypes = EnumSet.allOf(DispatcherType.class);
            } else {
                dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
            }
        }

        Set<String> servletNames = new LinkedHashSet();
        Iterator var4 = this.servletRegistrationBeans.iterator();
	// 这部分代码作用尚不明确,留待以后探索
        while(var4.hasNext()) {
            ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next();
            servletNames.add(servletRegistrationBean.getServletName());
        }

        servletNames.addAll(this.servletNames);
        if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
	    // 系统默认的都走的这部分处理,拦截路径默认都是:/**
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
        } else {
            if (!servletNames.isEmpty()) {
                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames));
            }
	    // 我们自定义的都都了这里,拦截路径就是我们配置的:/test1,/test2
            if (!this.urlPatterns.isEmpty()) {
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
            }
        }

    }

在上面的代码中,我们看到了拦截路径的配置有两个方式:

  • servletNames
  • urlPatterns

接着跟下去,下降就是将Filter添加

    # ApplicationFilterRegistration.java
    public void addMappingForUrlPatterns(
            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
            String... urlPatterns) {

        FilterMap filterMap = new FilterMap();

        filterMap.setFilterName(filterDef.getFilterName());

        if (dispatcherTypes != null) {
            for (DispatcherType dispatcherType : dispatcherTypes) {
                filterMap.setDispatcher(dispatcherType.name());
            }
        }

	// Filter添加的相关代码
        if (urlPatterns != null) {
            // % decoded (if necessary) using UTF-8
            for (String urlPattern : urlPatterns) {
                filterMap.addURLPattern(urlPattern);
            }

            if (isMatchAfter) {
                context.addFilterMap(filterMap);
            } else {
                context.addFilterMapBefore(filterMap);
            }
        }
        // else error?
    }

上面的代码中,Filter添加有两处代码:一个是添加到Map中,一个是context中,后者好像还是有其他道道,后面继续研究看看

到这里,Filter就初始化完成了,下面看看使用方面的代码

Filter匹配添加

在日常开发中,Filter我们都会配置相关的匹配路径,不是所有的请求都进行过滤,那这块的匹配是怎么的?接下来就发起请求,探索Filter的匹配添加

下面的代码是核心的Filter匹配处理,但前面的触发调用目前暂时还没有梳理清楚,Wrapper好像挺关键的,暂时忽略它,先看Filter匹配处理相关的

    # ApplicationFilterFactory.java
    public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
	......
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
	// 获取Filter,得到上面初始化的Filter
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0)) {
            return filterChain;
        }

        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

	// 请求的路径
        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

	// 在这里就进行匹配了
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

    // 上面的触发调用后,就来到了下面的将Filter添加到列表中的相关diam
    # ApplicationFilterChain.java
    void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters) {
            if(filter==filterConfig) {
                return;
            }
        }

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }

上面的就是核心的Filter匹配添加的核心代码,值得注意的点有下面几个:

  • 会被匹配添加两次
  • 匹配有下面三种方式:
    • matchDispatcher(filterMap, dispatcher)
    • matchFiltersURL(filterMap, requestPath)
    • matchFiltersServlet(filterMap, servletName)

这里就有下面两点疑问了:

  • 为啥需要将两次匹配分开:是为了前后Filter区分?
  • 两次Filter循环匹配,好像就是匹配路径requestPath和匹配ServletName的区别,两者有何不同,为啥需要分开?

关于上面的疑问目前我也没找到确定的线索,后面的探索中,应该能把它补上

Filter触发

经过上面的Filter匹配,请求的Filter就初始化好了,下面就进入到处理调用环节

    # ApplicationFilterChain.java
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                        (java.security.PrivilegedExceptionAction<Void>) () -> {
                            internalDoFilter(req,res);
                            return null;
                        }
                );
            } catch( PrivilegedActionException pe) {
		    ......
            }
        } else {
	    // 调用触发
            internalDoFilter(request,response);
        }
    }

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
		// 获取当前Filter
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
		    // 调用触发
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
		    ......
            }
            return;
        }

        // 下面的代码有疑似结束Filter,触发请求函数处理相关的代码。先打个标记,后面探索
        // We fell off the end of the chain -- call the servlet instance
	......
    }

上面就是Filter调用触发的核心代码了,链式触发调用,在SpringCloudGateway和Netty中都有类型的相关代码,看着这种代码模式很经典啊,但细节就后面研究了

总结

经过上面的代码分析,Filter的基本核心代码已经被我们找到了:

  • 1.Filter的初始化:在应用程序启动时,进行Filter的初始化
  • 2.Filter的匹配添加:对于不用的请求路径,会匹配生成不同的Filter链路
    • 其中有用缓存吗:经过实验,每次请求都会进行匹配
  • 3.链式调用处理

其中还有很多疑问点,还有Order排序相关的好像没有找到,感兴趣的可以自行查找下

参考链接

SpringWeb项目中越权漏洞的实战检测与防御策略 本文深入探讨了SpringWeb项目中越权漏洞的实战检测与防御策略,包括未授权访问、水平越权和垂直越权的检测方法,并提供了基于过滤器和拦截器的解决方案。通过详细的代码示例和最佳实践,帮助开发者有效提升系统安全性,防止数据泄露和业务逻辑篡改。 阅读详情

相关推荐

STM32实战指南:TFTLCD液晶屏驱动与ILI9341应用解析

本文详细解析了STM32驱动TFTLCD液晶屏的实战技巧,重点介绍了ILI9341控制器的硬件连接、寄存器操作及性能优化方法。通过8080并行接口时序分析、GRAM操作优化和DMA传输应用,帮助开发者高效实现液晶屏驱动,适用于工业控制、智能家居等场景。

weixin_26873711的博客 232

SpringBoot系列教程web篇之过滤器Filter使用指南

实现 Filter 接口doFilter 方法中,显示调用。

程序员高级码农的博客 2554

使用cartographer纯定位替换amcl定位

经过实际使用,发现cartographer定位对于重定位的表现比amcl好。如果机器人的起始位置离原点较远,amcl很难自行重定位到正确的位置,只能手动修改初始位置。然而cartographer定位的表现则很好,在行驶一段距离或原地旋转一段时间后能够正确重定位。

XJ_75的博客 8313

SpringBoot整合Web过滤器、监听器、拦截器

整合过滤器 SpringBoot定义一个web过滤器只需要其继承HttpFilter类,并在类上使用@WebFilter注解声明过滤路径。 @Slf4j @WebFilter("/*") // 过滤器路径 public class MessageFilter extends HttpFilter { public void doFilter(HttpServletRequest request, HttpServletResponse response,

Nicholas_GUB的博客 1764

信号完整性分析学习--16--传输线效应

传输线效应 说到传输线效应,大多数人都会想到一个经验法则,那就是: “当互连线的长度大于1/6倍的信号上升时间的空间延伸时,互连线就体现出传输线效应。” 上升时间越小越容易体现传输线效应。 由此可见,同一根互连线,它是否体现传输线效应,要看它要传输什么样的信号。下图所示,一根延时为15ps的互连线,当它传输上升时间为60ps的信号时就会体现传输线效应;当它传输上升时间为180ps的信号时就不体现传输线效应。

qq_29476769的博客 7272

AI奶牛TMR智能饲喂机器人 Qt信创完整项目( 统信/麒麟国产系统直接编译)

内置**犊牛、育成牛、围产牛、泌乳牛、干奶牛**5套标准化饲喂配方;+" 饲喂批次完成|牛群:"+grpText+"|投喂饲料:"+QString::number(ft)+"吨|剩料:"+QString::number(st)+"吨|异常牛:"+QString::number(abCow);cv::putText(frame,"霉变变质饲料禁止饲喂",cv::Point(20,40),cv::FONT_HERSHEY_SIMPLEX,0.7,cv::Scalar(0,0,255),2);

寒寒豆豆软件公司的博客 181

springboot中过滤器@WebFilter的使用以及简单介绍限流算法

本文针对springboot中过滤器@WebFilter的使用进行简单介绍,包括一些名词概念、使用方法和使用场景等等,同时引出一些限流算法的简单介绍

qq_41114884的博客 5947

ROCm rocr-libhsakmt分析系列1-2:多GPU跨卡VRAM访问机制分析

本文基于ROCr-Runtime的libhsakmt库源码,深入分析了多GPU系统中跨设备内存访问的实现机制。主要内容包括: 实现流程概述:通过P2P拓扑发现、统一虚拟地址空间、页表映射和硬件转发四个步骤,实现零拷贝的跨GPU内存访问。

图形/计算GPU的驱动与虚拟化技术分享园地 2498

spring web.xml中 过滤器(Filter)的工作原理和代码

spring web.xml中 过滤器(Filter)的工作原理和代码

ttxs_10的博客 2643

YOLOv5源码逐行超详细注释与解读(3)——训练部分train.py

全网最详细的YOLOv5项目源码解读之训练部分train. py。全文近5万字!代码逐行注释,逐段讲解,小白入门必备!

路人贾的博客 3万+

EtherCAT主站SOEM -- 19 --Qt-Soem通过CSP模式(周期同步位置模式)控制一个电机转圈圈

以上就是EtherCAT主站SOEM – 19 --Qt-Soem通过CSP模式(周期同步位置模式)控制一个电机转圈圈的内容。有不明白的地方欢迎留言;有建议欢迎留言,我后面编写文档好改进。创作不容,如果文档对您有帮助,记得给个赞。

常驻客栈的博客--嵌入式EtherCAT 1994

springWeb---OncePerRequestFilter抽象类

作用: OncePerRequestFilter抽象类最主要的作用就是保证一次请求只通过一次filter,而不需要重复执行。从下图也可以看到在spring的filter中都继承了OncePerRequestFilter,为什么要这么做呢? 场景: 通过官方英文注释可以找到一些解释,这个抽象类的出现是为了兼容不同的web容器。因为在不同的servlet版本中,执行过程也不同。例如: 在Servle...

池海 1521

7 中望CAD二次开发-标注(一)(ZRX C++)

尺寸标注是CAD设计中最常用的功能之一,软件内置了很多标注功能,可以通过使用这些标注工具,对需要标注的长度、角度等一个个进行标注。但这显然效率很低,学习CAD二次开发,就是要把人从重复的劳动中解放出来,实现批量标注。本文介绍,如何通过二次开发进行标注。

cilvsuanna的博客 337

探究SpringWeb对于请求的处理过程

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

hongduilanjun的博客 529

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

探究SpringWeb对于请求的处理过程解析

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

y473158Yansu的博客 124

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

探究SpringWeb对于请求的处理过程3

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

woshiyanfu的博客 102

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

关于探究SpringWeb对于请求的处理过程2

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

woshiyanfu的博客 148

探究SpringWeb对于请求的处理过程2

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

luoxiayan的博客 117

探究SpringWeb对于请求的处理过程32245

首先在分析spring对请求处理之前之前,首先需要了解DispatcherServlet,它是Spring MVC的核心,负责接收HTTP请求,并根据请求信息分发到相应的Controller进行处理。DispatcherServlet重要特性如下:前端控制器模式:在Spring MVC框架中,DispatcherServlet实现了前端控制器设计模式。这个模式的主要思想是提供一个中心点,所有的请求将先到达这个中心点,然后由它进行分发。

stopluox的博客 120

ajax项目配置,Spring mvc环境下web项目配置ajax跨域访问

必须配置个filter,在filter设置httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");搞定了。之后还报了个小错误,认证httpServletResponse.setHeader("Access-Control-Allow-Headers", "Authentication");搞定了。重要的事情说三次:必须在f...

weixin_36231529的博客 267
上一篇: Spring源码解析 -- SpringWeb请求参数获取解析
下一篇: minetest Window编译运行
_萧_
博客等级 码龄10年 55粉丝 157原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值