Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程

Posted 说好不能打脸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程相关的知识,希望对你有一定的参考价值。

接上文《Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程(2)

3、多种HandlerAdapter执行适配器

上一篇文章中我们介绍了Spring MVC组件提供的多种映射处理器(HandlerMapping),但是映射处理器只是负责基于URL信息匹配合适的处理方式并不负责进行具体的处理,所以Spring MVC组件要完整完成为URL信息匹配合理的处理方式的步骤,就还应该为合适的处理方式找到匹配的执行方式,也就是要找到匹配的执行适配器(HandlerAdapter)。Spring MVC组件提供了多种执行适配器,这里本文首先进行介绍:

3.1、SimpleControllerHandlerAdapter执行适配器

如果URL信息通过SimpleUrlHandlerMapping映射处理器、BeanNameUrlHandlerMapping映射处理器匹配到合适的处理方式,那么正式执行这个处理方式的执行适配器,就是SimpleControllerHandlerAdapter。DispatcherServlet将通过doDispatch()方法中的以下逻辑,来调用HandlerAdapter执行适配器的以下代码正式执行处理过程:

public class DispatcherServlet extends FrameworkServlet {
  // ......
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ......
    // 以下位置获取到执行适配器
    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // ......
    // 在这里调用HandlerAdapter执行适配器,正式执行处理过程:
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    // ......
  }
  // ......
}

由于通过SimpleUrlHandlerMapping映射处理器、BeanNameUrlHandlerMapping映射处理器匹配的Controller层方法,都需要实现Spring MVC组件提供的Controller接口(org.springframework.web.servlet.mvc.Controller)中的handleRequest(HttpServletRequest , HttpServletResponse)方法。所以SimpleControllerHandlerAdapter执行适配器的正式执行方法就是直接对Controller.handleRequest(…)方法的调用,源代码如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
  // ......
  // SimpleControllerHandlerAdapter中的handle方法
  @Override
  @Nullable
  public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // 这里的handler(真实的执行者)一定实现了Controller接口
    // 那么就调用Controller接口的handleRequest(...)方法
	return ((Controller) handler).handleRequest(request, response);
  }
  // ......
}

SimpleControllerHandlerAdapter执行适配器的处理逻辑非常简单,这是因为使用该执行适配器正式执行的Controller层方法的类一定实现了Spring MVC组件提供的Controller接口,匹配的处理场景相对简单。而HttpRequestHandlerAdapter执行适配器是Spring MVC提供的另一种执行适配器,他们的处理场景都相对简单,也不是本专题重点关注的介绍主线,所以这里大致介绍到此即可。

3.2、RequestMappingHandlerAdapter执行适配器

RequestMappingHandlerAdapter执行适配器是Spring MVC组件中另外一种典型的执行适配器,如果一个URL请求基于RequestMappingHandlerMapping映射处理器成功匹配了真实的执行者,那么正式驱动这次执行的适配器就是RequestMappingHandlerAdapter执行适配器。

RequestMappingHandlerAdapter执行适配器也是本专题内容主线上的执行适配器,他基本覆盖了使用@RequestMapping注解(和相关注解)定义的Controller层方法的真实执行场景。这里面当然还涉及正确的HandlerExecutionChain执行管理器。下图描述了这几种关键对象的关系(这些关系实际上在本专题的上一篇文章中已经进行了详细介绍):

这里应该特别关注HandlerExecutionChain执行管理器中记录的真实执行者HandlerMethod类的实例对象。该类中的主要属性的值概括为:

// 这里借助对TestProjectController类中
// @GetMapping("/findParams") public void findParams(String param1 , String param2)controller层方法的调用
// 进行HandlerMethod对象中主要属性的说明
public class HandlerMethod {
  // ......
  // 该属性的值就是真实要调用的Controller方法所属的bean对象,例如XXXXX.TestProjectController类的对象
  private final Object bean;
  // bean工厂,没有特别情况就是DefaultListableBeanFactory
  private final BeanFactory beanFactory;
  // bean对象的类型为XXXXX.controller.TestProjectController
  private final Class<?> beanType;
  // 因为这个实际的method并不是java中的桥接方法
  // 所以method属性和bridgedMethod属性一致:XXXXX.TestProjectController.findParams(java.lang.String,java.lang.String)
  private final Method method;
  private final Method bridgedMethod;
  // 由于实际调用的方法有两个参数所以这里就是一个长度为2的数组,每一个都是HandlerMethodParameter类的实例
  private final MethodParameter[] parameters;
  // 在实际方法没有执行前,responseStatus属性和responseStatusReason属性都没有值
  @Nullable
  private HttpStatus responseStatus;
  @Nullable
  private String responseStatusReason;
  // ......
}

