支持多 @RequestBody 的自定义注解
概述
由于 @RequestBody
只能使用一次,所以通常在接收参数时会使用实体或者Map,在参数较多的情况下还要进行拆分,比较麻烦。本文通过自定义注解实现接口多次使用 @RequestBody
的功能,项目上传到96XL/Spring-MultiRequestBody: Spring多@RequestBody支持 (github.com)。
功能实现
核心类为 MultiRequestBody
、MultiRequestBodyArgumentResolver
、WebMvcConfig
。
自定义注解
package com.xl.multirequestbody.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiRequestBody {
/**
* 参数是否必传, 当接收参数是非基本数据类型时生效
*/
boolean required() default true;
/**
* 当 value 的值或者参数名不匹配时, 是否允许解析最外层属性到该对象
*/
boolean parseAllFields() default true;
/**
* 解析时用到的 json key
*/
String value() default "";
}
@Target(ElementType.PARAMETER)
指定注解作用范围是方法参数。@Retention(RetentionPolicy.RUNTIME)
指定注解生命周期是运行时也存在。- 注解属性在解析时参数使用,更加灵活的实现功能。
参数解析器
package com.xl.multirequestbody.resolver;
import com.xl.multirequestbody.annotation.MultiRequestBody;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;
import java.util.List;
public class MultiRequestBodyMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
public MultiRequestBodyMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
super(converters);
}
/**
* 设置支持的方法参数类型
*
* @param parameter 方法参数
* @return 是否支持
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 支持带 @MultiRequestBody 注解的参数
return parameter.hasParameterAnnotation(MultiRequestBody.class);
}
/**
* 参数解析
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(parameter, webRequest);
String name = parameter.getParameterName();
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return arg;
}
/**
* 获取参数
*/
private Object readWithMessageConverters(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
// 省略部分代码...
}
}
HandlerMethodArgumentResolver
是参数解析器的接口,所有参数解析器都要实现该接口,接口中有supportsParameter()
和resolveArgument
() 两个方法。其中supportsParameter()
方法表示是否启用这个参数解析器,返回 true 表示启用,返回 false 表示不启用。resolveArgument()
方法是具体的解析过程,就是从 request 中取出参数的过程,方法的返回值就对应了接口中参数的值。- 本类中继承的
AbstractMessageConverterMethodArgumentResolver
也是HandlerMethodArgumentResolver
接口的一个实现类(其他实现类后面会单独写一个文章来介绍)。至于为什么要继承这个类而非其他实现类,是因为需要用到该类中的validateIfApplicable()
和isBindExceptionRequired()
两个方法,这两个方法是对@Validated
参数校验注解的支持。其中resolveArgument()
方法的逻辑参考了RequestResponseBodyMethodProcessor
(也就是@RequestBody
注解参数解析器)的写法。 readWithMessageConverters()
方法是从 request 中取出参数并解析的过程,其中会使用到自定义注解中声明的属性,可以查看代码了解一下逻辑。
添加到适配器执行链
package com.xl.multirequestbody.config;
import com.xl.multirequestbody.resolver.MultiRequestBodyMethodArgumentResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.Charset;
import java.util.List;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 添加 MultiRequestBody 参数解析器
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MultiRequestBodyMethodArgumentResolver(super.getMessageConverters()));
}
/**
* 解决中文乱码问题
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(responseBodyConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
/**
* 指定编码格式
*/
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
}
扩展
该注解无法和 @RequestBody
注解同时使用,因为读取 http 请求体的操作,最终都要调用 HttpServletRequest
的 getInputStream()
方法和 getReader()
方法,而这两个方法总共只能被调用一次,第二次调用就会报错。如果同时使用,会调用两次方法进而报错。如果想要同时使用,可以参考多次读取HTTP请求体 - 今日说码 (96xl.top)文章内容。