最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

解決Spring/SpringBoot @RequestParam注解無法讀取application/json格式數(shù)據(jù)問題

 更新時間:2025年10月17日 09:22:10   作者:Peak_H_  
本文介紹了如何通過自定義HandlerMethodArgumentResolver,使Spring的@RequestParam注解支持接收JSON格式參數(shù),從而避免為少量參數(shù)單獨創(chuàng)建實體類,作者詳細分析了Spring源碼并給出了解決方案和相關(guān)配置步驟

Emmmm…最近在做項目的途中,有遇到一個方法需要接收的參數(shù)只有一個或者較少的時候就懶得寫實體類去接收,使用spring框架都知道,接收單個參數(shù)就使用@RequestParam注解就好了,但是前端對應的Content-type是需要改成application/x-www-form-urlencoded,所以在接口文檔上面特地標記了。

但是…不知道前端是格式改了但是參數(shù)還是用的json格式?jīng)]有改成鍵值對的方式傳遞還是什么原因,就一直說參數(shù)傳不過來,叫我改回json格式的。。

我也實在是懶,另外一個也覺得沒必要,就一兩個參數(shù)就新建一個實體,太浪費,但是這個問題讓我覺得不靈活蠻久了,也一直沒找到辦法,所以借這個機會,打開了我的開發(fā)神器,www.baidu.com…輸入我的問題,找了好久也沒找到有解決的方案,然后就想著看下Spring內(nèi)部是怎么處理的吧,就稍微跟了下源碼,下面就說下我解決的方案。

一、RequestMappingHandlerAdapter

RequestMappingHandlerAdapter實現(xiàn)了HandlerAdapter接口,顧名思義,表示handler的adapter,這里的handler指的是Spring處理具體請求的某個Controller的方法,也就是說HandlerAdapter指的是將當前請求適配到某個Handler的處理器。

RequestMappingHandlerAdapter是HandlerAdapter的一個具體實現(xiàn),主要用于將某個請求適配給@RequestMapping類型的Handler處理,這里面就包含著請求數(shù)據(jù)和響應數(shù)據(jù)的處理。

		// 這里可以獲取到處理程序方法參數(shù)解析器的一個列表
        List<HandlerMethodArgumentResolver> argumentResolvers =
                requestMappingHandlerAdapter.getArgumentResolvers()

如果是想處理響應參數(shù)的話就使用

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

能獲取到這個列表了,那需要加入我們自己定義的處理器應該不太麻煩了吧?(這里不講返回數(shù)據(jù)的自定義策略處理,網(wǎng)上也有其他文章,如果需要可以找下)

二、HandlerMethodArgumentResolver

策略接口解決方法參數(shù)代入?yún)?shù)值在給定請求的上下文(翻譯的源碼注釋)

簡單的理解為:它負責處理你Handler方法里的所有入?yún)ⅲ喊ㄗ詣臃庋b、自動賦值、校驗等等。

那么這個時候我已經(jīng)知道了第一步獲取到的那個列表中存放的類型是什么了,簡而言之,我們只需要實現(xiàn)這個策略類,編寫我們自己的算法或邏輯就行了

這個接口里面有兩個方法需要實現(xiàn):

第一個方法的作用:是否與給定方法的參數(shù)是由該解析器的支持。(如果返回true,那么就使用該類進行參數(shù)轉(zhuǎn)換,如果返回false,那么繼續(xù)找下一個策略類) 

第二個方法的作用:解決方法參數(shù)成從給定請求的自變量值。 由WebDataBinderFactory提供了一個方法來創(chuàng)建一個WebDataBinder所需數(shù)據(jù)綁定和類型轉(zhuǎn)換目的時實例。(簡單來講,就是轉(zhuǎn)換參數(shù)值的,返回的就是解析的參數(shù)值)

三、RequestParamMethodArgumentResolver

這個類就是用來處理Controller的方法上有加@RequestParam注解的具體處理器。

首先會調(diào)用這個方法來確定是否使用這個處理器解析參數(shù),那么我們也看到了,如果參數(shù)有RequestParam注解,那么則會使用該類進行處理,那么我們能不能效仿呢?

四、MyHandlerMethodArgumentResolver

?這個沒啥好說,就自己定義的參數(shù)解析器。

  • 直接上代碼吧
