使用Spring MVC或Spring Boot中打印或记录日志一般使用AOP记录Request请求和Response响应参数,在不使用AOP的前提下,如果在Filter中打印日志,在打印或消费请求类型为Content-Type:application/json的请求时,会出现严重的问题。
在Spring体系中,过滤器的定义我们一般采用继承OncePerRequestFilter的方式,当然也可以使用原始的Filter。
错误写法一:
如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { filterChain.doFilter(request, response); printRequestLog(request); printResonseLog(response); }
运行测试后你会发现抛出如下异常:
java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.31.jar:9.0.31]
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) ~[na:1.8.0_191]
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) ~[na:1.8.0_191]
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) ~[na:1.8.0_191]
at java.io.InputStreamReader.read(InputStreamReader.java:184) ~[na:1.8.0_191]
at java.io.BufferedReader.fill(BufferedReader.java:161) ~[na:1.8.0_191]
at java.io.BufferedReader.readLine(BufferedReader.java:324) ~[na:1.8.0_191]
at java.io.BufferedReader.readLine(BufferedReader.java:389) ~[na:1.8.0_191]
at com.micro.backend.filter.LoggingFilter.getBodyString(LoggingFilter.java:60) [classes/:na]
at com.micro.backend.filter.LoggingFilter.doFilterInternal(LoggingFilter.java:49) [classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639) [tomcat-embed-core-9.0.31.jar:9.0.31]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.31.jar:9.0.31]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.31.jar:9.0.31]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
错误写法二:
如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { printRequestLog(request); printResonseLog(response); filterChain.doFilter(request, response); }
运行测试后你会发现抛出如下异常:
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
遇到这样的问题你是不是有坐立不安、心烦意乱、百爪挠心的痛楚,不要着急,下面我给出一个解决方案。
首先我们使用装饰器模式,创建request和response两个包装类,如下:
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; /** * @Description: 请求包装器 * @Author: liuliya * @CreateDate: 2020/4/29 10:00 */ public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); body = getRequestBodyString(request).getBytes(Charset.defaultCharset()); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } }; } public String getRequestBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset())); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } }
package com.micro.backend.filter.support; import org.apache.commons.io.output.TeeOutputStream; import javax.servlet.ServletOutputStream; import javax.servlet.ServletResponse; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; /** * @Description: 响应包装器 * @Author: liuliya * @CreateDate: 2020/4/29 10:00 */ public class ResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); private PrintWriter writer = new PrintWriter(bos); public ResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletResponse getResponse() { return this; } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener listener) { } private TeeOutputStream tee = new TeeOutputStream(ResponseWrapper.super.getOutputStream(), bos); @Override public void write(int b) throws IOException { tee.write(b); } }; } @Override public PrintWriter getWriter() throws IOException { return new TeePrintWriter(super.getWriter(), writer); } public byte[] toByteArray() { return bos.toByteArray(); } }
package com.micro.backend.filter.support; import java.io.PrintWriter; //PrintWriter是一种写入字符的一种操作类,可以写入字符,TeePrintWriter继承了他,主要功能是把原始的字符流copy到branch里面。 public class TeePrintWriter extends PrintWriter { PrintWriter branch; public TeePrintWriter(PrintWriter main, PrintWriter branch) { super(main, true); this.branch = branch; } public void write(char buf[], int off, int len) { super.write(buf, off, len); super.flush(); branch.write(buf, off, len); branch.flush(); } public void write(String s, int off, int len) { super.write(s, off, len); super.flush(); branch.write(s, off, len); branch.flush(); } public void write(int c) { super.write(c); super.flush(); branch.write(c); branch.flush(); } public void flush() { super.flush(); branch.flush(); } }
接下来创建最重要的LoggingFilter类,继承OncePerRequestFilter,或者直接继承Servlet中原始的Filter。
package com.micro.backend.filter; import com.micro.backend.filter.support.RequestWrapper; import com.micro.backend.filter.support.ResponseWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.UnsupportedEncodingException; /** * @Author: liuliya * @CreateDate: 2020/4/28 23:30 */ @Slf4j @Configuration public class LoggingFilter extends OncePerRequestFilter { private static final String REQUEST_PREFIX_NAME = "Request请求: "; private static final String RESPONSE_PREFIX_NAME = "Response请求: "; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request = new RequestWrapper(request); response = new ResponseWrapper(response); filterChain.doFilter(request, response); printRequestLog(request); printResponseLog((ResponseWrapper) response); } private void printRequestLog(final HttpServletRequest request) { StringBuilder msg = new StringBuilder(); msg.append(REQUEST_PREFIX_NAME); HttpSession session = request.getSession(false); if (session != null) { msg.append("sessionId = ").append(session.getId()).append("; "); } if (request.getMethod() != null) { msg.append("method = ").append(request.getMethod()).append("; "); } if (request.getContentType() != null) { msg.append("contentType = ").append(request.getContentType()).append("; "); } msg.append("uri = ").append(request.getRequestURI()); if (request.getQueryString() != null) { msg.append('?').append(request.getQueryString()); } if (request instanceof RequestWrapper && !isMultipart(request) && !isBinaryContent(request)) { RequestWrapper requestWrapper = (RequestWrapper) request; msg.append("; payload = ").append(requestWrapper.getRequestBodyString(request)); } log.info(msg.toString()); } private boolean isBinaryContent(final HttpServletRequest request) { if (request.getContentType() == null) { return false; } return request.getContentType().startsWith("image") || request.getContentType().startsWith("video") || request.getContentType().startsWith("audio"); } private boolean isMultipart(final HttpServletRequest request) { return request.getContentType() != null && request.getContentType().startsWith("multipart/form-data"); } private void printResponseLog(final ResponseWrapper response) { StringBuilder msg = new StringBuilder(); msg.append(RESPONSE_PREFIX_NAME); try { msg.append("; payload = ") .append(new String(response.toByteArray(), response.getCharacterEncoding())); } catch (UnsupportedEncodingException e) { log.warn("Failed to parse response payload", e); } log.info(msg.toString()); } }
参考以上我整理出的代码,你就会发现奇迹!!!
为什么要这么写呢,其本质是把请求流拷贝了一份,一个供filterChain向下传递,一个来做流的消费,再有一个就是运用装饰器模式的精髓所在。
到此这篇关于详解在Spring MVC或Spring Boot中使用Filter打印请求参数问题的文章就介绍到这了,更多相关SpringBoot Filter打印请求参数内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!
本文向大家介绍学习SpringMVC——如何获取请求参数详解,包括了学习SpringMVC——如何获取请求参数详解的使用技巧和注意事项,需要的朋友参考一下 @RequestParam,你一定见过;@PathVariable,你肯定也知道;@QueryParam,你怎么会不晓得?!还有你熟悉的他(@CookieValue)!她(@ModelAndView)!它(@ModelAttribute)!没错
本文向大家介绍SpringMVC post请求中文乱码问题解决,包括了SpringMVC post请求中文乱码问题解决的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了SpringMVC post请求中文乱码问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 我们在页面难免提交一些中文数据给后台处理,但是发现后台拿到的数据乱码,可以
本文向大家介绍SpringBoot拦截器Filter的使用方法详解,包括了SpringBoot拦截器Filter的使用方法详解的使用技巧和注意事项,需要的朋友参考一下 前言: 最新Servlet 3.0拦截器的使用 1.pom.xml添加需要使用的依赖 2.添加Filter拦截器 3.添加测试控制器 4.添加启动类 5.添加拦截后调整的页面filter.html 6.右键项目Run As启动项目,
本文向大家介绍详解在springmvc中解决FastJson循环引用的问题,包括了详解在springmvc中解决FastJson循环引用的问题的使用技巧和注意事项,需要的朋友参考一下 我们先来看一个例子: 输出: 可以看到,这个json如果发到前端是无法使用的,幸好FastJson提供了解决办法,我们来看下,解决办法为禁用循环引用检测,代码如下: 输出如下: 问题是如果我们在spring mvc中
本文向大家介绍SpringMVC Controller解析ajax参数过程详解,包括了SpringMVC Controller解析ajax参数过程详解的使用技巧和注意事项,需要的朋友参考一下 在使用ajax发送请求时,如果发送的JSON数据的参数是一个类中的不同属性,在Controller类的方法中使用@RequestBody Object obj会直接封装进obj对象中 例如: 前端部分代码 J
你好,我有Spring引导2项目,我正在使用骆驼的路线。 我有一个骆驼Restendpoint和一个骆驼路线: 我也尝试过使用spring boot rest controller,但在获取.To(${url})中的参数值时仍然存在问题 编辑:我已经编辑了路线