后续章节的内容,本文将继续按照RequestMappingHandlerAdapter执行适配器所处理的场景介绍下去,原因之一是其它诸如SimpleControllerHandlerAdapter执行适配器所匹配的处理场景太过简单,没有详细介绍下去的意义;另一个重要原因是,RequestMappingHandlerAdapter执行适配器所代表的处理场景最具代表性,也是读者理解Spring MVC进行HTTP请求处理过程的最佳学习路径。

4、参数解析器

RequestMappingHandlerAdapter执行适配器将借助自己的handleInternal(HttpServletRequest , HttpServletResponse , HandlerMethod) 方法正式开始执行方法的调用。代码片段如下所示:

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, 
                                      HandlerMethod handlerMethod) throws Exception {
  ModelAndView mav;
  checkRequest(request);
  // session不是线程安全的,如果系统有保证session线程安全性的要求则执行该分支
  // 该分支不在本文内容的主线上,故没有贴出此部分代码
  if (this.synchronizeOnSession) {
    // ......
  } else {
    // No synchronization on session demanded at all...
    // 执行invokeHandlerMethod方法,该方法内部主要是构造一个ServletInvocableHandlerMethod对象
    // 并作相关处理
    mav = invokeHandlerMethod(request, response, handlerMethod);
  }
  // ......
  return mav;
}

如果说Spring MVC组件进行HTTP信息接收和处理的第一步,是通过映射处理器(HandlerMapping)、执行适配器(HandlerAdapter)和执行管理器(HandlerExecutionChain)找到当次请求是否应该由某个Controller层方法进行处理,以及记录那个最终映射的Controller层方法各方面的描述。

那么Spring MVC组件进行HTTP信息接收和处理的第二步,就是正式调用这个方法。这个步骤涉及到通过参数解析器HandlerMethodArgumentResolver将HTTP请求中的各种信息解析成Controller层方法对应的入参对象,这个步骤还涉及正式进行最终Controller层方法的调用。

注意,因为在Spring MVC组件进行HTTP信息接收和处理的第一步,涉及很多种执行适配器(HandlerAdapter),所以为了专注于对处理主线的描述,本文仅讨论由RequestMappingHandlerAdapter执行适配器进行处理的HTTP请求场景,且执行管理器中的handler属性是一个HandlerMethod对象。

RequestMappingHandlerAdapter执行适配器是针对Controller层方法专门设计的执行适配器,更精确的说法是该执行适配器专门针对@RequestMapping注解处理设计的执行适配器。但是由本专题上一篇文章的描述可知,Controller层方法涉及的定义场景非常复杂,请看如下的示例:

@PostMapping("")
doSomeOtherthing(@RequestBody YourBusiness yourBusiness , 
                 @PathVariable("param") String param , 
                 @RequestParam("key") String value ,
                 @RequestAttribute("org.springframework.session.SessionRepository.CURRENT_SESSION") HttpSession session) {
  // ......
  // 这里是逻辑代码
  // ......
}

可以看到以上示例的controller层方法定义中,第一个参数采用@RequestBody注解,表示使用HTTP请求的Body部分数据进行参数映射;第二个参数采用@PathVariable注解,表示使用URL地址通配符进行参数映射;第三个参数采用@RequestParam注解,表示使用URL的Query部分进行参数映射;第四个参数采用@RequestAttribute注解,表示使用Servlet上下文中的信息进行参数映射。

