Spring-MultiRequestBody
Spring-MultiRequestBody copied to clipboard
无法支持 @Validated 注解
@PostMapping("/register")
public Result register(@Validated(UserInfoModel.RegisterAction.class) @MultiRequestBody UserInfoModel userInfoModel, @MultiRequestBody @NotBlank String code){
System.out.println(userInfoModel);
return new Result(0,"test");
}
在方法中添加 @Validated 注解 无法执行验证,有什么解决办法吗?
这个问题我今天找到了解决方案,记录一下 在 RequestBody 中有继承 AbstractMessageConverterMethodArgumentResolver ,里面有两个方法
/**
* Validate the binding target if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter descriptor
* @since 4.1.5
* @see #isBindExceptionRequired
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
/**
* Whether to raise a fatal bind exception on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method parameter descriptor
* @return {@code true} if the next method argument is not of type {@link Errors}
* @since 4.1.5
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
将它们复制到 MultiRequestBodyArgumentResolver 类中,然后在 resolveArgument 方法中,需要将之前获取参数的代码提取出来单独放在一个方法,最后:
/**
* 参数解析,利用fastjson
* 注意:非基本类型返回null会报空指针异常,要通过反射或者JSON工具类创建一个空对象
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessage(webRequest,parameter);
String name = parameter.getParameterName();
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return arg;
}
这样就可以 @MultiRequestBody 和 @Validated 一起使用了
@PostMapping("/register")
public Result register(@Validated(UserInfoModel.RegisterAction.class) @MultiRequestBody UserInfoModel userInfoModel,@MultiRequestBody @NotBlank String code){
System.out.println(userInfoModel);
System.out.println(code);
return new Result(0,"test");
}
我也尝试过去继承 AbstractMessageConverterMethodArgumentResolver 这个类,但是里面要配置消息解析器还有返回值等很多东西,水平有限很多也不明白,所以单独把那两个方法提取出来比较好用
还有 @RequestBody 和 @MultiRequestBody 一起使用会报错,因为使用这两个注解会调用 getInputStream 获取 HttpServletRequest 中传过来的数据,而 getInputStream 只能读取一次,然后就标记为不可读取了,两个注解一起使用,第二次去 getInputStream 的时候就会报错。解决办法:
先将第一次读取到 request 中的值记录下来:
package com.itellyou.api.handler;
import org.apache.commons.io.IOUtils;
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 HttpServletRequestWrapperHandler extends HttpServletRequestWrapper {
private final String body;
/**
* Constructs a request object wrapping the given request.
*
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public HttpServletRequestWrapperHandler(HttpServletRequest request) throws IOException {
super(request);
this.body = IOUtils.toString(request.getReader());
}
public String getBody() {
return body;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
然后使用过滤器将上面的类实例化
package com.itellyou.api.handler;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(filterName="FilterHandler",urlPatterns="/*")
@Configuration
public class FilterHandler implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//取Body数据
HttpServletRequestWrapperHandler requestWrapper = new HttpServletRequestWrapperHandler(request);
//TODO something
chain.doFilter(requestWrapper != null ? requestWrapper : request,servletResponse);
}
}
这样就记录好 request 中的值了 以下使用就不会报错了:
@PostMapping("/register")
public Result register(@Validated(UserInfoModel.RegisterAction.class) @RequestBody UserInfoModel userInfoModel,@MultiRequestBody @NotBlank String code){
System.out.println(userInfoModel);
System.out.println(code);
return new Result(0,"test");
}
可以写下测试代码,测试通过后提下PR
上个PR?