Spring Servlet Web 常用过滤器 : FormContentFilter

本文深入解析了Spring Boot中的FormContentFilter,一种针对DELETE、PUT和PATCH HTTP方法的过滤器,用于分析FORM表单参数并将其作为Servlet请求参数暴露。文章详细介绍了其工作原理,包括如何在基于Spring Boot的Servlet Web应用中使用OrderedFormContentFilter,以及源代码分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

该过滤器针对DELETE,PUTPATCH这三种HTTP method分析其FORM表单参数,将其暴露为Servlet请求参数。

缺省情况下,Servlet规范仅针对HTTP POST做这样的要求。

该过滤器继承自OncePerRequestFilter,也就是说,它在整个请求处理过程中最多只会被应用一次。

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

源代码分析

源代码版本 Spring Servlet Web 5.1.3

package org.springframework.web.filter;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

public class FormContentFilter extends OncePerRequestFilter {

	// 该过滤器仅针对如下三种HTTP method生效
	private static final List<String> HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE");

	private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();


	/**
	 * Set the converter to use for parsing form content.
	 * 设置分析表单内容的转换器,缺省使用的是一个 AllEncompassingFormHttpMessageConverter
	 * >By default this is an instance of AllEncompassingFormHttpMessageConverter.
	 */
	public void setFormConverter(FormHttpMessageConverter converter) {
		Assert.notNull(converter, "FormHttpMessageConverter is required");
		this.formConverter = converter;
	}

	/**
	 * The default character set to use for reading form data.
	 * 设置表单内容分析时使用的字符集
	 * 该方法是 getFormConverter.setCharset(charset) 的快捷方法
	 */
	public void setCharset(Charset charset) {
		this.formConverter.setCharset(charset);
	}


	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// 分析表单内容获取参数 params 
		MultiValueMap<String, String> params = parseIfNecessary(request);
		if (!CollectionUtils.isEmpty(params)) {
			// 如果 params 不为空,则封装请求为一个FormContentRequestWrapper然后继续过滤器链的调用
			filterChain.doFilter(new FormContentRequestWrapper(request, params), response);
		}
		else {
			// 如果 params 为空,使用原来的请求继续过滤器链的调用
			filterChain.doFilter(request, response);
		}
	}

	// 分析表单内容获取参数
	@Nullable
	private MultiValueMap<String, String> parseIfNecessary(HttpServletRequest request) throws IOException {
		// 必须是 : PUT, DELETE , PATCH
		// 并且必须是 : application/x-www-form-urlencoded
		if (!shouldParse(request)) {
			return null;
		}

		// 获取请求主体流,分析其中的参数为MultiValueMap<String, String>并返回
		HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
			@Override
			public InputStream getBody() throws IOException {
				return request.getInputStream();
			}
		};
		return this.formConverter.read(null, inputMessage);
	}

	private boolean shouldParse(HttpServletRequest request) {
		// 仅仅支持 PUT, DELETE , PATCH
		if (!HTTP_METHODS.contains(request.getMethod())) {
			return false;
		}
		// 仅仅支持 application/x-www-form-urlencoded
		try {
			MediaType mediaType = MediaType.parseMediaType(request.getContentType());
			return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType);
		}
		catch (IllegalArgumentException ex) {
			return false;
		}
	}


	// 封装请求和指定的参数,让指定的参数呈现为被封装请求的参数
	private static class FormContentRequestWrapper extends HttpServletRequestWrapper {

		private MultiValueMap<String, String> formParams;

		public FormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> params) {
			super(request);
			this.formParams = params;
		}

		@Override
		@Nullable
		public String getParameter(String name) {
			// 注意,这里还是优先使用request的queryString参数,只是queryString不存在时才从formParams中获取
			String queryStringValue = super.getParameter(name);
			String formValue = this.formParams.getFirst(name);
			return (queryStringValue != null ? queryStringValue : formValue);
		}

		@Override
		public Map<String, String[]> getParameterMap() {
			Map<String, String[]> result = new LinkedHashMap<>();
			Enumeration<String> names = getParameterNames();
			while (names.hasMoreElements()) {
				String name = names.nextElement();
				result.put(name, getParameterValues(name));
			}
			return result;
		}

		@Override
		public Enumeration<String> getParameterNames() {
			Set<String> names = new LinkedHashSet<>();
			names.addAll(Collections.list(super.getParameterNames()));
			names.addAll(this.formParams.keySet());
			return Collections.enumeration(names);
		}

		// 获取指定属性的所有值,会合并queryString中的值和外部指定的formParams中的值到一起然后返回
		@Override
		@Nullable
		public String[] getParameterValues(String name) {
			String[] parameterValues = super.getParameterValues(name);
			List<String> formParam = this.formParams.get(name);
			if (formParam == null) {
				return parameterValues;
			}
			if (parameterValues == null || getQueryString() == null) {
				return StringUtils.toStringArray(formParam);
			}
			else {
				List<String> result = new ArrayList<>(parameterValues.length + formParam.size());
				result.addAll(Arrays.asList(parameterValues));
				result.addAll(formParam);
				return StringUtils.toStringArray(result);
			}
		}
	}

}

