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

Posted 说好不能打脸

tags:

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

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

上一篇文章讲到Spring MVC组件在接受到HTTP请求后,为了正确处理这个请求至少需要解决三个大的问题(当然还有很多细枝末节的问题),这些问题包括如何为当前URL匹配合适的响应处理方式,这些响应处理方式可能是返回一个静态资源,可能是匹配一个servlet,还可能是匹配一个Controller层的处理方法;这些问题还包括当找到了匹配的Controller层的处理方法后,如何将HTTP请求中携带的各种信息映射成方法的各种入参;另外的问题还包括当Controller层处理方法完成调用后(无论调用是否成功),如何将处理结果映射成HTTP响应信息。

上一篇文章本专题详细分析了可能产生这些问题的场景和细节,本篇文章开始本专题将详细介绍Spring MVC组件是如何解决这些问题的。

1、寻找匹配的URL处理方法:映射处理器、执行管理器和执行适配器

URL请求到达服务端后,Spring MVC组件需要首先找到这个URL请求该如何处理,而这个工作就交给映射处理器(HandlerMapping)来完成。在ApplicationFilterChain协调完成所有需要调用的Filter过滤器后,ApplicationFilterChain将通过internalDoFilter方法中的逻辑将操作处理权交给DispatcherServlet,后者是Spring MVC中提供的一种Servlet实现。DispatcherServlet在其getHandler()方法中,将为这次URL请求匹配适当的处理器,代码片段如下所示:

// 为当前URL匹配合适的执行管理方式。后续的执行管理方式将以HandlerExecutionChain对象的形式进行描述
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  // handlerMappings存储了当前系统中已注册的所有映射处理器,后文将介绍这些处理器
  if (this.handlerMappings != null) {
	for (HandlerMapping mapping : this.handlerMappings) {
	  // 如果handler对象不为null,说明后续的处理方式已经匹配到了
	  HandlerExecutionChain handler = mapping.getHandler(request);
	  if (handler != null) {
		return handler;
	  }
	}
  }
  return null;
}

请注意:映射处理器HandlerMapping的作用不是直接处理URL请求,而是为URL请求寻找合适的处理方式,以便能够继续后续的处理过程。 这从getHandler()方法返回HandlerExecutionChain对象就可以看出来:HandlerExecutionChain是执行管理器,负责记录后续处理过程。而且从HandlerExecutionChain的名字就可以知道该类管理了一个执行链,是典型的责任链模式。

在Java语言的设计技巧中,使用什么样的设计模式是可以通过类的命名看出来的,例如责任链模式中,关键的管理类会以Chain关键字结尾;再例如工厂模式中的主要调用类会以Factory关键字结尾;再例如策略模式的主要接口,会以Strategy关键字结尾……

我们来看一下HandlerExecutionChain执行管理器的主要源代码,以便继续描述后续的处理过程:

// ......
// 执行管理器
public class HandlerExecutionChain {
  // ......
  // 这是真实执行者,不同的映射处理器创建的真实处理者完全不一样
  private final Object handler;
  // interceptors和interceptorList记录了当前系统中已经注册的拦截器
  // 由于Spring MVC的历史原因,所以这些拦截器是分为两个属性进行存储的。
  // 正式请求时,这两个属性记录的拦截器会进行合并处理
  @Nullable
  private HandlerInterceptor[] interceptors;
  @Nullable
  private List<HandlerInterceptor> interceptorList;
  // ......
}

读者如果读到这里,那么就可以和HandlerInterceptor接口中preHandle、postHandle等方法如何执行对应起来了,也可以大致猜测出来为什么preHandle方法中传递的handler参数是一个Object类型的对象了(但是HandlerInterceptor拦截器并不是本文的主要内容,本专题的后续内容会进行拦截器工作原理的介绍)。

HandlerExecutionChain执行管理器中的handler属性,就是这个真实的执行者为什么是一个Object类型的属性呢?这是因为不同的映射处理器,对应的真实处理者完全不一样,而后续的处理逻辑也不需要建立具体的接口进行规范,所以这里直接使用Object类型的属性进行记录。

请注意HandlerExecutionChain执行管理器的责任链只负责各个HandlerInterceptor拦截器的链式执行过程管理,并不负责真实执行者(HandlerExecutionChain管理器中的handler属性)的执行,因为作为一个Object对象,HandlerExecutionChain执行管理器是不清楚handler该如何执行。驱动真实执行者handler真正执行的是HandlerAdapter执行适配器。

