使用springboot实现用两个@RequestBody修饰入参
最近遇到Controller中需要多个@RequestBody的情况,但是发现并不支持这种写法,
这样导致
1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收;
2、多个对象需要封装到一个对象里才可以用@RequestBody接收。
结论:@RequestBody最多只能有一个,而@RequestParam()可以有多个。
但是可以自己实现多个@RequestBody。
多个@RequestBody验证(Map类型)
这边来个测试例子:
发起请求:
结果直接报错:
Required request body is missing: public java.util.Map<java.lang.String, java.lang.Object> com.xy.controller.TestController.tt(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.Object>)
说明确实不能用两个@RequestBody.
多个@RequestBody验证(Bean类型)
两个bean入参:
报错:
跟踪源码,看看为什么不行?
重要源码节点:获取方法参数值
InvocableHandlerMethod.getMethodArgumentValues
我们看看这里能获取到两个入参类型,是我们的bean没错
并且这里也能获取到我们给的值:
接下来报错出现了,当要给第二个参数填值的时候,报错了:
报错在这里:可以看到报错信息了。
RequestResponseBodyMethodProcessor.readWithMessageConverters
不能有两个@RequestBody的原理
http请求后会有一个输入流InputStream,我们猜测,它是流读取完后,因为流需求关闭,所以当要对第二个参数进行填值的时候,就读取不到内容了。
源码进入到下面这个位置,读取内容为空:
支持两个@RequestBody解决方案
请求实现重写:
RepeatedlyRequestWrapper.java
import cn.hutool.core.io.IoUtil;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
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 RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
body = IoUtil.readBytes(request.getInputStream(), false);
}
@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 int available() throws IOException {
return body.length;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
过滤器:
RequestFilter.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
}
过滤器配置:
FilterConfig.java
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RequestFilter());
registration.addUrlPatterns("/*");
registration.setName("requestFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}
再次请求:
已经可以了!!!
但是在接收请求参数的时候如果需要同时接收两个类,也建议封装为一个DTO对象进行接收!