從http中獲取請求參數的方法,Spring源碼解析 -- SpringWeb請求參數獲取解析

 2023-10-12 阅读 30 评论 0

摘要:Spring源碼解析 – SpringWeb請求參數獲取解析 簡介 在文章:Spring Web 請求初探中,我們看到最后方法反射調用的相關代碼,本篇文章就探索其中的參數是如何從請求中獲取的 概覽 方法反射調用的關鍵代碼如下: public class InvocableHandlerMethod

Spring源碼解析 – SpringWeb請求參數獲取解析


簡介

在文章:Spring Web 請求初探中,我們看到最后方法反射調用的相關代碼,本篇文章就探索其中的參數是如何從請求中獲取的

概覽

方法反射調用的關鍵代碼如下:

public class InvocableHandlerMethod {protected Object doInvoke(Object... args) throws Exception {Method method = getBridgedMethod();ReflectionUtils.makeAccessible(method);try {if (KotlinDetector.isSuspendingFunction(method)) {return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);}return method.invoke(getBean(), args);}catch (IllegalArgumentException ex) {......}}
}

現在我們更新我們的代碼如下,增加接個參數:

import com.example.springexample.vo.User;
import org.springframework.web.bind.annotation.*;@RestController
public class HelloWorld {@GetMapping("/")public String helloWorld(@RequestParam(value = "id") Integer id,@RequestParam(value = "name") String name) {return "Hello world:" + id;}@GetMapping("/test1")public String helloWorld1(@RequestHeader(value = "id") Integer id) {return "Hello world:" + id;}@PostMapping("/test2")public String helloWorld2(@RequestBody User user) {return "Hello world:" + user.toString();}
}

發起下面的請求,在上面反射調用的代碼中打上斷點,跟蹤調用棧分析:

GET http://localhost:8080/?id=1&id=2POST http://localhost:8080/test2
Content-Type: application/json{"name": "name", "password": "password"}

源碼解析

首先查看調用棧,我們找到了參數最開始獲取的相關代碼,在下面的代碼中獲取相關的參數:

	# InvocableHandlerMethod.java@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 得到了相關的參數Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}return doInvoke(args);}

從http中獲取請求參數的方法?繼續跟蹤看看函數:getMethodArgumentValues

	# InvocableHandlerMethod.javaprotected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 這里獲得了參數信息列表,比如類型之類的MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);// 這里進行了一次參數查找,但沒有匹配上args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {// 最終在這里匹配上了參數args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}

在上面的代碼中,我們可以看到大致下面的邏輯:

  • 獲取方法調用所需的參數信息
  • 嘗試獲取具體參數值:findProvidedArgument
  • 嘗試獲取具體參數值:this.resolvers.resolveArgument
  • 拋出未獲取異常

findProvidedArgument 沒怎么看懂,日后在看下

我們先跟著函數:this.resolvers.resolveArgument

	# HandlerMethodArgumentResolverComposite.java@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 獲取參數解析器HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" +parameter.getParameterType().getName() + "]. supportsParameter should be called first.");}// 返回解析結果return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// 首先查找緩存,如果緩存不存在則遍歷查找參數解析器列表HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}

在上面的函數中,首先取獲取對應參數的解析器,關鍵函數就是: supportsParameter

解析器目前調試發現有32個,本地請求:GET http://localhost:8080?id=1&id=2 匹配上的是: RequestParamMethodArgumentResolver.java ,其判斷的代碼如下:

	# RequestParamMethodArgumentResolver.java@Overridepublic boolean supportsParameter(MethodParameter parameter) {if (parameter.hasParameterAnnotation(RequestParam.class)) {if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);return (requestParam != null && StringUtils.hasText(requestParam.name()));}else {return true;}}else {if (parameter.hasParameterAnnotation(RequestPart.class)) {return false;}parameter = parameter.nestedIfOptional();if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {return true;}else if (this.useDefaultResolution) {return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());}else {return false;}}}

請求參數是什么、大致就是看看是否有相關的注解、符合要求之類的,這部分就不細看

我們繼續看看具體參數獲取處理:

	# AbstractNamedValueMethodArgumentResolver.java@Override@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 獲取參數名稱,GET http://localhost:8080?id=1&id=2 就是 idNamedValueInfo namedValueInfo = getNamedValueInfo(parameter);// 方法調用的信息,如類、參數類型之類的MethodParameter nestedParameter = parameter.nestedIfOptional();// 看著想是參數效驗?沒完全看懂,先跳過Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}// 獲取參數值Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {if (namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);}// 在下面的函數中會進行參數轉換// 比如在請求:GET http://localhost:8080?id=1&id=2// 得到的結果是一個數組,有兩個值,但轉換后直接就得到了 1// 這部分的細節日后回過頭再來看if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}// Check for null value after conversion of incoming argument valueif (arg == null && namedValueInfo.defaultValue == null &&namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}

上面的函數是一處關鍵代碼:

  • 獲取參數名稱
  • 獲取參數信息,如類型之類的
  • 進行具體參數值
  • 進行參數轉換

其中參數轉換還比較麻煩,粗略看了下細節還是比較多,本地先跳過,后面再過來看

我們繼續跟蹤獲取具體參數值的相關函數: resolveName(resolvedName.toString(), nestedParameter, webRequest);

	# RequestParamMethodArgumentResolver.java@Override@Nullableprotected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);if (servletRequest != null) {Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {return mpArg;}}Object arg = null;// 看著像是獲取文件?MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);if (multipartRequest != null) {List<MultipartFile> files = multipartRequest.getFiles(name);if (!files.isEmpty()) {arg = (files.size() == 1 ? files.get(0) : files);}}if (arg == null) {// 獲取參數名中請求中對應的值// http://localhost:8080?id=1&id=2 就得到了 [1, 2]String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;}

在上面的代碼中,我們可以看到參數值的獲取是先去 multipartRequest 中獲取,如果為空再到函數: getParameterValues 中獲取

java解析yaml。看得好像是文件優先?是不是同名的話,就優先獲取文件了?這個大家也可以試下

繼續跟蹤函數: getParameterValues

    # Parameters.javapublic String[] getParameterValues(String name) {handleQueryParameters();// no "facade"ArrayList<String> values = paramHashValues.get(name);if (values == null) {return null;}return values.toArray(new String[0]);}

跟蹤下去就是Tomcat的函數處理了,感覺很有類似自己寫Tcp服務時自定義解析協議的感覺,這部分細節就不細看了

到這里就得到最終的結果: [1, 2]

經過: arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); 得到了最后的值: 1

總結

本篇文章中,我們大致探索了請求參數的獲取源碼,大致有下面的步驟:

  • 獲取所需的參數列表,其中有一些參數的信息,比如類型,進行遍歷獲取其值
  • findProvidedArgument 匹配獲取,這部分功能未探索
  • this.resolvers.resolveArgument :參數解析器獲取
    • 遍歷所有的解析器
    • 得到對應的解析器
    • 得到對應的值
    • 值的轉換

java解析json報文、期間,我們實驗兩種請求:

GET http://localhost:8080/?id=1&id=2POST http://localhost:8080/test2
Content-Type: application/json{"name": "name", "password": "password"}

兩個請求的參數解析器是不一樣的,但也是我們日常開發中常用的,比如第二個,但本篇文章中并沒有具體解析,大家感興趣可以自行根據本篇的思路進行解析

參考鏈接

  • Spring 源碼解析系列

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/135553.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息