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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【小家Spring】从OncePerRequestFilter的源码解读去了解Spring内置的Filter的特别之处以及常见过滤器使用介绍(中)

Spring内置OncePerRequestFilter实现


开始我们已经截图了,Spring内部了好些个该Filter的实现,我们只需要轻松配置一下即可使用了。下面介绍几个最常用的:

CharacterEncodingFilter


这个类是专门来解决body编码(乱码问题的),在我们还是web.xml时代的时候,大家肯定都熟悉这样的配置:

  <!-- characterEncodingFilter字符编码过滤器 -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!--要使用的字符集,一般我们使用UTF-8(保险起见UTF-8最好)-->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <!--是否强制设置request的编码为encoding,默认false,不建议更改-->
      <param-name>forceRequestEncoding</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <!--是否强制设置response的编码为encoding,建议设置为true,下面有关于这个参数的解释-->
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <!--这里不能留空或者直接写 ' / ' ,否者不起作用-->
    <url-pattern>/*</url-pattern>
  </filter-mapping>


只需要这么处理一下,我们就不再需要处理body体里面的编码问题了。


它的doFilterInternal方法的实现也比较简单,各位有兴趣可以自己翻看源码


HiddenHttpMethodFilter


浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持,spring3.0添加了这个过滤器,可以让我们的form表达拥有发任何标准的http请求的能力了。


在ajax rest编程风格大行其道的今天,可能这个使用场景比较少了。但是,但是,你懂的~


它的实现原理也异常简单,此处不做过多说明了


HttpPutFormContentFilter


有些人可能遇到过,用ajax发送一个put请求给后台的Spring MVC,发现request.getParameter()的时候拿不到值,很是纳闷。


其实,是因为对于表单提交,tomcat默认只解析POST的表单,对于PUT和DELETE的不处理,所以Spring拿不到。


解决办法一:修改tomcat的server.xml(极其不推荐)

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000"
           redirectPort="8443"
           parseBodyMethods="POST,PUT,DELETE"
           URIEncoding="UTF-8" />


解决办法二:使用Spring提供的HttpPutFormContentFilter


    <filter>
        <filter-name>httpPutFormContentFilter</filter-name>
        <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>httpPutFormContentFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


FormContentFilter:处理PUT请求等的请求参数


它是一个新贵。Spring 5.1后才推出。该过滤器针对DELETE,PUT和PATCH这三种HTTP method分析其FORM表单参数,将其暴露为Servlet请求参数。


缺省情况下,Servlet规范仅针对HTTP POST做这样的要求。这个我在之前有篇文章里都有说过:

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


因为FormContentFilter依赖的是Spring MVC的消息转换器:FormHttpMessageConverter,所以它支持的MediaType也必须只能是application/x-www-form-urlencoded的。


简单的说想要它和Servlet规范对POST一样对待,也是必须遵循我们博文里说的4大规范的


从FormContentFilter的效果也能想到,它肯定会调用request.getInputStream();,所以后续我们不能再使用getInputStream()了。另外它要想getParameter系列方法有效果,所以必须包装一下request。它用的是自己的静态内部类:


private static class FormContentRequestWrapper extends HttpServletRequestWrapper { ... }


RequestContextFilter


这个过滤器有点意思,字面理解:请求上下文过滤器。

作用:让你在一个请求的线程内,任意地方都可以获取到请求参数的相关信息,非常的方便。


这里面有两个Spring里面非常重要的类:

org.springframework.context.i18n.LocaleContextHolder

org.springframework.web.context.request.RequestContextHolder

这样当前请求随后的处理过程中,就可以在当前线程中获取的当前请求的信息,而无需把请求对象作为参数到处传递 。


这里注意一个概念,缺省情况下,Servlet容器对一个请求的整个处理过程,是由同一个线程完成的,中途不会切换线程。但这个线程在处理完一个请求后,会被放回到线程池用于处理其他请求。


该过滤器由web.xml或者WebMvcAutoConfiguration(Spring Boot内的)注册。

Spring还有两个类,也做了同样的事情,也会达到此种效果:

RequestContextListener和DispatcherServlet。因此可以看出,如果我们已经配置了DispatcherServlet是正常的Spring MVC环境,是没必要在配置此Filter的。


DispatcherServlet中往当前线程中设置请求的逻辑已经已经足够了,但是在一个Web应用中,并不是所有的请求都最终会被DispatcherServlet处理,比如匿名用户访问一个登录用户才能访问的资源,此时请求只会被安全过滤器处理,而不会到达DispatcherServlet,在这种情况下,该过滤器RequestContextFilter就起了担当了相应的职责。


Springboot 提供了一个OrderedRequestContextFilter继承自RequestContextFilter应用在基于Springboot的Servlet Web应用中。OrderedRequestContextFilter在RequestContextFilter的功能上仅仅增加了接口OrderedFilter定义的过滤器顺序,并且缺省使用优先级(-105)。在整个Servlet过滤器链中,过滤器的顺序数字越小,表示越先被调用。


这个过滤器的源码比较简单,反倒我觉得核心在LocaleContextHolder和RequestContextHolder这里。这里就不展开了,核心原理还是强大的ThreadLocal


MultipartFilte


和文件上传有关。当我们需要自定义文件上传解析器的时候,需要用到它来切换。


因为使用较少,参见:

springMVC:为MultipartFilte配置了上传文件解析器,报错或不能使用

解决自定义文件上传处理与Spring MultipartResolver的冲突问题


这里面有个结论挺有意思:Spring内置了两个上传处理器

image.png


CommonsMultipartResolver:使用commons Fileupload来处理multipart请求,使用时需导入jar包。

StandardServletMultipartResolver:是基于Servlet3.0来处理multipart请求的,所以不需要引用其他jar包,但是必须使用支持Servlet3.0的容器


CorsFilter


跨域相关的过滤器。


跨域:当一个资源从与该资源本身所在的服务器不同的域或端口不同的域或不同的端口请求一个资源时,资源会发起一个跨域 HTTP 请求。


出于安全考虑,浏览器会限制从脚本内发起的跨域HTTP请求。跨域资源共享机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。浏览器支持在 API 容器中使用 CORS,以降低跨域 HTTP 请求所带来的风险。


针对于JAVA开发而言,为了更好的做业务分层,经常会将前后端代码分离开来,发布在不同的服务器上,此时,便会遇到跨域的问题。


若在Spring MVC环境,解决此问题就更简单了~


AbstractRequestLoggingFilter


记录请求日志的一个过滤器,非常好用有木有。Spring默认给我们提供两个实现:

CommonsRequestLoggingFilter:

  @Override
  protected boolean shouldLog(HttpServletRequest request) {
    return logger.isDebugEnabled();
  }
  @Override
  protected void beforeRequest(HttpServletRequest request, String message) {
    logger.debug(message);
  }
  @Override
  protected void afterRequest(HttpServletRequest request, String message) {
    logger.debug(message);
  }


它调用初始化时候设置的GenericFilterBean中的logger进行记录,并且默认记录debug级别日志。

ServletContextRequestLoggingFilter:


  @Override
  protected void beforeRequest(HttpServletRequest request, String message) {
    getServletContext().log(message);
  }
  @Override
  protected void afterRequest(HttpServletRequest request, String message) {
    getServletContext().log(message);
  }


使用ServletContext来记录日志,【不会输出到控制台,ServletContext.log()日志输出tomcat的目录下,具体位置和tomcat的配置有关】


内置的两个实现,我们一般都用不着,此处主要是我们自己去实现的意义非常大。


参数介绍:

默认参数如下:

public static final String DEFAULT_BEFORE_MESSAGE_PREFIX = "Before request [";
public static final String DEFAULT_BEFORE_MESSAGE_SUFFIX = "]";
public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";
public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";
// 默认body体里只会打印出50个字符,自己可以自定义修改
private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 50;
//true表示包含查询参数  形如:[  uri=xxx?a=xx&b=xxx  ]
private boolean includeQueryString = false;
//true表示包含客户端相关信息
private boolean includeClientInfo = false;
//true表示包含请求头信息
private boolean includeHeaders = false;
//true表示包含body体性息
private boolean includePayload = false;


通用的,一些方法我们可以复写,来改变一些默认行为(大部分情况下都不需要复写~)


  public void setMaxPayloadLength(int maxPayloadLength) {
    Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' should be larger than or equal to 0");
    this.maxPayloadLength = maxPayloadLength;
  }
  //自定义请求前、后的前缀、后缀等等  这个其实是建议定制的  个性化点比较好
  public void setBeforeMessagePrefix(String beforeMessagePrefix) {
    this.beforeMessagePrefix = beforeMessagePrefix;
  }
  //是否过滤异步的请求  这里false表示要过滤
  @Override
  protected boolean shouldNotFilterAsyncDispatch() {
    return false;
  }
  //生成日志消息的方法  这个强烈不建议调用者自己去做(当然,你要个性化,你随意)  getMessagePayload
  protected String createMessage(HttpServletRequest request, String prefix, String suffix) { ... }
  //这个非常的重要,判断哪些请求输出日志,哪些不需要输出。比如get请求,我们一般都不需要输出日志的 (至于能够实现标注指定注解接口采取输出日志呢?这个就做不到了,因为还没有到方法呢,没有办法拿到方法元信息)  但是若通过HandlerInterceptor来拦截,是可以处理方法注解的
  protected boolean shouldLog(HttpServletRequest request) {
    return true;
  }
  //在系统默认的message的基础上,你自己再去个性化吧。或者不用此message变量都成~  比如自己可以计算出请求耗时~
  protected abstract void beforeRequest(HttpServletRequest request, String message);
  protected abstract void afterRequest(HttpServletRequest request, String message);



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
19天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
19天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
19天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
4天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
13 4
|
9天前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
38 10
|
5天前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
13 3
|
9天前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
9天前
|
XML 存储 Java
Spring-源码深入分析(二)
Spring-源码深入分析(二)
|
9天前
|
XML 设计模式 Java
Spring-源码深入分析(一)
Spring-源码深入分析(一)
|
2月前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
584 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
下一篇
无影云桌面