那么RequestMappingHandlerAdapter执行适配器内部是怎么应对这些复杂场景的呢?我们来看RequestMappingHandlerAdapter执行适配器中最主要的invokeHandlerMethod(HttpServletRequest , HttpServletResponse , HandlerMethod)方法是如何进行处理的:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  ServletWebRequest webRequest = new ServletWebRequest(request, response);
  try {
	// ......
	
	// 首先创建一个ServletInvocableHandlerMethod类的对象invocableMethod,它是HandlerMethod类的子类
	// 所以读者可以看成,在这个步骤创建了一个handlerMethod对象的副本
	ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
	// 为invocableMethod对象设定系统中已有的参数解析器argumentResolvers。
	// 需要注意argumentResolvers是一个全局变量,它是HandlerMethodArgumentResolverComposite类的实例
	// 后者是一个典型的组合模式,里面包括了当前系统中所有已注册的参数解析器
	if (this.argumentResolvers != null) {
	  invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	// 为invocableMethod对象设定系统中已注册的返回值处理器
	// 需要注意returnValueHandlers是一个全局变量,它是HandlerMethodReturnValueHandlerComposite类的实例
	// 后者也是一个典型的组合模式,其中包括了当前系统中所有已注册的返回值处理器
	if (this.returnValueHandlers != null) {
	  invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}
	// 为invocableMethod对象设定其它属性,这些属性在本篇文章中暂时不涉及,后续内容中会进行讲解
	invocableMethod.setDataBinderFactory(binderFactory);
	invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
	
	// ......
	// 这里还有一段代码涉及到异步请求处理、Spring MVC中ModelAndView容器的管理
	// 但这和本文当前的主旨没有太大关系,所以暂时忽略这段代码
	// ......
	
	// 调用invocableMethod对象的invokeAndHandle方法,正式进入Spring MVC组件对HTTP请求处理的第二大步骤
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	if (asyncManager.isConcurrentHandlingStarted()) {
	  return null;
	}
	return getModelAndView(mavContainer, modelFactory, webRequest);
  } finally {
	webRequest.requestCompleted();
  }
}

由以上代码可知RequestMappingHandlerAdapter执行适配器的invokeHandlerMethod方法构建了一个ServletInvocableHandlerMethod类的对象invocableMethod,然后再为invocableMethod对象设定可支持的参数解析器、返回值处理器等信息,最后调用invocableMethod对象的invokeAndHandle方法正式开始Spring MVC组件处理HTTP请求的第二步。

