强烈推荐一个大神的人工智能的教程:http://www.captainai.net/zhanghan
【前言】
最近对代码进行统一日志处理,通过拦截器,打印请求日志,方便排查问题,通过拦截器取参数后遇到一个问题:Required request body is missing;在进行了相关实验后最终解决此问题。
【解决问题】
一、问题复现
1、代码实现
(1)演示拦截控制器
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:InterceptController.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.controller; import com.zhanghan.zhboot.controller.request.InterceptRequest; import com.zhanghan.zhboot.util.wrapper.WrapMapper; import com.zhanghan.zhboot.util.wrapper.Wrapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import java.util.HashMap; import java.util.Map; @RestController @Api(value = "演示拦截控制器",tags = {"演示拦截控制器"}) public class InterceptController { @ApiOperation(value="演示拦截",tags = {"演示拦截控制器"}) @RequestMapping(value = "/intercept", method = RequestMethod.POST) public Wrapper intercept(@Valid @RequestBody InterceptRequest interceptRequest) { System.out.println(interceptRequest.toString()); Map<String, Object> map = new HashMap(); map.put("intLombok", interceptRequest.getIntLombok()); map.put("strLombok", interceptRequest.getStrLombok()); return WrapMapper.ok(map); } }
(2)所需的请求实体
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:InterceptRequest.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.controller.request; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @ApiModel("拦截器演示请求实体") @Data public class InterceptRequest { @ApiModelProperty(value = "整数类型") private Integer intLombok; @ApiModelProperty(value = "字符串类型") private String strLombok; }
(3)日志拦截器
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:InterceptLog.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.intercept; import io.micrometer.core.instrument.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.nio.charset.Charset; @Component @Order(10000) public class InterceptLog implements HandlerInterceptor { //日志操作 private static final Logger log = LoggerFactory.getLogger(InterceptLog.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { beforeRequestLogInfo(request); return true; } //记录请求日志 private void beforeRequestLogInfo(HttpServletRequest request) { ///获取请求方式 String method = request.getMethod(); ///请求地址 String url = request.getRequestURI(); ///获取请求参数 String paramsStr = ""; if ("POST".equals(method)) { ///获取post请求参数 paramsStr = this.getPostParm(request); } else if ("GET".equals(method)) { //Get方式请求参数 paramsStr = request.getQueryString(); } ///日志模板 String Template = "请求方式:%s ;请求地址:%s ;请求参数:%s 。"; ///记录日志 log.info(String.format(Template, method, url, paramsStr)); } private String getPostParm(HttpServletRequest request) { try { return getPostBodyParams(request.getInputStream(), request.getCharacterEncoding()); } catch (Exception e) { ///记录日志 log.error("getPostParm exception", e); return "getPostParm exception"; } } /** * 获取POST请求中Body参数 * * @param * @return 字符串 */ private String getPostBodyParams(ServletInputStream inputStream, String charset) throws Exception { try { String body = StreamUtils.copyToString(inputStream, Charset.forName(charset)); if (StringUtils.isEmpty(body)) { return ""; } return body; } catch (Exception e) { throw e; } } }
(4)项目启动时加载拦截器
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:WebMvcConfig.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.config; import com.zhanghan.zhboot.intercept.InterceptLog; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new InterceptLog()).addPathPatterns("/**"); super.addInterceptors(registry); } }
2、启动项目进行相关验证
(1)在浏览器访问swagger:http://localhost:8080/swagger-ui.html
(2)查看日志
2019-08-07 14:58:55.333 INFO 12320 --- [io-8080-exec-10] c.z.zhboot.intercept.InterceptLog : 请求方式:POST ;请求地址:/intercept ;请求参数:{ "intLombok": 0, "strLombok": "string" } 。 2019-08-07 14:58:57.743 ERROR 12320 --- [io-8080-exec-10] c.z.z.filter.GlobalExceptionHandler : HttpMessageNotReadableException=Required request body is missing: public com.zhanghan.zhboot.util.wrapper.Wrapper com.zhanghan.zhboot.controller.InterceptController.lombok(com.zhanghan.zhboot.controller.request.InterceptRequest) org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.zhanghan.zhboot.util.wrapper.Wrapper com.zhanghan.zhboot.controller.InterceptController.lombok(com.zhanghan.zhboot.controller.request.InterceptRequest) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:166) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE] at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:791) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) [tomcat-embed-core-9.0.13.jar:9.0.13] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.13.jar:9.0.13] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_144] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_144] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.13.jar:9.0.13] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
3、报错原理示意图
二、解决方案
1、解决方案一:去除控制器方法上的@RequestBody或将其设置为false
2、解决方案二:增加过滤器保存流
(1)增加Body封装类,用来保存requestBody
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:BodyReaderWrapper.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.intercept; import org.springframework.util.StreamUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; public class BodyReaderWrapper extends HttpServletRequestWrapper { //用于将流保存下来 private byte[] requestBody; public BodyReaderWrapper(HttpServletRequest request) throws IOException { super(request); requestBody = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); 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 readListener) { } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } }
(2)增加过滤器
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:BodyReaderFilter.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot.filter; import com.zhanghan.zhboot.intercept.BodyReaderWrapper; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter(filterName="bodyReaderFilter",urlPatterns="/*") public class BodyReaderFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // do nothing } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper=null; if(request instanceof HttpServletRequest) { requestWrapper=new BodyReaderWrapper((HttpServletRequest)request); } if(requestWrapper==null) { chain.doFilter(request, response); }else { chain.doFilter(requestWrapper, response); } } @Override public void destroy() { // do nothing } }
(3)启动类增加Bean,识别过滤器
/* * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved. * 项目名称:实战SpringBoot * 类名称:ZhBootApplication.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhboot; import com.zhanghan.zhboot.filter.BodyReaderFilter; import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class}) public class ZhBootApplication { public static void main(String[] args) { SpringApplication.run(ZhBootApplication.class, args); } @Bean public FilterRegistrationBean<BodyReaderFilter> Filters() { FilterRegistrationBean<BodyReaderFilter> registrationBean = new FilterRegistrationBean<BodyReaderFilter>(); registrationBean.setFilter(new BodyReaderFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setName("bodyReaderFilter"); return registrationBean; } }
(4)修改拦截器
(5)原理分析示意图
3、启动项目进行相关验证
(1)在浏览器访问swagger:http://localhost:8080/swagger-ui.html
(2)查看日志,项目正常记录了访问日志
2019-08-07 15:33:10.738 INFO 10232 --- [nio-8080-exec-2] c.z.zhboot.intercept.InterceptLog : 请求方式:POST ;请求地址:/intercept ;请求参数:{ "intLombok": 0, "strLombok": "string" } 。 InterceptRequest(intLombok=0, strLombok=string)
三、项目地址
https://github.com/dangnianchuntian/springboot
【总结】
1、实践和理论是相辅相成;
2、原理可以让我们举一反三,实践可以让我们检验原理,并且加深对原理的理解;