• SpringBoot 请求参数解析全过程


    1、图解请求过程

    在这里插入图片描述

     

    • 上述是SpringMVC完整的请求过程,经过三次TCP/IP的握手之后来到请求。而请求参数的解析主要是在第5、6步完成的。

    • 前面几个步骤的过程可以参考这里,下面主要介绍请求参数是如何解析获得的。

    2、DispatcherServlet整体调度

    1. public class DispatcherServlet extends FrameworkServlet {
    2. @Override
    3. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    4. ...
    5. try {
    6. doDispatch(request, response);
    7. }
    8. finally {
    9. ...
    10. }
    11. }
    12. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    13. HttpServletRequest processedRequest = request;
    14. HandlerExecutionChain mappedHandler = null;
    15. boolean multipartRequestParsed = false; //判断是否是文件上传
    16. try {
    17. ModelAndView mv = null; //响应的视图
    18. Exception dispatchException = null;
    19. try {
    20. // 1. 检查是否是文件上传
    21. processedRequest = checkMultipart(request);
    22. multipartRequestParsed = (processedRequest != request);
    23. // 2. 找到映射器处理器MappingHandler
    24. mappedHandler = getHandler(processedRequest);
    25. if (mappedHandler == null) {
    26. noHandlerFound(processedRequest, response);
    27. return;
    28. }
    29. // 3. 找到处理器的适配器
    30. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    31. // 4. 拦截器的前置PreHandle方法执行
    32. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    33. return;
    34. }
    35. // 5. 获取参数、执行controller
    36. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    37. // 6. 使用默认的视图名进行封装(首页)
    38. applyDefaultViewName(processedRequest, mv);
    39. // 7. 执行拦截器的后置postHandle方法
    40. mappedHandler.applyPostHandle(processedRequest, response, mv);
    41. // 8. 对执行结果的参数、视图,内部执行拦截器的afterCompletion方法
    42. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    43. }
    44. ...
    45. ...
    46. }
    1. 前面几个步骤没啥用,主要是第(2)步找到Controller.Method方法

    2. 然后开始为这个Method方法找到合适的适配器进行处理,SpringBoot一启动就自动配置了5个映射器,4个适配器。

    3. 在这里插入图片描述

      最后参数解析、Controller.Method的执行都在第(5)步完成。

    4. 3. RequestMappingHandlerAdapter适配器先行一步

    5. 这里需要注意适配器是不唯一的,但是他们都是AbstractHandlerMethodAdapter抽象类的实现,这里以@RequestMapping标注的方法为事例。

       

      1. public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
      2. implements BeanFactoryAware, InitializingBean {
      3. protected ModelAndView handleInternal(HttpServletRequest request,
      4. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
      5. ModelAndView mav;
      6. checkRequest(request);
      7. // Execute invokeHandlerMethod in synchronized block if required.
      8. if (this.synchronizeOnSession) {
      9. HttpSession session = request.getSession(false);
      10. if (session != null) {
      11. Object mutex = WebUtils.getSessionMutex(session);
      12. synchronized (mutex) {
      13. mav = invokeHandlerMethod(request, response, handlerMethod);
      14. }
      15. }
      16. else {
      17. // No HttpSession available -> no mutex necessary
      18. mav = invokeHandlerMethod(request, response, handlerMethod);
      19. }
      20. }
      21. else {
      22. // No synchronization on session demanded at all...
      23. mav = invokeHandlerMethod(request, response, handlerMethod);
      24. }
      25. ....
      26. return mav;
      27. }
      28. @Nullable
      29. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      30. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
      31. ServletWebRequest webRequest = new ServletWebRequest(request, response);
      32. try {
      33. ...
      34. // 1. 创建一个请求 - 处理对象
      35. ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
      36. // 2. 设置参数解析器(核心)
      37. if (this.argumentResolvers != null) {
      38. invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
      39. }
      40. // 3. 设置返回值处理器(Model、ModelAndView、@ResponseBody等等)
      41. if (this.returnValueHandlers != null) {
      42. invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      43. }
      44. // 这里还有一系列的初始化东西,在这里省略。
      45. ...
      46. // 4. 执行和处理。
      47. invocableMethod.invokeAndHandle(webRequest, mavContainer);
      48. if (asyncManager.isConcurrentHandlingStarted()) {
      49. return null;
      50. }
      51. return getModelAndView(mavContainer, modelFactory, webRequest);
      52. }
      53. finally {
      54. webRequest.requestCompleted();
      55. }
      56. }
      57. }

    6. 可以看到核心方法invokeHandlerMethod就是这个,但是这里经过了一系列的针对Session有效的验证。

    7. 到这里还没有进行参数的解析,只是配置了默认情况下所有的参数解析器(27个左右)和 返回值处理器(15个左右)

    8. 在这里插入图片描述

      中间还有一系列的东西需要配置、初始化等等,最后再交给invokeAndHandle(webRequest, mavContainer)方法去完成处理

    4、ServletInvocableHandlerMethod 

    1. public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    2. public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    3. // 1. 获取返回值
    4. Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    5. // 2. 设置响应状态
    6. setResponseStatus(webRequest);
    7. }
    8. }
    1. 可以看到第(2)步已经开始进行响应状态的设置,所以可以猜到到这里请求已经结束了但是没有响应。

    2. 根据上面的推测,那么参数解析、执行都藏匿在invokeForRequest()方法中。

    5、InvocableHandlerMethod

    1. public class InvocableHandlerMethod extends HandlerMethod {
    2. @Nullable
    3. public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    4. Object... providedArgs) throws Exception {
    5. Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    6. if (logger.isTraceEnabled()) {
    7. logger.trace("Arguments: " + Arrays.toString(args));
    8. }
    9. return doInvoke(args);
    10. }
    11. }
    12. // 最终幕后
    13. protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    14. Object... providedArgs) throws Exception {
    15. // 1. 先获取到所有的参数
    16. MethodParameter[] parameters = getMethodParameters();
    17. if (ObjectUtils.isEmpty(parameters)) {
    18. return EMPTY_ARGS;
    19. }
    20. Object[] args = new Object[parameters.length];
    21. for (int i = 0; i < parameters.length; i++) {
    22. MethodParameter parameter = parameters[i];
    23. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    24. args[i] = findProvidedArgument(parameter, providedArgs);
    25. if (args[i] != null) {
    26. continue;
    27. }
    28. if (!this.resolvers.supportsParameter(parameter)) {
    29. throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    30. }
    31. try {
    32. args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    33. }
    34. catch (Exception ex) {
    35. ...
    36. }
    37. }
    38. return args;
    39. }
    1. 这里好像也没有看到什么实质性的东西,只有getMethodArgumentValues()方法。

    2. 而所有的东西最后都在这个方法里面可以看到。

    5.1、请求样例

    首先给出如下的请求样例,便于Debug参数是如何解析出来的。

    1. <a href="/param?username=admin&password=123456">测试@RequestParam</a>
    2. <br/><br/>
    3. @RequestMapping("/param")
    4. @ResponseBody
    5. public Map<String, Object> RequestParamTest(@RequestParam("username") String name,
    6. @RequestParam("password") String pwd,
    7. @RequestParam Map<String,String> m){
    8. Map<String,Object> map = new HashMap<>();
    9. map.put("name", name);
    10. map.put("pwd", pwd);
    11. map.put("allParam", m);
    12. return map;
    13. }

    5.2、获取形参名字

    1. // 1. 先获取到所有的参数
    2. MethodParameter[] parameters = getMethodParameters();
    3. if (ObjectUtils.isEmpty(parameters)) {
    4. return EMPTY_ARGS;
    5. }

    在这里插入图片描述

     

    1. 这里获取所有参数的名字,并且存放在一个数组里面,后面需要使用到。

    2. 如果是没有参数的请求,那么会直接返回空参数EMPTY_ARGS。

    5.3、为参数分配空间并初始化

    1. Object[] args = new Object[parameters.length];
    2. for (int i = 0; i < parameters.length; i++) {
    3. MethodParameter parameter = parameters[i];
    4. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    5. args[i] = findProvidedArgument(parameter, providedArgs);
    6. if (args[i] != null) {
    7. continue;
    8. }
    9. ...
    10. }
    1. 先为每个参数值分配一个Object对象,多个参数值就形成一个数组。

    2. 先初始化参数名字的发现器、找到供应参数之类的东西。

    5.4、参数解析器的适配支持

    1. if (!this.resolvers.supportsParameter(parameter)) {
    2. throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    3. }

    这里主要检查第(3)步中提供的27个resolvers解析器是否有满足解析当前参数的。

    1. 如果没有将会抛出异常。直接结束请求。

    2. 如果存在会先将适配的参数解析器丢入第(3)步中提到的解析器缓存,这样便于下次使用的时候取,而不是每次都增强for循环每次都去适配一次。

    1. public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    2. @Nullable
    3. private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    4. // 1. 如果缓存中有直接获取
    5. HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    6. // 2. 如果没有,增强for循环去适配。
    7. if (result == null) {
    8. for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    9. if (resolver.supportsParameter(parameter)) {
    10. result = resolver;
    11. this.argumentResolverCache.put(parameter, result);
    12. break;
    13. }
    14. }
    15. }
    16. return result;
    17. }
    18. }

    判断是否是当前参数的解析器很简单:

    1. 前面几步已经说明注解开始生效了,那么判断的时候直接根据注解类型与当前参数解析器提供的注解是否一致,以及参数类型是否匹配的问题。

    2. 注:这里的参数类型匹配主要是区分Map和非Map参数,对于Map参数提供了单独的参数解析器。

    5.5、开始解析

    1. public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    2. @Override
    3. @Nullable
    4. public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    5. NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    6. // 缓存里面直接获取
    7. HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    8. if (resolver == null) {
    9. throw new IllegalArgumentException("Unsupported parameter type [" +
    10. parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    11. }
    12. return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    13. }
    14. }

    在这里插入图片描述

    5.6、解析并赋值 

    1. public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
    2. @Override
    3. @Nullable
    4. public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    5. NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    6. // 1. 参数名信息
    7. NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    8. MethodParameter nestedParameter = parameter.nestedIfOptional();
    9. Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    10. if (resolvedName == null) {
    11. throw new IllegalArgumentException(
    12. "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    13. }
    14. // 2. 参数值
    15. Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    16. if (arg == null) {
    17. if (namedValueInfo.defaultValue != null) {
    18. arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
    19. }
    20. else if (namedValueInfo.required && !nestedParameter.isOptional()) {
    21. handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
    22. }
    23. arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
    24. }
    25. else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
    26. arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
    27. }
    28. ...
    29. handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    30. return arg;
    31. }
    32. }

    在这里插入图片描述

    1. 先解析出请求URL中的名字,并且验证名字的合法性之类的。

    2. 解析参数的值,如果没有解析到根据注解是否有提供默认的值…

    3. 最后每个参数都这样解析出来,全部解析出来之后走到第(4)步,最后给出响应!

    6、原生Servlet参数解析

    原生的Servlet参数解析也是一样的,只不过解析器不同。所有的解析器大概分成以下几类:

    注解参数还是Servlet API的解析器。

    注解的参数解析器又分为map类型参数、非map类型参数。

    Servlet API的大部分都是由一个参数解析器ServletRequestMethodArgumentResolver类进行解析的。
     

    1. public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
    2. @Override
    3. public boolean supportsParameter(MethodParameter parameter) {
    4. Class<?> paramType = parameter.getParameterType();
    5. return (WebRequest.class.isAssignableFrom(paramType) ||
    6. ServletRequest.class.isAssignableFrom(paramType) ||
    7. MultipartRequest.class.isAssignableFrom(paramType) ||
    8. HttpSession.class.isAssignableFrom(paramType) ||
    9. (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
    10. (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
    11. InputStream.class.isAssignableFrom(paramType) ||
    12. Reader.class.isAssignableFrom(paramType) ||
    13. HttpMethod.class == paramType ||
    14. Locale.class == paramType ||
    15. TimeZone.class == paramType ||
    16. ZoneId.class == paramType);
    17. }
    18. }

     

  • 相关阅读:
    企业建设数字化工厂的四个要点
    项目部署之Jenkins
    linux设备树节点添加新的复位属性之后设备驱动加载异常问题分析
    数据分享|R语言生态学种群空间点格局分析:聚类泊松点过程对植物、蚂蚁巢穴分布数据可视化...
    OMV6 安装Extras 插件失败的解决方法
    大数据精准营销一站式解决你的获客难题
    Hive主要介绍
    什么是AI开源大模型宇宙?
    java-php-net-python-代驾网站计算机毕业设计程序
    ICDE2023 | VEND:基于点编码的边存在性判定
  • 原文地址:https://blog.csdn.net/tumu6889/article/details/125329118