再次强调,本文这里描述的第二步场景,是基于第一步已经匹配到RequestMappingHandlerAdapter执行适配器,且HandlerExecutionChain执行管理器中的handler属性是一个handlerMethod对象的情况。那么invocableMethod对象的invokeAndHandle方法内部到底进行了一些什么处理呢?我们继续进行源代码执行过程跟踪:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

  ServletWebRequest webRequest = new ServletWebRequest(request, response);
  try {
	// ......
	
	// 首先创建一个ServletInvocableHandlerMethod类的对象invocableMethod,它是HandlerMethod类的子类
	// 所以读者可以看成,在这个步骤创建了一个handlerMethod对象的副本
	ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
	// 为invocableMethod对象设定系统中已有的参数解析器argumentResolvers。
	// 需要注意argumentResolvers是一个全局变量,它是HandlerMethodArgumentResolverComposite类的实例
	// 后者是一个典型的组合模式(后文会重点讲解),里面包括了当前系统中所有已注册的参数解析器
	if (this.argumentResolvers != null) {
	  invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	// 为invocableMethod对象设定系统中已注册的返回值处理器
	// 需要注意returnValueHandlers是一个全局变量,它是HandlerMethodReturnValueHandlerComposite类的实例
	// 后者也是一个典型的组合模式,其中包括了当前系统中所有已注册的返回值处理器
	if (this.returnValueHandlers != null) {
	  invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}
	// 为invocableMethod对象设定其它属性,这些属性在本篇文章中暂时不涉及,后续内容中会进行讲解
	invocableMethod.setDataBinderFactory(binderFactory);
	invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
	
	// ......
	// 这里还有一段代码涉及到异步请求处理、Spring MVC中ModelAndView容器的管理
	// 但这和本文当前的主旨没有太大关系,所以暂时忽略这段代码
	// ......
	
	// 调用invocableMethod对象的invokeAndHandle方法,正式进入Spring MVC组件对HTTP请求处理的第二大步骤
	invocableMethod.invokeAndHandle(webRequest, mavContainer);
	// ......
	return getModelAndView(mavContainer, modelFactory, webRequest);
  } finally {
	webRequest.requestCompleted();
  }
}

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 
  Object... providedArgs) throws Exception {
  // 该方法的执行过程主要是两步:
  // 第一步是调用invokeForRequest方法正式执行Controller层方法(也是本文重点说明的步骤)
  // 第二步是根据返回值情况,进行返回信息的组装
  
  // ======= 以下是第一步
  Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
  setResponseStatus(webRequest);
	
  // ======= 以下是第二步,由于不是本文的重点,所以暂时省略
  // ......
  try {
	this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
  } catch (Exception ex) {
	// ......
  }
}

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  Object... providedArgs) throws Exception {
  // invokeForRequest该方法内部主要执行了两个步骤:
  // 第一个步骤,就是使用getMethodArgumentValues方法,为将要执行的Controller层方法的每一个参数
  // 进行值信息的转换,换句话说就是将HTTP请求中的各种信息,按照Controller层方法上使用的不同参数注解转换成特定的入参值
  // 第二个步骤,就是正式将这些得到的参数值,带入到具体的Controller层方法中进行执行,并得到返回值
  
  // ====== 这是第一个步骤(那么这个方法是如何工作的,请看后续的代码内容)
  Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  if (logger.isTraceEnabled()) {
	logger.trace("Arguments: " + Arrays.toString(args));
  }
  // ======= 这是第二个步骤
  return doInvoke(args);
}

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {
  // 取得将要正式调用的Controller层方法的入参列表
  // 如果将要正式调用的Controller层方法无需传入任何入参,则返回一个EMPTY_ARGS常量
  // 这个EMPTY_ARGS常量是个长度为0的数组。
  MethodParameter[] parameters = getMethodParameters();
  if (ObjectUtils.isEmpty(parameters)) {
    return EMPTY_ARGS;
  }
  // 以下循环,依次为每一个入参寻找匹配的参数解析器,并进行参数值的绑定
  // 请注意以下使用的resolvers全局变量,这个全局变量是一个HandlerMethodArgumentResolverComposite类的对象
  // 这是一种典型的组合模式(设计模式中的一种),后文将详细进行讲解
  Object[] args = new Object[parameters.length];
  for (int i = 0; i < parameters.length; i++) {
    // ...... 省略了一些本文不进行介绍的代码
	
	// 首先判定当前的参数是否有任何的参数解析器支持参数值的转换
	// 如果没有任何参数解析器支持则抛出异常
	// 如果存在,HandlerMethodArgumentResolverComposite将记录这个参数所使用的参数解析器
	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) {
      // ...... 省略了一些无关紧要的代码
	  throw ex;
	}
  }
  // 在完成所有入参值的转换后,向调用者返回一个数组
  return args;
}

// 正式进行Controller层方法的调用
@Nullable
protected Object doInvoke(Object... args) throws Exception {
  ReflectionUtils.makeAccessible(getBridgedMethod());
  try {
	return getBridgedMethod().invoke(getBean(), args);
  } catch (IllegalArgumentException ex) {
	// ..... 省略了一些无关紧要的代码
  } catch (InvocationTargetException ex) {
	// ..... 省略了一些无关紧要的代码
  }
}

以上的代码过程读者无需全部阅读,只需要从以上代码片段中知晓两件事情即可,就是invocableMethod对象的invokeAndHandle方法和其相关方法主要完成了两件事情,第一件事情就是为匹配的Controller层方法转换各个入参,这个过程说得再详细一点就是为指定的入参对象MethodParameter匹配正确的参数解析器,并基于参数解析器将HTTP请求中特定的信息(可能是HTTP的Header部分的某个属性,可能是HTTP信息Query部分的某个传参,还可能是HTTP信息Body部分的信息)转换成入参对象值;第二件事情则是正式调用Controller层方法本身(这个过程相对简单,只需要一笔带过即可)。如下图所示:

后文本专题就来具体介绍多个Spring MVC组件中的重要组成部分——参数解析器(接后文)。

以上是关于Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程的主要内容,如果未能解决你的问题,请参考以下文章

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC 传参详解(下)

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程

Spring/Boot/Cloud系列知识:SpringMVC进行HTTP信息接收和发送的过程