众所周知,request.getInputStream()只能调一次。如果希望在请求进入Controller之前统一打印请求参数(拦截器或过滤器),又不影响业务,我们只能将获取到的输入流缓存起来,后续都从缓存中获取即可。
首先,自定义一个ServletInputStream
package com.cjs.example.log.filter;import javax.servlet.ReadListener;import javax.servlet.ServletInputStream;import java.io.ByteArrayInputStream;import java.io.IOException;/** * @Author: ChengJianSheng * @Date: 2023/3/6 */public class CustomServletInputStream extends ServletInputStream { private ByteArrayInputStream inputStream; public CustomServletInputStream(byte[] body) { this.inputStream = new ByteArrayInputStream(body); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return inputStream.read(); }}
然后,自定义一个HttpServletRequestWrapper
package com.cjs.example.log.filter;import org.apache.commons.io.IOUtils;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.*;/** * @Author: ChengJianSheng * @Date: 2023/3/6 */public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = IOUtils.toByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { return new CustomServletInputStream(body); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); }}
接下来,写一个过滤器,在过滤器中打印请求参数
package com.cjs.example.log.filter;import org.apache.commons.io.IOUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;/** * @Author: ChengJianSheng * @Date: 2023/3/6 */public class LogFilter implements Filter { private final static Logger logger = LoggerFactory.getLogger(LogFilter.class); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { CustomHttpServletRequestWrapper requestWrapper = new CustomHttpServletRequestWrapper((HttpServletRequest) servletRequest); printLog(requestWrapper); filterChain.doFilter(requestWrapper, servletResponse); } private void printLog(CustomHttpServletRequestWrapper requestWrapper) throws IOException { logger.info("请求URL: {}", requestWrapper.getRequestURL()); String method = requestWrapper.getMethod(); if ("GET".equalsIgnoreCase(method)) { logger.info("请求参数: {}", requestWrapper.getQueryString()); } else if ("POST".equalsIgnoreCase(method) && "application/json".equalsIgnoreCase(requestWrapper.getContentType())) { String body = IOUtils.toString(requestWrapper.getInputStream(), requestWrapper.getCharacterEncoding()); logger.info("请求参数: {}", body); } }}
请求经过过滤器的时候,首先在构造CustomHttpServletRequestWrapper的时候将请求中的InputStream转成字节数字缓存到内存中,然后后面每次再getInputStream()的时候都从缓存中取出内容并返回一个新的ServletInputStream
最后,定义一个配置类
package com.cjs.example.log.config;import com.cjs.example.log.filter.LogFilter;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @Author: ChengJianSheng * @Date: 2023/3/6 */@Configurationpublic class CustomLogAutoConfiguration { @Bean @ConditionalOnMissingBean public LogFilter logFilter() { return new LogFilter(); }}
在resources/META-INF/spring.factories中新增一行自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.cjs.example.log.config.CustomLogAutoConfiguration