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/**");/** 排除的拦截路径(此路径不拦截 */
}
}