1
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.


神易风
106 声望52 粉丝

alert("hello world")