spring:拦截器(HandlerInterceptor)中获取POST请求参数

spring的拦截器(HandlerInterceptor)机制工作在收到HTTP请求之后,参数解析(反序列化)之前阶段。
这个阶段,因为请求方法的参数还没有被反序列化,所以要想获取HTTP的请求参数,就要自己从HttpServletRequest中获取。
对于GET方法,这个很简单,调用HttpServletRequest.getParameter就可以得到。
对于POST方法要从请求内容里获取,即从HttpServletRequest里的InputStream里获取到,然后解析为JSON就可以了。
  但是,InputStream是单向的,一次性的,如果在拦截器中直接调用HttpServletRequest.getInputStream()获取了数据,后续方法调用解析参数时再调用HttpServletRequest.getInputStream()读取就会失败,因为输入流结束了。
  所以拦截器(HandlerInterceptor)中获取POST请求参数本身并不是问题,问题就是要解决HttpServletRequest的输入流能被多次读取问题。
  解决的思路就是将HttpServletRequest基于HttpServletRequestWrapper封装起来,实现javax.servlet.Filter接口,用封装后的HttpServletRequestWrapper替换掉整个请求链路中的HTTP请求实例(HttpServletRequest)。
  要实现的HttpServletRequestWrapper类负责将HttpServletRequest中的InputStream完整数据读取出来保存下来,以便重复读取。
  这样才能确保HttpServletRequest中的InputStream可以多次被读取。
  
以下为HTTP请求过滤器过滤器(javax.servlet.Filter)的实现,包含了HttpServletRequestWrapper封装类实现

BufferedRequestFilter.java

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.stereotype.Component;
import com.google.common.base.Charsets;

/**
 * HTTP请求过滤器<br>
 * 将{@link HttpServletRequest}封装为{@link BufferedRequestWrapper}替换掉整个请求链路中的Request。
 * 
 * @author guyadong
 *
 */
@Component
@WebFilter(urlPatterns = "/*", filterName = "bufferedRequestFilter")
public class BufferedRequestFilter implements Filter {

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

	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		if (servletRequest instanceof HttpServletRequest && !(servletRequest instanceof BufferedRequestWrapper)) {
			/** 将请求封装为BufferedRequestWrapper传递下去 */
			HttpServletRequestWrapper requestWrapper = new BufferedRequestWrapper((HttpServletRequest) servletRequest);
			filterChain.doFilter(requestWrapper, servletResponse);
		} else {
			filterChain.doFilter(servletRequest, servletResponse);
		}
	}

	@Override
	public void destroy() {

	}

	/**
	 * 基于 HttpServletRequestWrapper封装,允许 HTTP请求中的{@link InputStream}被重复读取
	 */
	private static class BufferedRequestWrapper extends HttpServletRequestWrapper {
		private final byte[] buffer;

		public BufferedRequestWrapper(HttpServletRequest request) throws IOException {
			super(request);
			/** 读取InputSteam中的数据保存到 buffer */
			InputStream is = request.getInputStream();
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			byte[] buf = new byte[1024];
			int readCount;
			while ((readCount = is.read(buf)) > 0) {
				os.write(buf, 0, readCount);
			}
			buffer = os.toByteArray();
		}

		@Override
		public ServletInputStream getInputStream() {
			return new BufferedServletInputStream(buffer);
		}

		@Override
		public BufferedReader getReader() throws IOException {
			return new BufferedReader(new InputStreamReader(this.getInputStream(), Charsets.UTF_8));
		}

	}

	private static class BufferedServletInputStream extends ServletInputStream {
		private final ByteArrayInputStream inputStream;

		public BufferedServletInputStream(byte[] buffer) {
			this.inputStream = new ByteArrayInputStream(buffer);
		}

		@Override
		public int read() throws IOException {
			return inputStream.read();
		}

		@Override
		public boolean isFinished() {
			return inputStream.available() == 0;
		}

		@Override
		public boolean isReady() {
			return !isFinished();
		}

		@Override
		public void setReadListener(ReadListener readListener) {
			// Not implemented
		}
	}
}

有了上面WapperRequestFilter的支持,HandlerInterceptor中就可以正常读HttpServletRequest的InputStream,而不影响后续的参数反序列化了。
拦截器读取HTTP 请求参数示例:

import java.lang.reflect.Method;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

/**
 * HTTP请求拦截器,实现在HTTP请求解析之前获取POST请求中的参数
 */