/**
 * @BelongsProject: 
 * @BelongsPackage: 
 * @Author: hef
 * @CreateTime: 2020-06-20 18:49
 * @Description: 描述
 */
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {


    /**
     * 這個是處理@RequestParam注解的原本策略類
     */
    private RequestParamMethodArgumentResolver requestParamMethodArgumentResolver;

    /**
     * 全參構(gòu)造
     */
    public MyHandlerMethodArgumentResolver(RequestParamMethodArgumentResolver requestParamMethodArgumentResolver) {
        this.requestParamMethodArgumentResolver = requestParamMethodArgumentResolver;
    }

    /**
     * 當參數(shù)前有@RequestParam注解時,會使用此 解析器
     * <p>
     * 注:此方法的返回值將決定:是否使用此解析器解析該參數(shù)
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
    	//很明顯,就是判斷是否有這個注解
        return methodParameter.hasParameterAnnotation(RequestParam.class);
    }


    /**
     * 解析參數(shù)
     */
    @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!");
        }
        //獲取到內(nèi)容類型
        String contentType = request.getContentType();
        //如果類型是屬于json 那么則跑自己解析的方法
        if (null != contentType && contentType.contains(applicationJson )) {
        	//獲取參數(shù)名稱
            String parameterName = methodParameter.getParameterName();
            //獲取參數(shù)類型
            Class<?> parameterType = methodParameter.getParameterType();
			//因為json數(shù)據(jù)是放在流里面,所以要去讀取流,
			//但是ServletRequest的getReader()和getInputStream()兩個方法只能被調(diào)用一次,而且不能兩個都調(diào)用。
			//所以這里是需要寫個自定義的HttpServletRequestWrapper,主要功能就是需要重復讀取流數(shù)據(jù)
            String read = getRead(request.getReader());
            //轉(zhuǎn)換json
            JSONObject jsonObject = JSON.parseObject(read);
            Object o1;
            if (jsonObject == null) {
            	//這里有一個可能性就是比如get請求,參數(shù)是拼接在URL后面,但是如果我們還是去讀流里面的數(shù)據(jù)就會讀取不到
                Map<String, String[]> parameterMap = request.getParameterMap();
                o1 = parameterMap.get(parameterName);
            }else {
                o1 = jsonObject.get(parameterName);
            }
            Object arg = null;
            //如果已經(jīng)獲取到了值的話那么再做類型轉(zhuǎn)換
            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;
    }

    /**
     * 流轉(zhuǎn)字符串
     *
     * @param bf
     * @return
     */
    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

自己的策略類已經(jīng)寫好了,那么怎么加入到配置中去呢?

/**
 * @BelongsProject: 
 * @BelongsPackage: 
 * @Author: hef
 * @CreateTime: 2020-06-20 18:49
 * @Description: 描述
 */
@Configuration
public class ConfigArgumentResolvers {
    private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;

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

	//springBoot啟動的時候執(zhí)行
    @PostConstruct
    private void addArgumentResolvers() {
        // 獲取到框架定義好的參數(shù)解析集合
        List<HandlerMethodArgumentResolver> argumentResolvers =
                requestMappingHandlerAdapter.getArgumentResolvers();
        MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver = getMyHandlerMethodArgumentResolver(argumentResolvers);
        // ha.getArgumentResolvers()獲取到的是不可變的集合,所以我們需要新建一個集合來放置參數(shù)解析器
        List<HandlerMethodArgumentResolver> myArgumentResolvers =
                new ArrayList<>(argumentResolvers.size() + 1);
        //這里有一個注意點就是自定義的處理器需要放在RequestParamMethodArgumentResolver前面
        //為什么呢?因為如果放在它后面的話,那么它已經(jīng)處理掉了,就到不了我們自己定義的策略里面去了
        //所以直接把自定義的策略放在第一個,穩(wěn)妥!
        // 將自定義的解析器,放置在第一個; 并保留原來的解析器
        myArgumentResolvers.add(myHandlerMethodArgumentResolver);
        myArgumentResolvers.addAll(argumentResolvers);
        //再把新的集合設(shè)置進去
        requestMappingHandlerAdapter.setArgumentResolvers(myArgumentResolvers);
    }

    /**
     * 獲取MyHandlerMethodArgumentResolver實例
     */
    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!");
        }
        //實例化自定義參數(shù)解析器
        return new MyHandlerMethodArgumentResolver(requestParamMethodArgumentResolver);
    }
}

五、MyHttpServletRequestWrapper

這個就是自定義的HttpServletRequest,保證可以重復獲取到流數(shù)據(jù)

