SpringBoot自定义参数解析器,使被@RequestBody标注的参数能额外接收Content-Type为application/x-www-form-urlencoded的请求

基础知识介绍

       在SpringBoot里,若Controller层里方法的形参前使用了@RequestBody注解,那么该参数将会被RequestResponseBodyMethodProcessor解析器进行解析,若此时Content-Type为application/x-www-form-urlencoded,那么会报Unsupported Media Type错误,这就要求:请求的Content-Type必须为application/json了。

       一般的,若Controller层里方法的形参前(不管有没有其它注解,只要)没使用@RequestBody注解,那么该参数几乎都是符合ServletModelAttributeMethodProcessor解析器要求的,进而会使用ServletModelAttributeMethodProcessor解析器进行解析;当请求的Content-Type为application/x-www-form-urlencoded时,几乎用的都是ServletModelAttributeMethodProcessor解析器。

注:参数符合RequestResponseBodyMethodProcessor解析器要求的条件是
       parameter.hasParameterAnnotation(RequestBody.class)。可详见
       RequestResponseBodyMethodProcessor类源码。

注:参数符合ServletModelAttributeMethodProcessor解析器要求的条件是
       parameter.hasParameterAnnotation(ModelAttribute.class) ||
       this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())

       可详见ModelAttributeMethodProcessor类源码。


需求介绍及实现方式说明

声明:本文通过实现下述需求,进行示例

需求介绍:

       实现被@RequestBody注解的参数,既能接收Content-Type为application/json的请求的参数值,又能接收Content-Type为application/x-www-form-urlencoded的请求的参数值。

实现方式概述:

       自定义参数解析器,当参数前有@RequestBody时,使用该解析器;在该解析器的内部,判断Content-Type,若Content-Type为application/x-www-form-urlencoded,那么采用ServletModelAttributeMethodProcessor解析器;否者采用RequestResponseBodyMethodProcessor解析器。

注:即相当于对RequestResponseBodyMethodProcessor解析器进行了简单的封装。


具体实现代码示例

自定义参数解析器

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;

import javax.servlet.http.HttpServletRequest;

/**
 * 自定义参数解析器
 *
 * 注: 自定义参数解析器, 实现对RequestResponseBodyMethodProcessor的扩展
 *
 * 提示: 此解析器要实现的功能是: 若controller方法的参数前, 使用了@RequestBody注解, 那么解析此参数时,
 *                           1、若Content-Type为application/x-www-form-urlencoded,
 *                              那么走ServletModelAttributeMethodProcessor解析器
 *                           2、若Content-Type不为application/x-www-form-urlencoded,
 *                              那么走本应该走的RequestResponseBodyMethodProcessor解析器
 *
 * @author JustryDeng
 * @date 2019/8/19 19:47
 */
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
     * 解析Content-Type为application/json的默认解析器是RequestResponseBodyMethodProcessor
     */
    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    /**
     * 解析Content-Type为application/x-www-form-urlencoded的默认解析器是ServletModelAttributeMethodProcessor
     */
    private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;

    /**
     * 全参构造
     */
    public MyHandlerMethodArgumentResolver(RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor,
                                           ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor) {
        this.requestResponseBodyMethodProcessor = requestResponseBodyMethodProcessor;
        this.servletModelAttributeMethodProcessor = servletModelAttributeMethodProcessor;
    }

    /**
     * 当参数前有@RequestBody注解时, 解析该参数 会使用此 解析器
     *
     * 注:此方法的返回值将决定:是否使用此解析器解析该参数
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(RequestBody.class);
    }

    /**
     * 解析参数
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory)
                                  throws Exception {
        final String applicationXwwwFormUrlencoded = "application/x-www-form-urlencoded";
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);

        if (request == null) {
            throw  new RuntimeException(" request must not be null!");
        }
        String contentType = request.getContentType();
        /*
         * 如果ContentType是application/x-www-form-urlencoded,那么使用ServletModelAttributeMethodProcessor解析器
         *
         * 注:其实默认的,当系统识别到参数前有@RequestBody注解时,就会走RequestResponseBodyMethodProcessor解析器;这里就
         *    相当于在走默认的解析器前走了个判断而已。
         */
        if (applicationXwwwFormUrlencoded.equals(contentType)) {
            return servletModelAttributeMethodProcessor.resolveArgument(methodParameter,
                    modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
        }
        return requestResponseBodyMethodProcessor.resolveArgument(methodParameter,
                    modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
    }
}

