强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan
最近对代码进行统一日志处理,通过拦截器,打印请求日志,方便排查问题,通过拦截器取参数后遇到一个问题:Required request body is missing;在进行了相关实验后最终解决此问题。
一、问题复现
1、代码实现
(1)演示拦截控制器
/*
* Copyright (c) 2019. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:InterceptController.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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 map = new HashMap();
map.put("intLombok", interceptRequest.getIntLombok());
map.put("strLombok", interceptRequest.getStrLombok());
return WrapMapper.ok(map);
}
}
(2)所需的请求实体
/*
* Copyright (c) 2019. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:InterceptRequest.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:InterceptLog.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:WebMvcConfig.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:BodyReaderWrapper.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:BodyReaderFilter.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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. [email protected] All Rights Reserved.
* 项目名称:实战SpringBoot
* 类名称:ZhBootApplication.java
* 创建人:张晗
* 联系方式:[email protected]
* 开源地址: 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 Filters() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
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、原理可以让我们举一反三,实践可以让我们检验原理,并且加深对原理的理解;