Spring/SpringBoot?@RequestParam注解無法讀取application/json格式數(shù)據(jù)問題解決
前言
Emmmm…最近在做項(xiàng)目的途中,有遇到一個(gè)方法需要接收的參數(shù)只有一個(gè)或者較少的時(shí)候就懶得寫實(shí)體類去接收,使用spring框架都知道,接收單個(gè)參數(shù)就使用@RequestParam注解就好了,但是前端對應(yīng)的Content-type是需要改成application/x-www-form-urlencoded,所以在接口文檔上面特地標(biāo)記了。但是…不知道前端是格式改了但是參數(shù)還是用的json格式?jīng)]有改成鍵值對的方式傳遞還是什么原因,就一直說參數(shù)傳不過來,叫我改回json格式的。。我也實(shí)在是懶,另外一個(gè)也覺得沒必要,就一兩個(gè)參數(shù)就新建一個(gè)實(shí)體,太浪費(fèi),但是這個(gè)問題讓我覺得不靈活蠻久了,也一直沒找到辦法,所以借這個(gè)機(jī)會(huì),打開了我的開發(fā)神器,www.baidu.com…輸入我的問題,找了好久也沒找到有解決的方案,然后就想著看下Spring內(nèi)部是怎么處理的吧,就稍微跟了下源碼,下面就說下我解決的方案。
一、RequestMappingHandlerAdapter
RequestMappingHandlerAdapter實(shí)現(xiàn)了HandlerAdapter接口,顧名思義,表示handler的adapter,這里的handler指的是Spring處理具體請求的某個(gè)Controller的方法,也就是說HandlerAdapter指的是將當(dāng)前請求適配到某個(gè)Handler的處理器。
RequestMappingHandlerAdapter是HandlerAdapter的一個(gè)具體實(shí)現(xiàn),主要用于將某個(gè)請求適配給@RequestMapping類型的Handler處理,這里面就包含著請求數(shù)據(jù)和響應(yīng)數(shù)據(jù)的處理。
// 這里可以獲取到處理程序方法參數(shù)解析器的一個(gè)列表 List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers()
如果是想處理響應(yīng)參數(shù)的話就使用
//這里可以獲取到處理程序方法返回值的處理器 List<HandlerMethodReturnValueHandler> originalHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
能獲取到這個(gè)列表了,那需要加入我們自己定義的處理器應(yīng)該不太麻煩了吧?(這里不講返回?cái)?shù)據(jù)的自定義策略處理,網(wǎng)上也有其他文章,如果需要可以找下)
二、HandlerMethodArgumentResolver
策略接口解決方法參數(shù)代入?yún)?shù)值在給定請求的上下文(翻譯的源碼注釋)
簡單的理解為:它負(fù)責(zé)處理你Handler方法里的所有入?yún)ⅲ喊ㄗ詣?dòng)封裝、自動(dòng)賦值、校驗(yàn)等等。
——————————————————————————————————————————
那么這個(gè)時(shí)候我已經(jīng)知道了第一步獲取到的那個(gè)列表中存放的類型是什么了,簡而言之,我們只需要實(shí)現(xiàn)這個(gè)策略類,編寫我們自己的算法或邏輯就行了
這個(gè)接口里面有兩個(gè)方法需要實(shí)現(xiàn):
第一個(gè)方法的作用:是否與給定方法的參數(shù)是由該解析器的支持。(如果返回true,那么就使用該類進(jìn)行參數(shù)轉(zhuǎn)換,如果返回false,那么繼續(xù)找下一個(gè)策略類)
第二個(gè)方法的作用:解決方法參數(shù)成從給定請求的自變量值。 由WebDataBinderFactory提供了一個(gè)方法來創(chuàng)建一個(gè)WebDataBinder所需數(shù)據(jù)綁定和類型轉(zhuǎn)換目的時(shí)實(shí)例。(簡單來講,就是轉(zhuǎn)換參數(shù)值的,返回的就是解析的參數(shù)值)
三、RequestParamMethodArgumentResolver
這個(gè)類就是用來處理Controller的方法上有加@RequestParam注解的具體處理器。
首先會(huì)調(diào)用這個(gè)方法來確定是否使用這個(gè)處理器解析參數(shù),那么我們也看到了,如果參數(shù)有RequestParam注解,那么則會(huì)使用該類進(jìn)行處理,那么我們能不能效仿呢?
四、MyHandlerMethodArgumentResolver
這個(gè)沒啥好說,就自己定義的參數(shù)解析器。
直接上代碼吧
/** * @BelongsProject: * @BelongsPackage: * @Author: hef * @CreateTime: 2020-06-20 18:49 * @Description: 描述 */ public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { /** * 這個(gè)是處理@RequestParam注解的原本策略類 */ private RequestParamMethodArgumentResolver requestParamMethodArgumentResolver; /** * 全參構(gòu)造 */ public MyHandlerMethodArgumentResolver(RequestParamMethodArgumentResolver requestParamMethodArgumentResolver) { this.requestParamMethodArgumentResolver = requestParamMethodArgumentResolver; } /** * 當(dāng)參數(shù)前有@RequestParam注解時(shí),會(huì)使用此 解析器 * <p> * 注:此方法的返回值將決定:是否使用此解析器解析該參數(shù) */ @Override public boolean supportsParameter(MethodParameter methodParameter) { //很明顯,就是判斷是否有這個(gè)注解 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(); //因?yàn)閖son數(shù)據(jù)是放在流里面,所以要去讀取流, //但是ServletRequest的getReader()和getInputStream()兩個(gè)方法只能被調(diào)用一次,而且不能兩個(gè)都調(diào)用。 //所以這里是需要寫個(gè)自定義的HttpServletRequestWrapper,主要功能就是需要重復(fù)讀取流數(shù)據(jù) String read = getRead(request.getReader()); //轉(zhuǎn)換json JSONObject jsonObject = JSON.parseObject(read); Object o1; if (jsonObject == null) { //這里有一個(gè)可能性就是比如get請求,參數(shù)是拼接在URL后面,但是如果我們還是去讀流里面的數(shù)據(jù)就會(huì)讀取不到 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啟動(dòng)的時(shí)候執(zhí)行 @PostConstruct private void addArgumentResolvers() { // 獲取到框架定義好的參數(shù)解析集合 List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver = getMyHandlerMethodArgumentResolver(argumentResolvers); // ha.getArgumentResolvers()獲取到的是不可變的集合,所以我們需要新建一個(gè)集合來放置參數(shù)解析器 List<HandlerMethodArgumentResolver> myArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1); //這里有一個(gè)注意點(diǎn)就是自定義的處理器需要放在RequestParamMethodArgumentResolver前面 //為什么呢?因?yàn)槿绻旁谒竺娴脑?那么它已經(jīng)處理掉了,就到不了我們自己定義的策略里面去了 //所以直接把自定義的策略放在第一個(gè),穩(wěn)妥! // 將自定義的解析器,放置在第一個(gè); 并保留原來的解析器 myArgumentResolvers.add(myHandlerMethodArgumentResolver); myArgumentResolvers.addAll(argumentResolvers); //再把新的集合設(shè)置進(jìn)去 requestMappingHandlerAdapter.setArgumentResolvers(myArgumentResolvers); } /** * 獲取MyHandlerMethodArgumentResolver實(shí)例 */ 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) { // 因?yàn)樵谖覀冏约翰呗岳锩媸沁€需要用到這個(gè)原本的類的,所以需要得到這個(gè)對象實(shí)例 requestParamMethodArgumentResolver = (RequestParamMethodArgumentResolver) argumentResolver; } } if (requestParamMethodArgumentResolver == null) { throw new RuntimeException("RequestParamMethodArgumentResolver not be null!"); } //實(shí)例化自定義參數(shù)解析器 return new MyHandlerMethodArgumentResolver(requestParamMethodArgumentResolver); } }
五、MyHttpServletRequestWrapper
這個(gè)就是自定義的HttpServletRequest,保證可以重復(fù)獲取到流數(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); //在讀取流之前獲取一次這個(gè)parameterMap,否則讀取流后無法再解析出數(shù)據(jù), // 原因是org.apache.catalina.connector.Request里面有usingInputStream 和 usingReader兩個(gè)全局變量記錄流是否被讀取過 //org.apache.catalina.connector.Request里面的parseParameters方法就是用來解析請求參數(shù)(Parse request parameters.) //在解析參數(shù)之前會(huì)有一個(gè)判斷,如果流被讀取過 則不再解析請求參數(shù) // // if (usingInputStream || usingReader) { 這是源碼里面的判斷 // success = true; // return; // } //如果先請求過一次后,那么org.apache.catalina.util.ParameterMap里面會(huì)有一個(gè)locked狀態(tài),如果讀過一次之后 會(huì)變成鎖定狀態(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ù)也可以用此方法,處理起來更簡單 ,只需要實(shí)例化自定義處理器的時(shí)候傳入另外兩個(gè)個(gè)處理器就可以了
/** * 解析Content-Type為application/json的默認(rèn)解析器是RequestResponseBodyMethodProcessor */ private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor; /** * 解析Content-Type為application/x-www-form-urlencoded的默認(rèn)解析器是ServletModelAttributeMethodProcessor */ private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;
到這一步就已經(jīng)實(shí)現(xiàn)了RequestParam注解也可以接受Json格式數(shù)據(jù)了,我也沒進(jìn)行更多的測試,具體還會(huì)出現(xiàn)什么關(guān)聯(lián)性的問題暫時(shí)是沒發(fā)現(xiàn),后續(xù)如果有碼友出現(xiàn)了什么問題可以留言一起討論,本人小菜雞一枚,希望寫的不好的地方大神多多指教,不勝感激!
總結(jié)
到此這篇關(guān)于Spring/SpringBoot @RequestParam注解無法讀取application/json格式數(shù)據(jù)問題解決的文章就介紹到這了,更多相關(guān)@RequestParam注解無法讀取application/json內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis的mapper.xml文件中入?yún)⒑头祷刂档膶?shí)現(xiàn)
這篇文章主要介紹了MyBatis的mapper.xml文件中入?yún)⒑头祷刂档膶?shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01JAVA開發(fā)環(huán)境Vs?code配置步驟詳解
這篇文章主要為大家介紹了JAVA開發(fā)環(huán)境Vs?code配置步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04java通過isAccessAllowed方法實(shí)現(xiàn)訪問控制
在Web應(yīng)用開發(fā)中,使用Apache Shiro框架的isAccessAllowed方法可以有效管理用戶的訪問權(quán)限,本文詳細(xì)解析了該方法的實(shí)現(xiàn)過程,包括用戶身份驗(yàn)證、權(quán)限判斷和安全性分析,下面就一起來了解一下2024-09-09java8如何根據(jù)某一屬性條件快速篩選list中的集合
這篇文章主要介紹了java8如何根據(jù)某一屬性條件快速篩選list中的集合,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01springboot validator枚舉值校驗(yàn)功能實(shí)現(xiàn)
這篇文章主要介紹了springboot validator枚舉值校驗(yàn)功能實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Java?設(shè)計(jì)模式以虹貓藍(lán)兔的故事講解單例模式
單例模式(Singleton?Pattern)是?Java?中最簡單的設(shè)計(jì)模式之一。這種類型的設(shè)計(jì)模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式2022-03-03mybatisplus實(shí)現(xiàn)自動(dòng)創(chuàng)建/更新時(shí)間的項(xiàng)目實(shí)踐
Mybatis-Plus提供了自動(dòng)填充功能,可以通過實(shí)現(xiàn)MetaObjectHandler接口來實(shí)現(xiàn)自動(dòng)更新時(shí)間的功能,本文就來介紹一下mybatisplus實(shí)現(xiàn)自動(dòng)創(chuàng)建/更新時(shí)間的項(xiàng)目實(shí)踐,感興趣的可以了解下2024-01-01java可變參數(shù)(不定向參數(shù))的作用與實(shí)例
這篇文章主要給大家介紹了關(guān)于java可變參數(shù)(不定向參數(shù))的作用與實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04