首先梳理一下一下一个 http 请求到达服务端后发生的事。

  • 硬件:网卡受到网络(准确说是物理层)的请求之后将交由操作系统处理
  • 操作系统:http 的请求会基于 tcp 的连接,而对 tcp 的处理都是由操作系统完成(包括处理 socket 监听等,程序内的 socket 最终是通过系统调用的方式操作系统 socket 的行为,本质上 socket 部分的工作逻辑由操作系统完成)
    tcp 的连接的对象是主机,即一个 tcp 的链路连接的是两台电脑(两个 ip),不存在 tcp 直接连接两个程序的情况。在 tcp 连接之后,将收到的数据依据端口号传送给对应端口号的进程。
  • http 服务器:这里 http 服务器指一个特殊的进程。上一步从 tcp 上获取的数据的载荷即为一个 http 请求,http 请求同样不应该直接由应用程序处理(这样做需要实现必要的 http 标准,不考虑复用但就一个应用来说过于复杂)。业务无关的逻辑(至少包括将 http 请求从数据流还原到一个 http 的数据结构)将交由 http 服务器处理(常见的有 nginx 和 apache),比如实现转发请求,或者根据当前 http 服务器的状态返回错误信息,或者返还相应的请求文件(大多数 http 服务器有相关的实现但不是必须)。
  • 应用服务器:包含实际的业务逻辑的,非静态的 http 请求将从 http 服务器转由应用服务器处理,对于 spring 的应用程序来说,一般情况应用服务器在初步解析 http 请求(比如路径,请求参数,端口号,和头信息等)之后,将交由对应的 servlet 程序处理(servlet 应用服务器由于可包含多个 servlet 程序也称为 servlet 容器)。
  • spring-web:spring 应用向应用服务器注册一个 servlet(这个 servlet 的路径和端口号即是配置文件中的 server.port 和 contextPath)。内部的路径和不同的 http 方法则由 spring 自身去处理。一个请求最终的处理逻辑在 controller 中得到处理。

spring web

内容只涉及最基本的 web,不涉及 MVC (虽然 mvc 也是通过类似的方式处理的,具体见:Web on Servlet Stack (spring.io))。

DispatcherServlet

spring web (servlet 基础上的) 使用一个特殊的 servlet,即 DispatcherServelet 作为对 spring 应用请求的入口(spring 应用的启动的入口可见另一篇博客,spring boot 启动过程 (kicey.site))。

DispatcherServlet 作为一个特殊的类,需要 WebApplicationContext (一个特殊的容器)来完成自身的实例化和注册,那么 WebApplicationContext 自然需要一个对象抽象 servlet 容器负责对容器的调用,这个对象就是 ServletContextWebApplicationContext 允多个实例并具有层级结构,那么 DispatcherServlet 也具有相应情况下的实现方式,不展开。

特殊的 bean 类型

DispatcherServlet 相关的一些类型:

  • HandlerMapping
    将请求转交给相应的拦截器和处理者进行处理,主要的实现有 RequestMappingHandlerMapping 对使用 @RequestMapping 定义的路由进行处理,和 SimpleUrlHandlerMapping 对显示注册路由的处理者进行处理。
  • HandlerAdapter
    帮助 DispatcherServlet 调用请求对应的处理者,比如调用一个 controller 需要对类和相应的方法上的注解进行解析,于是这些处理者特定的逻辑由 HandlerAdapter 完成,对于 DispatcherServlet 而言这部分是透明的(不需要关注)。
  • HandlerExceptionResolver
    对请求中的异常进行处理,比如在一个处理请求发生错误时返回一个错误页面。

servlet 配置

servlet 依赖于一些配置(支持 xml 于 java 形式的配置),spring 中负责使用 servlet 的容器(区别于 tomcat 作为 servlet 容器)的创建是 WebApplicationInitializer 接口。可以使用一个实现了该接口的抽象类 AbstractDispatcherServletInitializer ,覆盖其中的方法,可以指定 http 路径,spring 容器配置文件,根容器(spring 容器可以具有层级关系,在 web 中也可以),servlet 上的过滤器(filter),以及是否启用 servlet 和 filter 的异步处理(默认启用)。

