解決Spring/SpringBoot @RequestParam注解無法讀取application/json格式數(shù)據(jù)問題
Emmmm…最近在做項(xiàng)目的途中,有遇到一個(gè)方法需要接收的參數(shù)只有一個(gè)或者較少的時(shí)候就懶得寫實(shí)體類去接收,使用spring框架都知道,接收單個(gè)參數(shù)就使用@RequestParam注解就好了,但是前端對(duì)應(yīng)的Content-type是需要改成application/x-www-form-urlencoded,所以在接口文檔上面特地標(biāo)記了。
但是…不知道前端是格式改了但是參數(shù)還是用的json格式?jīng)]有改成鍵值對(duì)的方式傳遞還是什么原因,就一直說參數(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處理具體請(qǐng)求的某個(gè)Controller的方法,也就是說HandlerAdapter指的是將當(dāng)前請(qǐng)求適配到某個(gè)Handler的處理器。
RequestMappingHandlerAdapter是HandlerAdapter的一個(gè)具體實(shí)現(xiàn),主要用于將某個(gè)請(qǐng)求適配給@RequestMapping類型的Handler處理,這里面就包含著請(qǐng)求數(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ù)值在給定請(qǐng)求的上下文(翻譯的源碼注釋)

簡單的理解為:它負(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ù)成從給定請(qǐng)求的自變量值。 由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請(qǐng)求,參數(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è)對(duì)象實(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方法就是用來解析請(qǐng)求參數(shù)(Parse request parameters.)
//在解析參數(shù)之前會(huì)有一個(gè)判斷,如果流被讀取過 則不再解析請(qǐng)求參數(shù) //
// if (usingInputStream || usingReader) { 這是源碼里面的判斷
// success = true;
// return;
// }
//如果先請(qǐng)求過一次后,那么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對(duì)象,使用自定義的
/**
* @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)行更多的測(cè)試,具體還會(huì)出現(xiàn)什么關(guān)聯(lián)性的問題暫時(shí)是沒發(fā)現(xiàn)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot中@RequestParam和@PathVariable區(qū)別
- SpringBoot中@PathVariable、@RequestParam和@RequestBody的區(qū)別和使用詳解
- Springboot中@RequestParam和@PathVariable的用法與區(qū)別詳解
- SpringBoot傳遞單一參數(shù)時(shí)@RequestParam和@RequestBody的區(qū)別小結(jié)
- Spring/SpringBoot?@RequestParam注解無法讀取application/json格式數(shù)據(jù)問題解決
- 解決Springboot 2 的@RequestParam接收數(shù)組異常問題
- SpringBoot @RequestParam、@PathVaribale、@RequestBody實(shí)戰(zhàn)案例
相關(guān)文章
Spring?Boot?配置文件類型properties?格式與yml?格式
這篇文章主要介紹了Spring?Boot?配置文件類型properties?格式與yml?格式,文章圍繞主題展開詳細(xì)內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05
手動(dòng)模擬JDK動(dòng)態(tài)代理的方法
這篇文章主要介紹了手動(dòng)模擬JDK動(dòng)態(tài)代理的方法,幫助大家更好的了解和學(xué)習(xí)Java 代理的相關(guān)知識(shí),感興趣的朋友可以了解下2020-11-11
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之時(shí)間復(fù)雜度與空間復(fù)雜度
對(duì)于一個(gè)算法,其時(shí)間復(fù)雜度和空間復(fù)雜度往往是相互影響的,當(dāng)追求一個(gè)較好的時(shí)間復(fù)雜度時(shí),可能會(huì)使空間復(fù)雜度的性能變差,即可能導(dǎo)致占用較多的存儲(chǔ)空間,這篇文章主要給大家介紹了關(guān)于Java時(shí)間復(fù)雜度、空間復(fù)雜度的相關(guān)資料,需要的朋友可以參考下2022-02-02
IDEA運(yùn)行Java項(xiàng)目報(bào)錯(cuò)java: 錯(cuò)誤: 不支持發(fā)行版本 xx的解決方法
這篇文章主要介紹了IDEA運(yùn)行Java項(xiàng)目報(bào)錯(cuò)java: 錯(cuò)誤: 不支持發(fā)行版本 xx的解決方法,文中有詳細(xì)的解決方案供大家參考,對(duì)大家解決問題有一定的幫助,需要的朋友可以參考下2025-04-04
詳解Java的Hibernate框架中的List映射表與Bag映射
這篇文章主要介紹了Java的Hibernate框架中的List映射表與Bag映射,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
SpringBoot實(shí)現(xiàn)快遞物流查詢功能(快遞鳥)
本文將基于springboot2.4.0實(shí)現(xiàn)快遞物流查詢,物流信息的獲取通過快遞鳥第三方實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-10-10
JDBC的擴(kuò)展知識(shí)點(diǎn)總結(jié)
這篇文章主要介紹了JDBC的擴(kuò)展知識(shí)點(diǎn)總結(jié),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)JDBC的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05
Java concurrency之Condition條件_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Condition的作用是對(duì)鎖進(jìn)行更精確的控制。下面通過本文給大家分享Java concurrency之Condition條件的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06

