【小家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);
}




相关文章
|
28天前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
5月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
375 70
|
10月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
258 2
|
6月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
114 0
|
11月前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
11月前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
8月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
298 7
|
9月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
202 2
|
9月前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
1226 1