我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决

前言

Emmmm…最近在做项目的途中,有遇到一个方法需要接收的参数只有一个或者较少的时候就懒得写实体类去接收,使用spring框架都知道,接收单个参数就使用@RequestParam注解就好了,但是前端对应的Content-type是需要改成application/x-www-form-urlencoded,所以在接口文档上面特地标记了。但是…不知道前端是格式改了但是参数还是用的json格式没有改成键值对的方式传递还是什么原因,就一直说参数传不过来,叫我改回json格式的。。我也实在是懒,另外一个也觉得没必要,就一两个参数就新建一个实体,太浪费,但是这个问题让我觉得不灵活蛮久了,也一直没找到办法,所以借这个机会,打开了我的开发神器,www.baidu.com…输入我的问题,找了好久也没找到有解决的方案,然后就想着看下Spring内部是怎么处理的吧,就稍微跟了下源码,下面就说下我解决的方案。

一、RequestMappingHandlerAdapter

RequestMappingHandlerAdapter实现了HandlerAdapter接口,顾名思义,表示handler的adapter,这里的handler指的是Spring处理具体请求的某个Controller的方法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器。

RequestMappingHandlerAdapter是HandlerAdapter的一个具体实现,主要用于将某个请求适配给@RequestMapping类型的Handler处理,这里面就包含着请求数据和响应数据的处理。

		// 这里可以获取到处理程序方法参数解析器的一个列表
        List<HandlerMethodArgumentResolver> argumentResolvers =
                requestMappingHandlerAdapter.getArgumentResolvers()

如果是想处理响应参数的话就使用

        //这里可以获取到处理程序方法返回值的处理器
        List<HandlerMethodReturnValueHandler> originalHandlers = 
                 requestMappingHandlerAdapter.getReturnValueHandlers();

能获取到这个列表了,那需要加入我们自己定义的处理器应该不太麻烦了吧?(这里不讲返回数据的自定义策略处理,网上也有其他文章,如果需要可以找下)

二、HandlerMethodArgumentResolver

策略接口解决方法参数代入参数值在给定请求的上下文(翻译的源码注释)

简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验等等。

——————————————————————————————————————————

那么这个时候我已经知道了第一步获取到的那个列表中存放的类型是什么了,简而言之,我们只需要实现这个策略类,编写我们自己的算法或逻辑就行了

这个接口里面有两个方法需要实现:

第一个方法的作用:是否与给定方法的参数是由该解析器的支持。(如果返回true,那么就使用该类进行参数转换,如果返回false,那么继续找下一个策略类)

第二个方法的作用:解决方法参数成从给定请求的自变量值。 由WebDataBinderFactory提供了一个方法来创建一个WebDataBinder所需数据绑定和类型转换目的时实例。(简单来讲,就是转换参数值的,返回的就是解析的参数值)

三、RequestParamMethodArgumentResolver

这个类就是用来处理Controller的方法上有加@RequestParam注解的具体处理器。

首先会调用这个方法来确定是否使用这个处理器解析参数,那么我们也看到了,如果参数有RequestParam注解,那么则会使用该类进行处理,那么我们能不能效仿呢?

四、MyHandlerMethodArgumentResolver

这个没啥好说,就自己定义的参数解析器。

直接上代码吧