<think>好的,用户提供了几条DEBUG级别的日志信息,希望优化Spring Boot应用启动时的日志,让这些提示不再出现。根据之前的对话历史,我之前已经给出过一些优化日志的方法,比如调整日志级别、排除自动配置类等。现在需要针对用户这次提供的具体日志进行分析,给出相应的解决方案。 首先,我需要看一下用户提供的日志内容。这三条日志都是关于Filter的配置信息,级别是DEBUG,来自不同的Filter类,比如OrderedRequestContextFilter、OrderedCharacterEncodingFilter和OrderedFormContentFilter。这说明在应用启动时,Spring Boot正在配置这些内置的过滤器,并输出了调试信息。 接下来,我要考虑用户的需求:他们不希望这些DEBUG日志在启动时出现。这时候,常规的做法是调整日志级别,将相关类的日志级别设置为更高级别,比如INFO或WARN,这样DEBUG信息就不会被输出。同时,用户可能希望在不影响功能的前提下减少日志,所以需要确认调整日志级别是否会影响其他必要的调试信息。 然后,我需要回忆之前的回答中提到的优化方法,比如调整日志级别和排除自动配置。不过,排除自动配置可能不太适用于这种情况,因为这些Filter属于Spring Web模块的一部分,排除可能会导致功能缺失。因此,更安全的方法应该是调整日志级别。 接下来,要确定这些Filter类的包路径。例如,OrderedRequestContextFilter属于org.springframework.boot.web.servlet.filter包,OrderedCharacterEncodingFilter可能在org.springframework.boot.web.servlet.filter或者类似的路径下,而OrderedFormContentFilter同样属于类似的包。因此,可以通过设置这些包的日志级别为INFO或WARN来隐藏DEBUG日志。 另外,用户可能在开发环境中启用了全局的DEBUG日志,导致这些信息被输出。这时候,检查日志配置文件(如logback-spring.xml或application.properties中的logging.level.root设置)是否有全局的DEBUG配置,可能需要将root日志级别调整为INFO,但这样可能会隐藏其他有用的DEBUG信息。因此,更推荐的是针对特定的包调整日志级别,而不是全局设置。 最后,需要提醒用户注意,调整日志级别后要确保应用功能不受影响,尤其是在生产环境中,可能需要保留一些重要的日志信息。同时,提供具体的配置示例,帮助用户快速实现优化。</think>以下是针对你提供的**Filter配置类DEBUG日志**的优化方案: --- ### 一、**日志分析** ```log 2025-03-13 11:09:50.241 DEBUG 12148 --- [main] o.s.b.w.s.f.OrderedRequestContextFilter : Filter 'requestContextFilter' configured for use 2025-03-13 11:09:50.241 DEBUG 12148 --- [main] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter 'characterEncodingFilter' configured for use 2025-03-13 11:09:50.241 DEBUG 12148 --- [main] o.s.b.w.s.f.OrderedFormContentFilter : Filter 'formContentFilter' configured for use ``` 这些日志属于**Spring Web模块自动配置过滤器**的调试信息,可通过以下方式优化。 --- ### 二、**优化方法** #### 1. **调整特定包的日志级别** - **目标**:隐藏`Filter`相关类的`DEBUG`日志 - **配置示例**(`application.properties`): ```properties # 直接定位过滤器配置类包路径 logging.level.org.springframework.boot.web.servlet.filter=WARN # 覆盖更精确的类路径(可选) logging.level.org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter=ERROR logging.level.org.springframework.boot.web.servlet.filter.OrderedFormContentFilter=ERROR ``` #### 2. **全局日志级别控制** - **如果开发环境启用了全局DEBUG**: ```properties # 将全局日志级别设为INFO(慎用,可能影响其他调试) logging.level.root=INFO ``` #### 3. **关闭Web过滤器自动配置(不推荐)** - **极端场景下可排除自动配置类**: ```java @SpringBootApplication(exclude = { WebMvcAutoConfiguration.class, // 会禁用所有WebMvc相关自动配置 ServletWebServerFactoryAutoConfiguration.class }) ``` > **风险提示**:会导致Web功能失效,仅用于特殊场景。 --- ### 三、**优化效果对比** | **日志内容** | **优化前** | **优化后** | |--------------|------------|------------| | `Filter 'requestContextFilter' configured` | DEBUG可见 | 完全隐藏 | | `Filter 'characterEncodingFilter' configured` | DEBUG可见 | 完全隐藏 | | `Filter 'formContentFilter' configured` | DEBUG可见 | 完全隐藏 | --- ### 四、**原理说明** 1. **日志来源**: 这些日志来自Spring Boot的`WebMvcAutoConfiguration`,在初始化Web过滤器时打印调试信息。 2. **安全隐藏**: 调整日志级别**不会影响过滤器功能**,仅控制日志输出。 --- ### 五、**注意事项** 1. **保留必要日志**: 若出现字符编码问题,需临时恢复`OrderedCharacterEncodingFilter`的DEBUG日志。 2. **兼容性验证**: 修改后建议测试以下功能: - 请求参数解析(依赖`formContentFilter`) - 请求上下文传递(依赖`requestContextFilter`) 建议优先使用**方法1**调整日志级别,既安全又可精准控制输出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值