详解在Spring MVC或Spring Boot中使用Filter打印请求参数问题
使用SpringMVC或SpringBoot中打印或记录日志一般使用AOP记录Request请求和Response响应参数,在不使用AOP的前提下,如果在Filter中打印日志,在打印或消费请求类型为Content-Type:application/json的请求时,会出现严重的问题。
在Spring体系中,过滤器的定义我们一般采用继承OncePerRequestFilter的方式,当然也可以使用原始的Filter。
错误写法一:
如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)
@Override protectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponse response,FilterChainfilterChain)throwsServletException,IOException{ filterChain.doFilter(request,response); printRequestLog(request); printResonseLog(response); }
运行测试后你会发现抛出如下异常:
java.io.IOException:Streamclosed
atorg.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359)~[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132)~[tomcat-embed-core-9.0.31.jar:9.0.31]
atsun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)~[na:1.8.0_191]
atsun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)~[na:1.8.0_191]
atsun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)~[na:1.8.0_191]
atjava.io.InputStreamReader.read(InputStreamReader.java:184)~[na:1.8.0_191]
atjava.io.BufferedReader.fill(BufferedReader.java:161)~[na:1.8.0_191]
atjava.io.BufferedReader.readLine(BufferedReader.java:324)~[na:1.8.0_191]
atjava.io.BufferedReader.readLine(BufferedReader.java:389)~[na:1.8.0_191]
atcom.micro.backend.filter.LoggingFilter.getBodyString(LoggingFilter.java:60)[classes/:na]
atcom.micro.backend.filter.LoggingFilter.doFilterInternal(LoggingFilter.java:49)[classes/:na]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)[spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)[tomcat-embed-core-9.0.31.jar:9.0.31]
atorg.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)[tomcat-embed-core-9.0.31.jar:9.0.31]
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)[na:1.8.0_191]
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)[na:1.8.0_191]
atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)[tomcat-embed-core-9.0.31.jar:9.0.31]
atjava.lang.Thread.run(Thread.java:748)[na:1.8.0_191]
错误写法二:
如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)
@Override protectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponse response,FilterChainfilterChain)throwsServletException,IOException{ printRequestLog(request); printResonseLog(response); filterChain.doFilter(request,response); }
运行测试后你会发现抛出如下异常:
org.springframework.http.converter.HttpMessageNotReadableException:Requiredrequestbodyismissing
遇到这样的问题你是不是有坐立不安、心烦意乱、百爪挠心的痛楚,不要着急,下面我给出一个解决方案。
首先我们使用装饰器模式,创建request和response两个包装类,如下:
importjavax.servlet.ReadListener; importjavax.servlet.ServletInputStream; importjavax.servlet.ServletRequest; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletRequestWrapper; importjava.io.*; importjava.nio.charset.Charset; /** *@Description:请求包装器 *@Author:liuliya *@CreateDate:2020/4/2910:00 */ publicclassRequestWrapperextendsHttpServletRequestWrapper{ privatefinalbyte[]body; publicRequestWrapper(HttpServletRequestrequest)throwsIOException{ super(request); body=getRequestBodyString(request).getBytes(Charset.defaultCharset()); } @Override publicBufferedReadergetReader()throwsIOException{ returnnewBufferedReader(newInputStreamReader(getInputStream())); } @Override publicServletInputStreamgetInputStream()throwsIOException{ finalByteArrayInputStreambais=newByteArrayInputStream(body); returnnewServletInputStream(){ @Override publicintread()throwsIOException{ returnbais.read(); } @Override publicbooleanisFinished(){ returnfalse; } @Override publicbooleanisReady(){ returnfalse; } @Override publicvoidsetReadListener(ReadListenerlistener){ } }; } publicStringgetRequestBodyString(ServletRequestrequest){ StringBuildersb=newStringBuilder(); InputStreaminputStream=null; BufferedReaderreader=null; try{ inputStream=request.getInputStream(); reader=newBufferedReader(newInputStreamReader(inputStream,Charset.defaultCharset())); Stringline; while((line=reader.readLine())!=null){ sb.append(line); } }catch(IOExceptione){ e.printStackTrace(); }finally{ if(inputStream!=null){ try{ inputStream.close(); }catch(IOExceptione){ e.printStackTrace(); } } if(reader!=null){ try{ reader.close(); }catch(IOExceptione){ e.printStackTrace(); } } } returnsb.toString(); } }
packagecom.micro.backend.filter.support; importorg.apache.commons.io.output.TeeOutputStream; importjavax.servlet.ServletOutputStream; importjavax.servlet.ServletResponse; importjavax.servlet.WriteListener; importjavax.servlet.http.HttpServletResponse; importjavax.servlet.http.HttpServletResponseWrapper; importjava.io.ByteArrayOutputStream; importjava.io.IOException; importjava.io.PrintWriter; /** *@Description:响应包装器 *@Author:liuliya *@CreateDate:2020/4/2910:00 */ publicclassResponseWrapperextendsHttpServletResponseWrapper{ privatefinalByteArrayOutputStreambos=newByteArrayOutputStream(); privatePrintWriterwriter=newPrintWriter(bos); publicResponseWrapper(HttpServletResponseresponse){ super(response); } @Override publicServletResponsegetResponse(){ returnthis; } @Override publicServletOutputStreamgetOutputStream()throwsIOException{ returnnewServletOutputStream(){ @Override publicbooleanisReady(){ returnfalse; } @Override publicvoidsetWriteListener(WriteListenerlistener){ } privateTeeOutputStreamtee=newTeeOutputStream(ResponseWrapper.super.getOutputStream(),bos); @Override publicvoidwrite(intb)throwsIOException{ tee.write(b); } }; } @Override publicPrintWritergetWriter()throwsIOException{ returnnewTeePrintWriter(super.getWriter(),writer); } publicbyte[]toByteArray(){ returnbos.toByteArray(); } }
packagecom.micro.backend.filter.support; importjava.io.PrintWriter; //PrintWriter是一种写入字符的一种操作类,可以写入字符,TeePrintWriter继承了他,主要功能是把原始的字符流copy到branch里面。 publicclassTeePrintWriterextendsPrintWriter{ PrintWriterbranch; publicTeePrintWriter(PrintWritermain,PrintWriterbranch){ super(main,true); this.branch=branch; } publicvoidwrite(charbuf[],intoff,intlen){ super.write(buf,off,len); super.flush(); branch.write(buf,off,len); branch.flush(); } publicvoidwrite(Strings,intoff,intlen){ super.write(s,off,len); super.flush(); branch.write(s,off,len); branch.flush(); } publicvoidwrite(intc){ super.write(c); super.flush(); branch.write(c); branch.flush(); } publicvoidflush(){ super.flush(); branch.flush(); } }
接下来创建最重要的LoggingFilter类,继承OncePerRequestFilter,或者直接继承Servlet中原始的Filter。
packagecom.micro.backend.filter; importcom.micro.backend.filter.support.RequestWrapper; importcom.micro.backend.filter.support.ResponseWrapper; importlombok.extern.slf4j.Slf4j; importorg.springframework.context.annotation.Configuration; importorg.springframework.web.filter.OncePerRequestFilter; importjavax.servlet.FilterChain; importjavax.servlet.ServletException; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjavax.servlet.http.HttpSession; importjava.io.IOException; importjava.io.UnsupportedEncodingException; /** *@Author:liuliya *@CreateDate:2020/4/2823:30 */ @Slf4j @Configuration publicclassLoggingFilterextendsOncePerRequestFilter{ privatestaticfinalStringREQUEST_PREFIX_NAME="Request请求:"; privatestaticfinalStringRESPONSE_PREFIX_NAME="Response请求:"; @Override protectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{ request=newRequestWrapper(request); response=newResponseWrapper(response); filterChain.doFilter(request,response); printRequestLog(request); printResponseLog((ResponseWrapper)response); } privatevoidprintRequestLog(finalHttpServletRequestrequest){ StringBuildermsg=newStringBuilder(); msg.append(REQUEST_PREFIX_NAME); HttpSessionsession=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(requestinstanceofRequestWrapper&&!isMultipart(request)&&!isBinaryContent(request)){ RequestWrapperrequestWrapper=(RequestWrapper)request; msg.append(";payload=").append(requestWrapper.getRequestBodyString(request)); } log.info(msg.toString()); } privatebooleanisBinaryContent(finalHttpServletRequestrequest){ if(request.getContentType()==null){ returnfalse; } returnrequest.getContentType().startsWith("image") ||request.getContentType().startsWith("video") ||request.getContentType().startsWith("audio"); } privatebooleanisMultipart(finalHttpServletRequestrequest){ returnrequest.getContentType()!=null &&request.getContentType().startsWith("multipart/form-data"); } privatevoidprintResponseLog(finalResponseWrapperresponse){ StringBuildermsg=newStringBuilder(); msg.append(RESPONSE_PREFIX_NAME); try{ msg.append(";payload=") .append(newString(response.toByteArray(),response.getCharacterEncoding())); }catch(UnsupportedEncodingExceptione){ log.warn("Failedtoparseresponsepayload",e); } log.info(msg.toString()); } }
参考以上我整理出的代码,你就会发现奇迹!!!
为什么要这么写呢,其本质是把请求流拷贝了一份,一个供filterChain向下传递,一个来做流的消费,再有一个就是运用装饰器模式的精髓所在。
到此这篇关于详解在SpringMVC或SpringBoot中使用Filter打印请求参数问题的文章就介绍到这了,更多相关SpringBootFilter打印请求参数内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!