DispatcherServlet在getHandlerAdapter()方法中将为真实执行者handler寻找匹配的执行适配器,Spring MVC组件有多种执行适配器,在本专题的后续内容中会进行介绍。这里我们先来看看DispatcherServlet中的getHandlerAdapter()方法:

// DispatcherServlet中的这个方法,将为真实的执行者handler寻找执行适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  // handlerAdapters是一个全局变量,记录了已经被应用系统支持的所有执行适配器
  if (this.handlerAdapters != null) {
	for (HandlerAdapter adapter : this.handlerAdapters) {
	  // 通过每一个系统支持的HandlerAdapter,寻找合适的执行适配器,直到找到为止
	  if (adapter.supports(handler)) {
	    return adapter;
	  }
	}
  } 
  // 如果没有找到执行适配器,则抛出异常
  throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

最终被匹配的HandlerAdapter执行适配器,将以HandlerAdapter.handle()方法正式进行处理过程的执行。以上本节中介绍的整个过程可以用下图进行表示:

在本专题中,我们将使用多篇文章依次介绍本节中提到的映射处理器HandlerMapping和执行适配器HandlerAdapter,我们首先介绍Spring MVC组件中那些常见的映射处理器,请看后文介绍。

1.1、SimpleUrlHandlerMapping映射处理器

SimpleUrlHandlerMapping映射处理器是一种不太常使用的映射处理器实现。该映射处理器可以通过一组K-V设定关系,描述访问路径和具体controller层处理过程的对应关系。这种方式下对应的controller层方法可以不区分具体的HTTP Method类型。那么SimpleUrlHandlerMapping映射处理器所匹配的HTTP请求方式在Spring Boot中如何进行配置呢?请看如下的示例:

  • 设定一个controller层方法:
@Component("simpleUrlController")
public class SimpleUrlController implements Controller {
  @Override
  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ..... 这里是具体的controller层实现
  }
}
  • 设定SimpleUrlHandlerMapping映射关系:
// 设置一个SimpleUrlHandlerMapping信息,
@Configuration
public class SimpleUrlHandlerMappingConfig extends SimpleUrlHandlerMapping {
  @Bean
  public SimpleUrlHandlerMapping getSimpleUrlHandlerMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    // 设定URL和controller的对应关系
    Properties urlProperties = new Properties();
    // 有没有“/”都无所谓,Key:表示访问路径;Value:表示使用的controller名字
    urlProperties.put("/yourSimpleUrl", "simpleUrlController");
    mapping.setMappings(urlProperties);
    mapping.setOrder(1);
    return mapping;
  }
}

以上的设置很容易理解,这里就不再对设置情况和执行效果进行赘述了。基于本节开始处介绍的Spring MVC组件为URL寻找匹配的处理方式的内容,通过SimpleUrlHandlerMapping设定的URL处理方式一旦匹配成功,就会返回一个HandlerExecutionChain执行管理器对象,该对象中最重要的handler属性(真正执行者),就是这个Controller类本身(例如以上示例中,HandlerExecutionChain中的handler属性就是SimpleUrlController对象)。doDispatch()方法中还会为本次处理匹配具体的HandlerAdapter执行适配器,这里匹配都的具体执行适配器是SimpleControllerHandlerAdapter,如下图所示:

注意,以上代码运行结果基于DispatcherServlet.doDispatch()方法的运行段落,有兴趣的读者可以直接阅读。另外需要注意,虽然称之为简单URL(“SimpleUrlXXXX”)但实际上这种简单设置的URL也可以支持通配符形式的URL,如下所示的配置是正确的:

@Bean
public SimpleUrlHandlerMapping getSimpleUrlHandlerMapping() {
  // ......
  // 设定URL
  Properties urlProperties = new Properties();
  urlProperties.put("/yourSimpleUrl/{test}", "simpleUrlController");
  mapping.setMappings(urlProperties);
  mapping.setOrder(1);
  // ......
}

// 这样一来以下URL请求都可以匹配到同一个controller层处理方法
// /yourSimpleUrl/yyyyy
// /yourSimpleUrl/xxxxx
// /yourSimpleUrl/zzzzz

1.2、BeanNameUrlHandlerMapping映射处理器:

BeanNameUrlHandlerMapping映射处理器可以匹配的Controller层方法,可以通过类似2.1小节的设置方式进行设置。最大的区别是这里设定的Controller层方法的@Component注解可以直接配置成一个路径,如下所示:

@Component("/simpleBeanNameUrlController")
public class SimpleBeanNameUrlController implements Controller {
  @Override
  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ..... 这里是具体的controller层实现
  }
}