/**
 * @BelongsProject: 
 * @BelongsPackage: 
 * @Author: hef
 * @CreateTime: 2020-06-22 16:29
 * @Description: 描述
 */
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //在讀取流之前獲取一次這個parameterMap,否則讀取流后無法再解析出數(shù)據(jù),
        // 原因是org.apache.catalina.connector.Request里面有usingInputStream 和 usingReader兩個全局變量記錄流是否被讀取過
        //org.apache.catalina.connector.Request里面的parseParameters方法就是用來解析請求參數(shù)(Parse request parameters.)
        //在解析參數(shù)之前會有一個判斷,如果流被讀取過 則不再解析請求參數(shù) //
        // if (usingInputStream || usingReader) { 這是源碼里面的判斷
        //                success = true;
        //                return;
        //            }
        //如果先請求過一次后,那么org.apache.catalina.util.ParameterMap里面會有一個locked狀態(tài),如果讀過一次之后 會變成鎖定狀態(tài) 那么后面再讀都是讀取解析過后的map
        //    /**
        //     * The current lock state of this parameter 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) {

            }
        };
    }

	/**
     * 解析流
     * @param request
     * @return
     */
	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對象,使用自定義的

/**
 * @BelongsProject: 
 * @BelongsPackage: 
 * @Author: hef
 * @CreateTime: 2020-06-22 16:47
 * @Description: 描述
 */
@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);
        }
    }
}

七、總結(jié)

如果是想@RequestBody接收表單形式的參數(shù)也可以用此方法,處理起來更簡單 ,只需要實例化自定義處理器的時候傳入另外兩個個處理器就可以了

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

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

到這一步就已經(jīng)實現(xiàn)了RequestParam注解也可以接受Json格式數(shù)據(jù)了,我也沒進行更多的測試,具體還會出現(xiàn)什么關(guān)聯(lián)性的問題暫時是沒發(fā)現(xiàn)。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • JAVA面試篇之邏輯運算符與按位運算符的區(qū)別詳析

    JAVA面試篇之邏輯運算符與按位運算符的區(qū)別詳析

    運算符在Java編程中至關(guān)重要的作用不容忽視,它們廣泛應用于數(shù)學函數(shù)、賦值語句以及邏輯比較等多個方面,這篇文章主要介紹了JAVA面試篇之邏輯運算符與按位運算符區(qū)別的相關(guān)資料,需要的朋友可以參考下
    2025-08-08
  • 基于swagger測試List類型參數(shù)過程詳解

    基于swagger測試List類型參數(shù)過程詳解

    這篇文章主要介紹了基于swagger測試List類型參數(shù)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-09-09
  • mybatis中的緩存機制

    mybatis中的緩存機制

    這篇文章主要介紹了mybatis中的緩存機制用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • JAVA開發(fā)常用類庫UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實例詳解

    JAVA開發(fā)常用類庫UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實例詳

    這篇文章主要介紹了JAVA開發(fā)常用類庫UUID、Optional、ThreadLocal、TimerTask、Base64使用方法與實例詳解,需要的朋友可以參考下
    2020-02-02
  • Java反射(JDK)與動態(tài)代理(CGLIB)詳解

    Java反射(JDK)與動態(tài)代理(CGLIB)詳解

    下面小編就為大家?guī)硪黄獪\談Java反射與動態(tài)代理。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-08-08
  • java啟動時自定義配置文件路徑,自定義log4j2.xml位置方式

    java啟動時自定義配置文件路徑,自定義log4j2.xml位置方式

    這篇文章主要介紹了java啟動時自定義配置文件路徑,自定義log4j2.xml位置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Java Swing仿QQ登錄界面效果

    Java Swing仿QQ登錄界面效果

    這篇文章主要為大家詳細介紹了Java Swing仿QQ登錄界面效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Mybatis執(zhí)行update失敗的解決

    Mybatis執(zhí)行update失敗的解決

    這篇文章主要介紹了Mybatis執(zhí)行update失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • javaweb上傳下載實例完整版解析(下)

    javaweb上傳下載實例完整版解析(下)

    這篇文章主要為大家詳細解析了javaweb上傳下載實例,本文重點在于文件下載功能的實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 通過反射注解批量插入數(shù)據(jù)到DB的實現(xiàn)方法

    通過反射注解批量插入數(shù)據(jù)到DB的實現(xiàn)方法

    今天小編就為大家分享一篇關(guān)于通過反射注解批量插入數(shù)據(jù)到DB的實現(xiàn)方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03

最新評論

定南县| 尚义县| 开远市| 仪陇县| 峨眉山市| 玛曲县| 开远市| 廉江市| 宁乡县| 遂川县| 南昌县| 台北市| 玛多县| 项城市| 兴化市| 青浦区| 名山县| 永安市| 绥阳县| 安国市| 高密市| 若羌县| 惠安县| 武穴市| 吉木乃县| 麻城市| 视频| 新安县| 拜城县| 崇礼县| 蛟河市| 象山县| 西华县| 庆云县| 遵义市| 民勤县| 称多县| 琼结县| 曲沃县| 赞皇县| 肃北|