Article source shenyifengtk.github.io reprint please indicate
Those developed with Spring MVC should have used @RequstBody to receive json parameters and convert them into pojo objects. object.
front-end value | backend receive | result |
---|---|---|
{"id": 3,"name":"xxx"} | User(id,name) | successful injection |
{"id": 3,"name":"xxx"} | (String name, int id) | does not support this method |
Sometimes an interface has only two parameters for upload, and we don't want to create a separate object for this, which may result in a pojo object for each hangdler method, and most objects may not be shared by other handlers. Rather, it is expected to be received using one or two parameters. Let's take a look first @RequestBody
this annotation realizes how to convert json into pojo object, which is realized by spring HandlerMethodArgumentResolver parameter parser, and understand the interface.
public interface HandlerMethodArgumentResolver {
/**
* 是否支持方法上参数的处理,只有返回ture,才会执行下面方法
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 将方法参数解析为给定请求的参数值
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
The default parameter parser can be seen under RequestMappingHandlerAdapter
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); //@RequestPart 文件注入
resolvers.add(new RequestParamMapMethodArgumentResolver()); //@RequestParam
resolvers.add(new PathVariableMethodArgumentResolver()); //@PathVariable
resolvers.add(new PathVariableMapMethodArgumentResolver()); //@PathVariable 会返回一个Map对象
resolvers.add(new MatrixVariableMethodArgumentResolver()); //@MatrixVariable
resolvers.add(new MatrixVariableMapMethodArgumentResolver()); //MatrixVariable 会返回Map 对象
resolvers.add(new ServletModelAttributeMethodProcessor(false)); //属性板顶
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); //@RequestBody 后面重点讲解
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); //@RequestPart
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); // @RequestHeader
resolvers.add(new RequestHeaderMapMethodArgumentResolver()); //@RequestHeader Map对象
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); //Cookie 值 注入
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); //@Value
resolvers.add(new SessionAttributeMethodArgumentResolver()); //@SessionAttribute
resolvers.add(new RequestAttributeMethodArgumentResolver()); //@RequestAttribute
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver()); //servlet api对象 HttpServletRequest 这类
resolvers.add(new ServletResponseMethodArgumentResolver()); //ServletResponse 对象注入
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); //RequestEntity、HttpEntity
resolvers.add(new RedirectAttributesMethodArgumentResolver()); //重定向
resolvers.add(new ModelMethodProcessor()); //返回Model 对象
resolvers.add(new MapMethodProcessor()); // 处理方法参数返回一个Map
resolvers.add(new ErrorsMethodArgumentResolver()); //处理错误方法参数,返回最后一个对象
resolvers.add(new SessionStatusMethodArgumentResolver()); //SessionStatus
resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); //UriComponentsBuilder
if (KotlinDetector.isKotlinPresent()) {
resolvers.add(new ContinuationHandlerMethodArgumentResolver());
}
// Custom arguments 自定义
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
It can be seen that most of the method parameters are implemented by the above processors, focusing on how RequestResponseBodyMethodProcessor injects json, mainly depends on how the two methods are implemented.
@Override
public boolean supportsParameter(MethodParameter parameter) {
//只有使用了@RequestBody 注解默认就开启处理
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//转换成对象了
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
//获取参数变量名称
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) { //使用WebDatabinder 对已经序列化对象属性绑定处理
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 adaptArgumentIfNecessary(arg, parameter);
}
The realization of the json serial number into an object is in the readWithMessageConverters method
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
//创建重复读写流
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//通过HttpMessageConverter 来对String json 转换成对象
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType); //前置处理
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); //后置处理 类型通知
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
//缩减部分代码
return body;
}
The http request content is converted into an object by calling the GenericHttpMessageConverter read method. At this time, the call is AbstractJackson2HttpMessageConverter
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
"UTF-16".equals(charset.name()) ||
"UTF-32".equals(charset.name());
try {
if (inputMessage instanceof MappingJacksonInputMessage) { //这个是使用了JsonView
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
if (isUnicode) {
return objectReader.readValue(inputMessage.getBody());
}
else {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
return objectReader.readValue(reader); //反序列化成Java 对象
}
}
}
if (isUnicode) {
return objectMapper.readValue(inputMessage.getBody(), javaType);
}
else {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
return objectMapper.readValue(reader, javaType);
}
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
}
}
It can be seen that @RequestBody is converted by calling ObjectMapper readValue through the GenericHttpMessageConverter class. It can only convert json to pojo object, and does not support conversion of simple types such as String, Integer, and Long, which has natural defects. If you want to inject the json key into the parameter, you need to implement a parameter parser manually. The simple implementation code is shown below.
hands-on
Goal: We want to create an annotation similar to @RequestBody, marking the current parameter to support direct injection of json key, this annotation can also be directly modified in the method, indicating that the entire method parameter is annotated, of course, it can also support the entire class, indicating that all methods are parameters are supported. Implementing a parameter parser corresponding to the annotation.
Create annotation class
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonKeyValue {
/**
* json key 如何没有则使用 类型变量名
* @return
*/
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
}
Implement a parameter parser
public class RequestJsonKeyValueMethodProcessor implements HandlerMethodArgumentResolver {
private ObjectMapper objectMapper ;
public RequestJsonKeyValueMethodProcessor(ObjectMapper objectMapper){
this.objectMapper = objectMapper;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean b = parameter.hasParameterAnnotation(JsonKeyValue.class);
if (!b) {
JsonKeyValue value = parameter.getMethodAnnotation(JsonKeyValue.class); //从方法上找注解
b = value != null;
if (!b){
value = parameter.getContainingClass().getAnnotation(JsonKeyValue.class); //从类上找注解
b = value != null;
}
}
return b;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = getCharset(contentType);
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
JsonNode jsonNode = objectMapper.readTree(reader);
String parameterName = parameterName(parameter);
JsonNode path = jsonNode.path(parameterName);
Object o = objectMapper.convertValue(path, parameter.getNestedParameterType());
return o;
}
private String parameterName(MethodParameter parameter){
JsonKeyValue annotation = parameter.getParameterAnnotation(JsonKeyValue.class);
if (annotation != null){
String name = annotation.name();
if (StringUtils.hasText(name))
return annotation.name();
}
return parameter.getParameterName();
}
//抄袭 AbstractMessageConverterMethodArgumentResolver 主要是防止将流读入后,controller 方法不能在读了
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
private final HttpHeaders headers;
@Nullable
private final InputStream body;
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
this.headers = inputMessage.getHeaders();
InputStream inputStream = inputMessage.getBody();
if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (inputStream.read() != -1 ? inputStream : null);
inputStream.reset();
}
else {
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
this.body = null;
}
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public InputStream getBody() {
return (this.body != null ? this.body : StreamUtils.emptyInput());
}
public boolean hasBody() {
return (this.body != null);
}
}
private Charset getCharset(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
return contentType.getCharset();
}
else {
return StandardCharsets.UTF_8;
}
}
}
Add custom parameter parser
@EnableWebMvc
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
private Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new RequestJsonKeyValueMethodProcessor(objectMapper));
}
}
There is no need to worry that the custom parameter resolver will override the spring native resolver. Here, the addArgumentResolvers add resolver will be automatically put into the CustomArgumentResolvers.
A simple custom parameter parser is completed, which supports the parsing of parameters such as String and Integer of ordinary classes. Note that this parameter parser does not consider the handling of empty parameters, and such cases as Optional are only suitable for some simple and rapid development scenarios. I don't know if your colleagues have used this knowledge in daily development. Our company's basic framework uses these technologies for rapid development, which greatly improves development efficiency.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。