如果这些不能满足需求,可以覆写 createDispatcherServlet() 方法去自定义 DispatcherServlet 对象。

DispatcherServlet 处理请求

  • 以 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 将 WebApplicationContext 作为请求对象的一个属性。
  • 将 locale resolver 作为请求对象的一个属性用于处理国际化。
  • 如果指定了 multipart file resolver ,请求将检查是否为 multipart 请求(一个请求体中包含多个文件/表单,具体见:HTTP协议之multipart/form-data请求分析 - 简书 (jianshu.com)),如果是,则将请求构造为一个 MultipartHttpServletRequest 对象。
  • 最后,找到相应的处理者(controller),使用相关的处理链处理(preprocessors,controller, postprocessors)。这个过程涉及到请求路径的匹配以及请求参数(包括请求头参数的限定),主要有两种匹配器( PathPatternAntPathMatcher)不展开。

拦截器

所有的 HandlerMapping 实现都支持拦截器的使用,拦截器是在 controller 处理请求之前,之后,以及完成请求时执行一些逻辑。拦截器需要实现 org.springframework.web.servlet 包中的 HandlerInterceptor ,并通过覆写 preHandle(..)postHandle(..)afterCompletion(..) 定义执行逻辑。preHandle(..) 返回一个布尔值表示请求是否满足继续处理的条件。

异常处理

对于请求处理中出现的异常将由 HandlerExceptionResolver 处理的,具体的可以在容器中定义多个 HandlerExceptionResolver 实例,并使用 @Order 接口定义处理的顺序,如果一个 HandlerExceptionResolver 不能处理当前的异常则向下一个传递。

请求详细日志

处于对敏感内容的考虑,spring 的日志中默认不包括请求参数等敏感信息,如果需要,配置如下:

public class MyInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}

过滤器

介绍一些常用(默认集成的)过滤器

  • FormContentFilter
    Servlet API 仅支持对 Post 请求通过 ServletRequest.getParameter*() 获取表单字段,FormContentFilter 拦截其他的 Http 请求(content-type 为 application/x-www-form-urlencoded)并包装 ServletRequest 使其可以获取相应的表单字段。
  • ForwardedHeaderFilter
    处理 http 的 Forwarded 请求头(主要是代理使用,在经过代理之后 http 中的 host 将变成代理的 host 而不是真正发出请求的 host ,那么代理就需要将真正的 host 记录在请求头 Forwarded 字段中, ForwardedHeaderFilter 对这个部分做处理)
  • ShallowEtagHeaderFilter
    不难看出这是为了处理 http 请求中通过 Etag 进行浏览器的协商缓存(具体见:客户端缓存 | 凤凰架构 (icyfenix.cn))。
  • 处理 Cors 的过滤器
    spring 处理跨域请求伪造同样是通过一组 servlet 的过滤器实现的,这里不展开。

请求响应字段

具体对一个请求的处理在一个 controller 的方法中,这些方法可以获取请求中的参数以及声明响应的参数(比如用于 accept-content 的判断)。

内容实在太过,具体见:Web on Servlet Stack (spring.io)

controller 异常处理

在 controller 中可以通过 @ExceptionHandler 定义异常处理的方法,将目标异常做为方法参数或者注解参数,如下:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler({FileSystemException.class, RemoteException.class})
	public ResponseEntity<String> handle(IOException ex) {
    // ...
	}
}

@ControllerAdvice

在普通的 controller 中使用 @ExceptionHandler@InitBinder ,和 @ModelAttribute 之对于当前或者其子类有效,如果希望全局有效则讲他们定义在 @ControllerAdvice 中。

此外

spring-web 不仅支持声明式的请求处理(@Controller, @RequestMapping 等),也支持函数式的请求处理(用函数进行路由和请求处理,不展开,详见:Web on Servlet Stack (spring.io))。