请注意这里在@Component注解中直接配置了一个URL路径“/simpleBeanNameUrlController”。BeanNameUrlHandlerMapping映射器支持的这种Controller层方法配置,也不需要去区分HTTP的method类型,无论使用POST形式的请求还是GET形式的请求(又或者其他类型的请求),Controller层方法都可以接收到本次处理。

BeanNameUrlHandlerMapping映射处理器对应获取到的HandlerExecutionChain对象,后者对象中的handler属性(真实执行者)对应的也是具体的controller对象,如下图所示:

SimpleUrlHandlerMapping映射处理器和BeanNameUrlHandlerMapping映射处理器所适配的Controller层配置方式,在实际工作中不太被使用,原因是这样的配置方式不太灵活,配置信息也不太好管理,但出于对Spring MVC组件的学习目的,读者还是有必要理解这些映射处理器的。

除了以上介绍的两种映射处理器外,Spring MVC组件还提供了WelcomePageHandlerMapping等映射处理器,这些映射处理器都有特定的配置和使用场景,这里就不再进行赘述了(主要是和本文的主要内容关系不大)。

1.3、RequestMappingHandlerMapping映射处理器:

该映射处理器是我们在正式工作时最常被使用的映射处理器,也是本专题多篇文章的介绍主线,该处理器负责处理大部分由开发人员通过@RequestMapping注解自行定义的controller方法,使用示例如下:

@RestController
@RequestMapping("/v1/test11")
public class TestProjectController {
  @GetMapping("/findParams")
  public YourResult findParams(String param1 , String param2) {
    // ..... 这里是具体的Controller层方法实现逻辑
  }
}

如果一个URL请求通过RequestMappingHandlerMapping映射处理器匹配成功,并获取到了相关的HandlerExecutionChain对象,那么HandlerExecutionChain对象中记录的真实执行者(即HandlerExecutionChain对象中的handler属性)就是一个HandlerMethod对象的引用。为什么真实执行者是一个handlerMethod对象,而不是一个具体的Controller层方法呢?

这是因为开发人员通过各种注解(主要是@RequestMapping注解)定义的Controller层方法特别多,而这些方法允许有多种完全不同形式的入参(可参见上篇文章对此问题的描述)和各种完全不同形式的返回值,所以这里的真实执行者不能像SimpleUrlHandlerMapping等映射处理器那样记录一个具体的Controller层方法,而只能抽象成一种执行方式的描述。这种抽象出来的执行方式描述包括了对具体Controller方法的bean对象信息、方法信息、入参信息、返回信息等信息的描述,这就是HandlerMethod对象的作用,以下是HandlerMethod类源代码中主要属性的介绍:

// HandlerMethod记录了
public class HandlerMethod {
  // controller方法所属的bean对象信息
  private final Object bean;
  // ......
  // bean对象对应的class类型
  private final Class<?> beanType;
  // 根据定义的方法不同,实际要执行的方法可能是一个普通方法,也可能是一个桥接方法
  // (关于桥接方法的知识点,不属于本文内容,读者可参看第三方资料)
  // 当实际要执行的方法是一个普通方法,则method属性和bridgedMethod属性的值一样
  private final Method method;
  private final Method bridgedMethod;
  // 该属性记录了实际执行方法入参信息(按顺序排列)
  private final MethodParameter[] parameters;
  // ......
  // 该属性记录了每个入参可能的泛型信息
  // 由于有一个入参可能存在多个泛型信息,所以这里记录的是一个二维数组
  @Nullable
  private volatile List<Annotation[][]> interfaceParameterAnnotations;
  // ......
}

如果一个URL请求通过RequestMappingHandlerMapping映射处理器匹配成功,那么真实处理后续过程的执行适配器为一个RequestMappingHandlerAdapter对象,如果读者对这种场景进行调试,将会看到类似如下的调试效果:

Spring MVC在处理一个完整的HTTP请求的第一步,就是寻找匹配的URL处理方法。其中最主要的目标就是基于Spring应用程序中已有的映射处理器HandlerMapping,为这个URL找到合适的执行管理器HandlerExecutionChain和执行适配器HandlerAdapter。以上内容中本文介绍了主要的映射处理器,并说明了执行管理器HandlerExecutionChain中最重要的实际执行者对象handler。

这些内容中最重要的映射处理器为RequestMappingHandlerMapping,因为该映射处理器会负责应用程序中绝大部分和业务逻辑相关的controller层处理方式的映射工作。后文本专题将继续基于映射处理器RequestMappingHandlerMapping的处理场景,介绍Spring MVC组件处理一个完整HTTP请求的第二步:为Controller层方法的每一个入参匹配参数值。

========(接后文)

以上是关于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信息接收和发送的过程