注册该参数解析器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * 配置注册参数解析器
 *
 * @author JustryDeng
 * @date 2019/8/20 10:13
 */
@Configuration
public class ConfigArgumentResolvers {

    private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;

    public ConfigArgumentResolvers(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
        this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
    }

    @PostConstruct
    private void addArgumentResolvers() {
        // 获取到的是不可变的集合
        List<HandlerMethodArgumentResolver> argumentResolvers =
                                                    requestMappingHandlerAdapter.getArgumentResolvers();
        MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver =
                                                    getMyHandlerMethodArgumentResolver(argumentResolvers);
        // ha.getArgumentResolvers()获取到的是不可变的集合,所以我们需要新建一个集合来放置参数解析器
        List<HandlerMethodArgumentResolver> myArgumentResolvers =
                                                    new ArrayList<>(argumentResolvers.size() + 1);
        // 将自定义的解析器,放置在第一个; 并保留原来的解析器
        myArgumentResolvers.add(myHandlerMethodArgumentResolver);
        myArgumentResolvers.addAll(argumentResolvers);
        requestMappingHandlerAdapter.setArgumentResolvers(myArgumentResolvers);
    }

    /**
     * 获取MyHandlerMethodArgumentResolver实例
     */
    private MyHandlerMethodArgumentResolver getMyHandlerMethodArgumentResolver(
            List<HandlerMethodArgumentResolver> argumentResolversList) {
        // 解析Content-Type为application/json的默认解析器
        RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
        // 解析Content-Type为application/x-www-form-urlencoded的默认解析器
        ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor = null;

        if (argumentResolversList == null) {
            throw new RuntimeException("argumentResolverList must not be null!");
        }
        for (HandlerMethodArgumentResolver argumentResolver : argumentResolversList) {
            if (requestResponseBodyMethodProcessor != null && servletModelAttributeMethodProcessor != null) {
                break;
            }
            if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
                requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor)argumentResolver;
                continue;
            }
            if (argumentResolver instanceof ServletModelAttributeMethodProcessor) {
                servletModelAttributeMethodProcessor = (ServletModelAttributeMethodProcessor)argumentResolver;
            }
        }
        if (requestResponseBodyMethodProcessor == null || servletModelAttributeMethodProcessor == null) {
            throw new RuntimeException("requestResponseBodyMethodProcessor and "
                    + " servletModelAttributeMethodProcessor must not be null!");
        }
        return new MyHandlerMethodArgumentResolver(requestResponseBodyMethodProcessor,
                servletModelAttributeMethodProcessor);
    }

测试一下

测试项目结构及相关测试类说明

项目结构:

注:ConfigArgumentResolvers与MyHandlerMethodArgumentResolver在上文中已给出,下面只给
        出Person与DemoController的内容。

Person:

DemoController:

基本功能测试(工具postman)

  • application/x-www-form-urlencoded访问/xyz:

  • application/json访问/abc:

  • application/json访问/foo:

postman效果如图所示:

后端控制台输出:

注:这说明当请求Content-Type为application/json时,@RequestParam获取不到请求体json串里面的值。

自定义参数解析器后的功能测试(工具postman)

  • application/x-www-form-urlencoded访问/abc:

  • application/x-www-form-urlencoded访问/foo:

postman效果如图所示:

后端控制台输出:

由此可见:自定义参数解析器,示例成功

^_^ 如有不当之处,欢迎指正

^_^ 参考链接
               
SpringBoot—多种Content-Type同时进行参数绑定的处理方法_daegis的博客-CSDN博客

^_^ 参考源码
             
  《SpringBoot部分源码》

^_^ demo代码
             
 demo代码下载

^_^ ​​​​​​​本文已经被收录进《程序员成长笔记》,笔者JustryDeng

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值