加打印语句,将请求参数打印出来。后面想一想,之后可能还会遇到这样的状况,若是每次遇到,我都去对应的方法中加日志打印,就变成重复工做。而且日志打印跟咱们的业务自己没有任何关系。json
记录日志网上主要有三种方法:数组
我选择了filter。为何选择它,由于我以为它相对于定义切点,而后切点先后处理来讲,更加方便;相对于 interceptor, 我更加熟悉这种方式。缓存
定义一个 LogFilter 。 里面对 HttpServletRequest 进行拦截,根据对应的 content-type 解析请求参数。主要代码以下app
/** * 功能描述: 打印请求参数 * @author lkb * @date 2020/5/6 * @param * @return */ @Slf4j public class LogFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //日志 doLog(request, response); // 将request 传到下一个Filter filterChain.doFilter(request, response); } private void doLog(HttpServletRequest request, HttpServletResponse response){ // 输出请求体 log.info("request. uri = {}, method = {}, requestParam = {}", request.getRequestURI(), request.getMethod(), getRequestParam(request)); //todo 返回结果也能够进行处理 } private String getRequestParam(HttpServletRequest request){ String requestParam = ""; String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE); try { if(StringUtils.isNotEmpty(requestContentType)){ if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) { // xml json requestParam = getRequestBody(request); } else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) { // 文件表单提交 requestParam = getFormParam(request); }else if(requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)){ // 普通表单提交 requestParam = toJson(request.getParameterMap()); } }else{ // 默认普通表单提交 requestParam = toJson(request.getParameterMap()); } }catch (Exception e){ log.error("getRequestParam error"); log.error(e.getMessage(),e); } return requestParam; } ... }
而后,注册这个filteride
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean logFilter() { final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); final LogFilter logFilter = new LogFilter(); filterRegistrationBean.setFilter(logFilter); return filterRegistrationBean; } }
上面两步以后,重启项目,能够看到请求过来后会打印出请求的uri、method、param 。this
原本觉得这样就万事大吉了。可是事实并非如此。加上上面代码后会发现,再controller 加上的 @requestBody 没有效果,取不到任何数据,并抛出异常,告诉咱们请求已经被读取过。 为何呢?spa
缘由很简单。由于在 doLog 中获取请求参数的时候,咱们已经将请求的 inputStream 给读取了。读取inputStream 时有一个offset,它表示你从哪里开始读取输入流。由于咱们读取了一遍 inputStream,因此offset已经在流的最末端了。咱们再去读取,就会发现没有东西能够读了。若是想重复读取 inputStream 就须要每次读取后重置 offset 的值。日志
固然为了方便,我并无去从新inputStream 中的reset 方法。而是选择,在读取请求后,将请求缓存起来。code
首先,BufferedServletInputStream 继承自 ServletInputStream。orm
public class BufferedServletInputStream extends ServletInputStream { private ByteArrayInputStream inputStream; public BufferedServletInputStream(byte[] buffer) { this.inputStream = new ByteArrayInputStream( buffer ); } @Override public int available(){ return inputStream.available(); } @Override public int read(){ return inputStream.read(); } @Override public int readLine(byte[] b, int off, int len){ return inputStream.read( b, off, len ); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }
而后,BufferedServletRequestWrapper 继承 HttpServletRequestWrapper。
@Slf4j public class BufferedServletRequestWrapper extends HttpServletRequestWrapper { private byte[] buffer; public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); InputStream is = request.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buff[] = new byte[1024]; int read; while ((read = is.read(buff)) > 0) { baos.write(buff, 0, read); } this.buffer = baos.toByteArray(); } @Override public ServletInputStream getInputStream() { return new BufferedServletInputStream(this.buffer); } }
里面使用一个 byte[] buffer 数组将请求缓存起来。
最后,在 LogFilter 中 doLog 前,对请求进行包装。
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 备份HttpServletRequest request = new BufferedServletRequestWrapper(request); //日志 doLog(request, response); // 将request 传到下一个Filter filterChain.doFilter(request, response); }
通过上诉处理,咱们就能够愉快地用日志记录请求参数了。
最后总结一下:
1. 记录请求参很多天志的方式最好采用切面的思想
2. inputStream 默认只能读取一次,屡次读取要从新处理inputStream
3. @requestBody 的原理能够了解一下