【小家Spring】从OncePerRequestFilter的源码解读去了解Spring内置的Filter的特别之处以及常见过滤器使用介绍(上)

简介: 【小家Spring】从OncePerRequestFilter的源码解读去了解Spring内置的Filter的特别之处以及常见过滤器使用介绍(上)

相关阅读


【小家Java】Servlet规范之—请求(request):Servlet中如何获取POST的请求参数?(使用getParameter())


前言


首先我附上一个截图:


image.png


从截图上我们可以看到我标红的我们比较熟悉,配置过的一些Filter,他们都继承自OncePerRequestFilter。


该Filter从字面上理解:只执行一次的Filter。可能有人会问了,我们自己写的Filter不都只执行一次吗?为何Spring还要专门提供这么一个类来处理呢?


这就是本文关心的内容,就Spring内置的这些Filter,我们去理解下Spring的用意何在~


在Spring中,Filter默认继承OncePerRequestFilter

关于OncePerRequestFilter



OncePerRequestFilter:顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢。


往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,我们可以看看Spring的javadoc怎么说:

 *
 * <p>As of Servlet 3.0, a filter may be invoked as part of a
 * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or
 * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in
 * separate threads. A filter can be configured in {@code web.xml} whether it
 * should be involved in async dispatches. However, in some cases servlet
 * containers assume different default configuration. 


简单的说就是去适配了不同的web容器,以及对异步请求,也只过滤一次的需求。另外打个比方:如:servlet2.3与servlet2.4也有一定差异:


在servlet2.3中,Filter会经过一切请求,包括服务器内部使用的forward转发请求和<%@ include file=”/login.jsp”%>的情况

servlet2.4中的Filter默认情况下只过滤外部提交的请求,forward和include这些内部转发都不会被过滤,


因此此处我有个建议:我们若是在Spring环境下使用Filter的话,个人建议继承OncePerRequestFilter吧,而不是直接实现Filter接口。这是一个比较稳妥的选择


需要注意的是:


@Override
public final void init(FilterConfig filterConfig) throws ServletException {}


它Final掉了init方法,因此若我们继承它,无法使用init方法了。但我们可以复写initFilterBean这个方法,实现我们比init方法更强大的一些逻辑,可以直接使用容器对象了,如下:~

@Component("helloFilter")
public class HelloFilter extends OncePerRequestFilter {
    @Override
    protected void initFilterBean() throws ServletException {
        System.out.println("Filter初始化...");
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        FilterConfig filterConfig = super.getFilterConfig();
        ServletContext servletContext = super.getServletContext();
        Environment environment = super.getEnvironment();
        filterChain.doFilter(request, response);
    }
}


需要注意的是,这个方法在初始化的时候,会被执行两次。虽然没什么影响,但个人认为这是Spring的Bug吧,执行两次的地方在GenericFilterBean这个类:


@Override
  public void afterPropertiesSet() throws ServletException {
  initFilterBean();
  }


以及它的init方法:


  @Override
  public final void init(FilterConfig filterConfig) throws ServletException {
  ...
  // Let subclasses do whatever initialization they like.
  initFilterBean();
  ...
  }

但是,继承自OncePerRequestFilter的Filter采用@WebFilter以及Spring Bean的方式都是ok好使的。若采用@WebFilterinitFilterBean方法就只会被执行一次,但是,但是,但是此时@Autowaire自动注入就不好使了,需要自己去容器里拿:


super.getServletContext();

所以看自己需求,哪个方便选哪个。(比如我的Filter只需要记录一下请求日志,没必要注入Spring的Bean,那么我觉得知己@WebFilter就更方便了)


OncePerRequestFilter源码解读


为了方便小伙伴阅读源码,此处我直接吧我自己的理解写在代码上:


/**
* 过滤器基类,旨在确保每个请求调度在任何servlet容器上执行一次执行。 
* 它提供了一个带有HttpServletRequest和HttpServletResponse参数的{@link #doFilterInternal}方法。
*/
public abstract class OncePerRequestFilter extends GenericFilterBean { ... }


我们知道filter最主要的是doFilter方法:


// 已过滤过的过滤器的固定后缀名
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
// 相当于生成已过滤的过滤器的名字(全局唯一的)
protected String getAlreadyFilteredAttributeName() {
  String name = getFilterName();
  if (name == null) {
    name = getClass().getName();
  }
  return name + ALREADY_FILTERED_SUFFIX;
}
//判断该请求是否是异步请求(Servlet 3.0后有异步请求,Spring MVC3.2开始)
protected boolean isAsyncDispatch(HttpServletRequest request) {
  return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
}
//是否需要不过滤异步的请求(默认是不多次过滤异步请求的)
//javadoc:javax.servlet.DispatcherType.ASYNC的请求方式意味着可能在一个请求里这个过滤器会被多个不同线程调用多次,而这里返回true,就能保证只会被调用一次
protected boolean shouldNotFilterAsyncDispatch() {
  return true;
}
//原理基本同上
protected boolean shouldNotFilterErrorDispatch() {
  return true;
}
//可以人工直接返回true  那这个请求就肯定不会被过滤了~~~~
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
  return false;
}
//这里很清楚的记录着  需要被跳过的请求们,这种请求直接就放行了
private boolean skipDispatch(HttpServletRequest request) {
  if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
    return true;
  }
  if (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null && shouldNotFilterErrorDispatch()) {
    return true;
  }
  return false;
}


有了介绍上面这些基础方法,那么问题接下来我们就可以看doFilter方法了:


  @Override
  public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
    //只处理http请求
    if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
      throw new ServletException("OncePerRequestFilter just supports HTTP requests");
    }
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    //判断这个请求是否需要执行过滤
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
    if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
      // 直接放行,不执行此过滤器的过滤操作
      filterChain.doFilter(request, response);
    } else {
      // 执行过滤,并且向请求域设置一个值,key就是生成的全局唯一的·alreadyFilteredAttributeName·
      request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
      try {
        //由子类自己去实现拦截的逻辑  注意 自己写时,filterChain.doFilter(request, response);这句代码不要忘了
        doFilterInternal(httpRequest, httpResponse, filterChain);
      }
      finally {
        // Remove the "already filtered" request attribute for this request.
        request.removeAttribute(alreadyFilteredAttributeName);
      }
    }
  }


最后有必要父类中提供的一个方法,获取到该Filter的名字:

// 如果在Spring应用程序上下文中初始化为bean,那么它将返回到bean工厂中定义的bean名称。
// 需要注意的是,如果是以bean的形式加入了。(比如Boot环境下),此时FilterConfig还未null的,所以有这个判断
@Nullable
protected String getFilterName() {
  return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
}




相关文章
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
647 2
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
865 70
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
1609 0
|
人工智能 安全 Java
Spring Boot 过滤器 拦截器 监听器
本文介绍了Spring Boot中的过滤器、拦截器和监听器的实现与应用。通过Filter接口和FilterRegistrationBean类,开发者可实现对请求和响应的数据过滤;使用HandlerInterceptor接口,可在控制器方法执行前后进行处理;利用各种监听器接口(如ServletRequestListener、HttpSessionListener等),可监听Web应用中的事件并作出响应。文章还提供了多个代码示例,帮助读者理解如何创建和配置这些组件,适用于构建更高效、安全和可控的Spring Boot应用程序。
1055 0
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
811 7
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
462 0