@Configuration
public class TokenExtractInterceptor implements HandlerInterceptor,WebMvcConfigurer{
	public TokenExtractInterceptor() {
	}
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		JSONObject body = JSON.parseObject(request.getInputStream(),JSONObject.class);
		Systemm.out.printf("body:%s\n",JSON.toJSONString(body, true));
		return true;
	}
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(this)
				.addPathPatterns("/**")  /** 添加拦截路径 */
				.excludePathPatterns("/error","/swagger-resources/**","/swagger/**", "/swagger2/**");/** 排除的拦截路径(此路径不拦截 */
	}
}
<think>我们正在讨论如何在Java中通过拦截器获取请求参数。根据引用内容,主要涉及在SpringBoot或SpringMVC中使用拦截器获取HTTP请求参数的方法。关键点:1.拦截器需要实现HandlerInterceptor接口,并重写preHandle方法。2.在preHandle方法中,我们可以通过HttpServletRequest对象获取请求参数。3.获取参数的方式有两种:一种是获取URL中的查询参数(即?后的参数),另一种是获取请求体(如POST请求的表单数据或JSON)。4.需要注意的是,请求体(InputStream)只能被读取一次,如果重复读取可能会导致问题(如引用[3]中提到的Requiredrequestbodyismissing错误)。根据引用[1]和[2],我们可以通过以下方式获取参数:-对于查询参数:使用request.getParameterMap()或request.getParameterNames()等。-对于请求体:如果是表单数据,同样可以使用request.getParameter系列方法;如果是JSON等数据,则需要从InputStream读取,但要注意不能重复读取。引用[3]提醒我们,在拦截器中读取请求体时,如果后续还需要使用(例如在Controller中),我们需要将请求体内容缓存下来,以便多次读取。因此,我们可以编写一个拦截器,在preHandle方法中获取请求参数并打印或处理。下面是一个示例:示例代码:我们将创建一个拦截器,用于获取GET请求的查询参数POST请求的表单参数。对于JSON等请求体,我们可以通过包装HttpServletRequest来实现多次读取(使用ContentCachingRequestWrapper)。但是注意:在拦截器中直接读取请求体可能会影响性能,且需要谨慎处理。这里我们主要展示如何获取查询参数和表单参数,对于JSON请求体,我们使用缓存的方式。步骤:1.创建拦截器类,实现HandlerInterceptor接口。2.在preHandle方法中,使用HttpServletRequest获取参数。3.注册拦截器。我们先展示一个简单的版本,只获取查询参数和表单参数(不处理JSON请求体):```javaimportjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.springframework.web.servlet.HandlerInterceptor;publicclassParamsInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//获取所有请求参数(包括查询参数和表单参数)System.out.println("请求参数:");request.getParameterMap().forEach((key,value)->{System.out.println(key+":"+String.join(",",value));});returntrue;}}```但是,上述代码无法获取请求体中的JSON数据。为了能够获取JSON请求体,我们需要将请求包装成可缓存的请求,然后读取缓存的内容。我们可以使用ContentCachingRequestWrapper来缓存请求体。但是注意,这个类在Spring中提供,我们需要在拦截器中对原始请求进行包装。然而,在拦截器中,我们通常不会修改请求对象。因此,我们可以通过过滤器(Filter)来包装请求,然后在拦截器中使用包装后的请求。但是,问题要求使用拦截器,所以我们这里有两种选择:1.在拦截器中,如果请求是JSON类型,我们可以通过读取InputStream来获取请求体,但这样会消耗掉InputStream,导致后续无法读取。所以不推荐。2.使用ContentCachingRequestWrapper,但需要在过滤器中进行包装,这样拦截器就可以使用包装后的请求。这里我们假设已经有一个过滤器做了包装。由于引用[3]提到了读取两次的问题,我们采用第二种方式,即通过过滤器包装请求。因此,我们首先创建一个过滤器,将请求包装为ContentCachingRequestWrapper:```javaimportjavax.servlet.*;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;importorg.springframework.web.util.ContentCachingRequestWrapper;publicclassCachingRequestBodyFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{HttpServletRequesthttpServletRequest=(HttpServletRequest)request;ContentCachingRequestWrapperwrappedRequest=newContentCachingRequestWrapper(httpServletRequest);chain.doFilter(wrappedRequest,response);//将包装后的请求传递下去}}```然后,在拦截器中,我们就可以从ContentCachingRequestWrapper中获取请求体:```javaimportorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.util.ContentCachingRequestWrapper;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.nio.charset.StandardCharsets;publicclassParamsInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//如果请求已经被包装为ContentCachingRequestWrapperif(requestinstanceofContentCachingRequestWrapper){ContentCachingRequestWrapperwrappedRequest=(ContentCachingRequestWrapper)request;//获取查询参数System.out.println("QueryStringParameters:");request.getParameterMap().forEach((key,value)->{System.out.println(key+":"+String.join(",",value));});//获取请求体byte[]contentAsByteArray=wrappedRequest.getContentAsByteArray();if(contentAsByteArray.length>0){Stringbody=newString(contentAsByteArray,StandardCharsets.UTF_8);System.out.println("RequestBody:"+body);}}else{//如果没有包装,只获取查询参数和表单参数System.out.println("RequestParameters:");request.getParameterMap().forEach((key,value)->{System.out.println(key+":"+String.join(",",value));});}returntrue;}}```最后,我们需要注册过滤器和拦截器。在SpringBoot中注册过滤器:```javaimportorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassFilterConfig{@BeanpublicFilterRegistrationBean<CachingRequestBodyFilter>filterRegistrationBean(){FilterRegistrationBean<CachingRequestBodyFilter>registrationBean=newFilterRegistrationBean<>();registrationBean.setFilter(newCachingRequestBodyFilter());registrationBean.addUrlPatterns("/*");returnregistrationBean;}}```注册拦截器:```javaimportorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassInterceptorConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newParamsInterceptor());}}```这样,我们就可以在拦截器获取请求参数(包括查询参数请求体)了。注意:ContentCachingRequestWrapper是Spring提供的,所以需要引入spring-web模块。总结:1.使用过滤器将请求包装为ContentCachingRequestWrapper,以便多次读取请求体。2.在拦截器中,通过ContentCachingRequestWrapper的getContentAsByteArray方法获取请求体内容。3.同时,通过getParameterMap获取查询参数和表单参数。但是,需要注意的是,getParameterMap只能获取到查询参数和表单参数(application/x-www-form-urlencoded或multipart/form-data),对于JSON(application/json)等,getParameterMap是获取不到的,所以需要从请求体中读取。因此,我们在拦截器中同时打印了查询参数请求体,这样就能覆盖大多数情况。引用说明:引用[1]和[2]提到了在拦截器获取请求参数,引用[3]提醒了请求体只能读取一次的问题,因此我们采用包装请求的方式解决。注意:这个示例可能会影响性能,因为缓存请求体会消耗内存,特别是大文件上传时。因此,在实际应用中,需要根据情况决定是否缓存请求体。以上就是在Java中通过拦截器获取请求参数的示例。§§相关问题§§1.拦截器和过滤器有什么区别?2.除了拦截器,还有哪些方式可以获取HTTP请求参数?3.如何解决拦截器中读取请求体导致后续Controller无法读取的问题?</think>在 Java 中通过拦截器获取 HTTP 请求参数的实现步骤如下,结合 Spring Boot 示例代码说明: ### 实现步骤 1. **创建拦截器类**:实现 `HandlerInterceptor` 接口 2. **重写 `preHandle` 方法**:在请求处理前获取参数 3. **处理不同参数类型**: - **查询参数**:`request.getParameterMap()` - **表单参数**:`request.getParameter()` - **JSON 请求体**:使用缓存请求包装器 4. **注册拦截器**:配置拦截规则 ### 完整示例代码 ```java import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; public class ParamsInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 1. 获取URL查询参数 (?key=value) Map<String, String[]> queryParams = request.getParameterMap(); System.out.println("URL参数: " + queryParams); // 2. 获取表单参数 (application/x-www-form-urlencoded) String formParam = request.getParameter("formKey"); System.out.println("表单参数: " + formParam); // 3. 获取JSON请求(application/json) if ("POST".equalsIgnoreCase(request.getMethod()) && request.getContentType() != null && request.getContentType().contains("application/json")) { // 使用缓存包装器解决多次读取问题[^3] CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(request); try { String jsonBody = new String( cachedRequest.getInputStream().readAllBytes(), StandardCharsets.UTF_8 ); System.out.println("JSON请求: " + jsonBody); } catch (IOException e) { e.printStackTrace(); } return true; // 继续执行后续处理 } return true; } } ``` ### 关键点说明 1. **请求体缓存包装器**(解决多次读取问题) ```java import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); this.cachedBody = request.getInputStream().readAllBytes(); } @Override public ServletInputStream getInputStream() { return new CachedBodyServletInputStream(this.cachedBody); } // 内部实现 static class CachedBodyServletInputStream extends ServletInputStream { private final ByteArrayInputStream buffer; public CachedBodyServletInputStream(byte[] body) { this.buffer = new ByteArrayInputStream(body); } // 实现必要方法... } } ``` 2. **注册拦截器** ```java import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ParamsInterceptor()) .addPathPatterns("/api/**"); // 拦截指定路径 } } ``` ### 注意事项 1. **请求体只能读取一次**:未缓存时读取后 InputStream 会关闭,导致后续处理报错 `Required request body is missing`[^3] 2. **性能影响**:大文件上传时谨慎使用缓存 3. **参数类型判断**:根据 `Content-Type` 头区分表单/JSON 4. **拦截器与过滤器区别**:拦截器在 Controller 前执行,过滤器在更早阶段处理 > 最佳实践:对于 JSON 请求体,建议在过滤器层进行缓存包装,再在拦截器获取内容[^2][^3]。
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值