public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {


    
    private RequestParamMethodArgumentResolver requestParamMethodArgumentResolver;

    
    public MyHandlerMethodArgumentResolver(RequestParamMethodArgumentResolver requestParamMethodArgumentResolver) {
        this.requestParamMethodArgumentResolver = requestParamMethodArgumentResolver;
    }

    
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
    	//很明显,就是判断是否有这个注解
        return methodParameter.hasParameterAnnotation(RequestParam.class);
    }


    
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory)
            throws Exception {
        final String applicationJson = "application/json";
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        if (request == null) {
            throw new RuntimeException(" request must not be null!");
        }
        //获取到内容类型
        String contentType = request.getContentType();
        //如果类型是属于json 那么则跑自己解析的方法
        if (null != contentType && contentType.contains(applicationJson )) {
        	//获取参数名称
            String parameterName = methodParameter.getParameterName();
            //获取参数类型
            Class<?> parameterType = methodParameter.getParameterType();
			//因为json数据是放在流里面,所以要去读取流,
			//但是ServletRequest的getReader()和getInputStream()两个方法只能被调用一次,而且不能两个都调用。
			//所以这里是需要写个自定义的HttpServletRequestWrapper,主要功能就是需要重复读取流数据
            String read = getRead(request.getReader());
            //转换json
            JSONObject jsonObject = JSON.parseObject(read);
            Object o1;
            if (jsonObject == null) {
            	//这里有一个可能性就是比如get请求,参数是拼接在URL后面,但是如果我们还是去读流里面的数据就会读取不到
                Map<String, String[]> parameterMap = request.getParameterMap();
                o1 = parameterMap.get(parameterName);
            }else {
                o1 = jsonObject.get(parameterName);
            }
            Object arg = null;
            //如果已经获取到了值的话那么再做类型转换
            if (o1 != null) {
                WebDataBinder binder = webDataBinderFactory.createBinder(nativeWebRequest, null, parameterName);
                arg = binder.convertIfNecessary(o1, parameterType, methodParameter);
            }
            return arg;
        }
		//否则跑原本的策略类.
        Object o = requestParamMethodArgumentResolver.resolveArgument(methodParameter,
                modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
        return o;
    }

    
    private static String getRead(BufferedReader bf) {
        StringBuilder sb = new StringBuilder();
        try {
            char[] buff = new char[1024];
            int len;
            while ((len = bf.read(buff)) != -1) {
                sb.append(buff, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}

四、ConfigArgumentResolvers

自己的策略类已经写好了,那么怎么加入到配置中去呢?


@Configuration
public class ConfigArgumentResolvers {
    private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;

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

	//springBoot启动的时候执行
    @PostConstruct
    private void addArgumentResolvers() {
        // 获取到框架定义好的参数解析集合
        List<HandlerMethodArgumentResolver> argumentResolvers =
                requestMappingHandlerAdapter.getArgumentResolvers();
        MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver = getMyHandlerMethodArgumentResolver(argumentResolvers);
        // ha.getArgumentResolvers()获取到的是不可变的集合,所以我们需要新建一个集合来放置参数解析器
        List<HandlerMethodArgumentResolver> myArgumentResolvers =
                new ArrayList<>(argumentResolvers.size() + 1);
        //这里有一个注意点就是自定义的处理器需要放在RequestParamMethodArgumentResolver前面
        //为什么呢?因为如果放在它后面的话,那么它已经处理掉了,就到不了我们自己定义的策略里面去了
        //所以直接把自定义的策略放在第一个,稳妥!
        // 将自定义的解析器,放置在第一个; 并保留原来的解析器
        myArgumentResolvers.add(myHandlerMethodArgumentResolver);
        myArgumentResolvers.addAll(argumentResolvers);
        //再把新的集合设置进去
        requestMappingHandlerAdapter.setArgumentResolvers(myArgumentResolvers);
    }

    
    private MyHandlerMethodArgumentResolver getMyHandlerMethodArgumentResolver(
            List<HandlerMethodArgumentResolver> argumentResolversList) {
        // 原本处理RequestParam的类
        RequestParamMethodArgumentResolver requestParamMethodArgumentResolver = null;

        if (argumentResolversList == null) {
            throw new RuntimeException("argumentResolverList must not be null!");
        }
        for (HandlerMethodArgumentResolver argumentResolver : argumentResolversList) {
            if (requestParamMethodArgumentResolver != null) {
                break;
            }
            if (argumentResolver instanceof RequestParamMethodArgumentResolver) {
            // 因为在我们自己策略里面是还需要用到这个原本的类的,所以需要得到这个对象实例
                requestParamMethodArgumentResolver = (RequestParamMethodArgumentResolver) argumentResolver;
            }
        }
        if (requestParamMethodArgumentResolver == null) {
            throw new RuntimeException("RequestParamMethodArgumentResolver not be null!");
        }
        //实例化自定义参数解析器
        return new MyHandlerMethodArgumentResolver(requestParamMethodArgumentResolver);
    }
}

五、MyHttpServletRequestWrapper

这个就是自定义的HttpServletRequest,保证可以重复获取到流数据


public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //在读取流之前获取一次这个parameterMap,否则读取流后无法再解析出数据,
        // 原因是org.apache.catalina.connector.Request里面有usingInputStream 和 usingReader两个全局变量记录流是否被读取过
        //org.apache.catalina.connector.Request里面的parseParameters方法就是用来解析请求参数(Parse request parameters.)
        //在解析参数之前会有一个判断,如果流被读取过 则不再解析请求参数 //
        // if (usingInputStream || usingReader) { 这是源码里面的判断
        //                success = true;
        //                return;
        //            }
        //如果先请求过一次后,那么org.apache.catalina.util.ParameterMap里面会有一个locked状态,如果读过一次之后 会变成锁定状态 那么后面再读都是读取解析过后的map
        //    
        //    private boolean locked = false;
        request.getParameterMap();
        body = ReadAsChars(request).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

	
	public static String ReadAsChars(ServletRequest request)
        {
            InputStream is = null;
            StringBuilder sb = new StringBuilder();
            try
            {
                is = request.getInputStream();

                byte[] b = new byte[4096];
                for (int n; (n = is.read(b)) != -1;)
                {
                    sb.append(new String(b, 0, n));
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            finally
            {
                if (null != is)
                {
                    try
                    {
                        is.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
}

六、HttpServletRequestReplacedFilter

替换掉原本的Request对象,使用自定义的


@Component
public class HttpServletRequestReplacedFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest) {
            requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) request);
        }
        if(null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
}

七、总结

如果是想@RequestBody接收表单形式的参数也可以用此方法,处理起来更简单 ,只需要实例化自定义处理器的时候传入另外两个个处理器就可以了

    
    private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;

    
    private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;

到这一步就已经实现了RequestParam注解也可以接受Json格式数据了,我也没进行更多的测试,具体还会出现什么关联性的问题暂时是没发现,后续如果有码友出现了什么问题可以留言一起讨论,本人小菜鸡一枚,希望写的不好的地方大神多多指教,不胜感激!

总结

到此这篇关于Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决的文章就介绍到这了,更多相关@RequestParam注解无法读取application/json内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决

RequestParam用于将指定的请求参数赋值给方法中的形参,可以接受简单类型属性,也可以接受对象类型,一般用于GET请求,下面这篇文章主要给大家介绍了关于Spring/SpringBoot @RequestParam注解无法读取application/json格式数据问题解决的相关资料,需要的朋友可以参考下
2022-11-13

如何解决springboot利用ConfigurationProperties注解配置数据源无法读取配置信息问题

小编给大家分享一下如何解决springboot利用ConfigurationProperties注解配置数据源无法读取配置信息问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起
2023-06-15

SpringBoot怎么解决Long型数据转换成json格式时丢失精度问题

这篇“SpringBoot怎么解决Long型数据转换成json格式时丢失精度问题”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇
2023-07-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录