Spring/SpringBoot?@RequestParam注解無法讀取application/json格式數(shù)據(jù)問題解決
前言
Emmmm…最近在做項目的途中,有遇到一個方法需要接收的參數(shù)只有一個或者較少的時候就懶得寫實體類去接收,使用spring框架都知道,接收單個參數(shù)就使用@RequestParam注解就好了,但是前端對應(yīng)的Content-type是需要改成application/x-www-form-urlencoded,所以在接口文檔上面特地標(biāo)記了。但是…不知道前端是格式改了但是參數(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指的是將當(dāng)前請求適配到某個Handler的處理器。
RequestMappingHandlerAdapter是HandlerAdapter的一個具體實現(xiàn),主要用于將某個請求適配給@RequestMapping類型的Handler處理,這里面就包含著請求數(shù)據(jù)和響應(yīng)數(shù)據(jù)的處理。
// 這里可以獲取到處理程序方法參數(shù)解析器的一個列表
List<HandlerMethodArgumentResolver> argumentResolvers =
requestMappingHandlerAdapter.getArgumentResolvers()
如果是想處理響應(yīng)參數(shù)的話就使用
//這里可以獲取到處理程序方法返回值的處理器
List<HandlerMethodReturnValueHandler> originalHandlers =
requestMappingHandlerAdapter.getReturnValueHandlers();
能獲取到這個列表了,那需要加入我們自己定義的處理器應(yīng)該不太麻煩了吧?(這里不講返回數(shù)據(jù)的自定義策略處理,網(wǎng)上也有其他文章,如果需要可以找下)
二、HandlerMethodArgumentResolver
策略接口解決方法參數(shù)代入?yún)?shù)值在給定請求的上下文(翻譯的源碼注釋)

簡單的理解為:它負責(zé)處理你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;
}
/**
* 當(dāng)參數(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,主要功能就是需要重復(fù)讀取流數(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,保證可以重復(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);
//在讀取流之前獲取一次這個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),后續(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)⒑头祷刂档膶崿F(xiàn)
這篇文章主要介紹了MyBatis的mapper.xml文件中入?yún)⒑头祷刂档膶崿F(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
JAVA開發(fā)環(huán)境Vs?code配置步驟詳解
這篇文章主要為大家介紹了JAVA開發(fā)環(huán)境Vs?code配置步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04
java通過isAccessAllowed方法實現(xiàn)訪問控制
在Web應(yīng)用開發(fā)中,使用Apache Shiro框架的isAccessAllowed方法可以有效管理用戶的訪問權(quán)限,本文詳細解析了該方法的實現(xiàn)過程,包括用戶身份驗證、權(quán)限判斷和安全性分析,下面就一起來了解一下2024-09-09
java8如何根據(jù)某一屬性條件快速篩選list中的集合
這篇文章主要介紹了java8如何根據(jù)某一屬性條件快速篩選list中的集合,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
springboot validator枚舉值校驗功能實現(xiàn)
這篇文章主要介紹了springboot validator枚舉值校驗功能實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
mybatisplus實現(xiàn)自動創(chuàng)建/更新時間的項目實踐
Mybatis-Plus提供了自動填充功能,可以通過實現(xiàn)MetaObjectHandler接口來實現(xiàn)自動更新時間的功能,本文就來介紹一下mybatisplus實現(xiàn)自動創(chuàng)建/更新時間的項目實踐,感興趣的可以了解下2024-01-01
java可變參數(shù)(不定向參數(shù))的作用與實例
這篇文章主要給大家介紹了關(guān)于java可變參數(shù)(不定向參數(shù))的作用與實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

