史上最烂 spring web 原理分析

盗引·下篇·spring web

spring web、spring web 与 tomcat、映射器与适配器、参数解析器与类型转换器、返回值处理器与消息转换器、异常处理器、@ControllerAdvice、spring web 工作流程。


版本

  • jdk:8
  • spring:5.3.20
  • spring boot:2.7.0

1 spring web

1.1 简介

  spring web 相关的组件包括 spring-web、spring-mvc、spring-webmvc,其核心功能如下:

  • spring-web:

    spring-web 集成了 http,由 http、remoting、web 三部分组成,提供了一些便捷的 servlet 过滤器,http 调用等功能。其是 spring-mvc 的基础。

  • spring-mvc:

    spring-mvc 是对 mvc 思想的支持或实现,且其是基于 servlet 实现的,通过 Servlet 接口的实现类 DispatcherServlet 来封装和实现其核心功能。DispatcherServlet 是 spring mvc 的入口,亦是其核心所在,其负责接收请求并将分发请求到处理程序,且会对请求结果作出一定处理。

  • spring-webmvc:

    spring-webmvc 是 spring-mvc 的一个实现,其基于 spring-web 和 spring-mvc。其提供了 mvc 中各个层面的具体实现,如各种 Handler、Adapter、Resolver 等。

1.2 DispatcherServlet

  DispatcherServlet 的工作原理大致如下:

dispatcher-servlet-process

  由上图可知,DispatcherServlet 的工作流程大致分为以下几个步骤:

  • 1、匹配 Handler:DispatcherServlet 接收到客户端的请求后会先请求 HandlerMapping(处理器映射器),以请求地址、请求方法等参数找到对应的 Hanlder(处理器),然后将 Handler 返回给 DispatcherServlet。
  • 2、执行 Handler:DispatcherServlet 获取到 Handler 后接着请求 HanlderAdapter(处理器适配器),HandlerAdapter 会以适配的方式执行 Handler ,并将执行结果 ModelAndView 返回给 DispatcherServlet。
  • 3、视图解析:DispatcherServlet 拿到执行结果后会请求 ViewResolver(视图解析器),对 ModelAndView 进行解析,并将解析结果 View 返回给 DispatcherServlet。
  • 4、视图渲染:DispatcherServlet 获取到解析结果 View 后会对其进行视图渲染,最后将渲染结果响应给客户端。

1.3 spring web 与 tomcat

1.3.1 DispatcherServlet 与 tomcat

  tomcat 是 web 容器,也可以理解其为 servlet 的容器,因为 tomcat 是通过 servlet 来处理请求,即 tomcat 接收到请求后会将其分发给 servlet 进行处理,然后将处理结果响应到客户端。

  DispatcherServlet 间接实现了 Servlet 接口,所以其本质上也是一个 servlet,同时其也是 spring mvc 的入口。spring 提供了 DispatcherServletRegistrationBean 类,该类是一个注册 bean,其会将 DispatcherServlet 注册到 tomcat 中,这样就为 spring mvc 与 tomcat 建立了连接。

  spring boot 内嵌了 tomcat 容器,其内嵌的方式是通过自动配置创建了 Tomcat 实例、DispatcherServlet 实例等,并通过 DispatcherServletRegistrationBean 将 DispatcherServlet 注册到 tomcat 中。spring boot 内嵌 tomcat 具体是通过 ServletWebServerFactoryAutoConfiguration 自动配置类实现的,该配置类中注册了三个 bean,分别是 TomcatServletWebServerFactory、DispatcherServlet、DispatcherServletRegistrationBean。其中 TomcatServletWebServerFactory 负责创建配置 tomcat 容器,DispatcherServlet 则作为 spring mvc 核心组件,DispatcherServletRegistrationBean 则负责将 DispatcherServlet 注册到 web 容器中。

1.3.2 DispatcherServlet 初始化

  默认情况下,DispatcherServlet 是在请求第一次到达时才进行初始化,也可通过 DispatcherServletRegistrationBean 的 loadOnStartup 属性进行设置。loadOnStartup 默认为 -1,表示在请求第一次到达时才进行初始化,当其大于 0 时则表示在 tomcat 初始化完成后初始化,其值越小越先初始化(spring web 设计允许有多个 web 容器存在)。

  DispatcherServlet 初始化发生在其实现的 onRefresh() 方法中,其初始化时会进行以下操作:

  • initHandlerMappings():准备处理器映射器组件,即 HandlerMapping。会将 HandlerMapping 接口的所有实例收集起来,并按照指定的顺序排序(若未指定排序则放至最后),排序的目的是为了应用的优先级。
  • initHandlerAdapters():准备处理器适配器,即 HandlerAdapter。
  • initMultipartResolver():准备文件解析器,即 MultipartResolver。
  • initViewResolvers():准备视图解析器,即 ViewResolver。
  • initHandlerExceptionResolvers():准备处理器异常解析器,即 ExceptionResolver。

2 映射器与适配器

2.1 映射器

2.1.1 映射器简介

  映射器,即处理器映射器,实际表现为 HandlerMapping 接口的实现实例。处理器可以理解为 controller 中的一个方法(被 @RequestMapping 注解及其派生注解标注的方法),映射器则表示一种映射关系,故处理器映射器可通俗理解为请求地址与处理器方法的对应关系(这里的请求地址即为 @RequestMapping 的注解值)。

  spring web 中的处理器映射器统一用 HandlerMapping 接口来表示,同时针对不同的使用场景添加了实现类,常用的实现类有 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping、WelcomePageHandlerMapping 五大类。其类关系图如下:

spring-web-handler-mapping

  • HandlerMapping:

    即 spring web 中的定义的处理器映射器接口,该接口的核心功能是 geHandler() 方法,其作用是根据某个请求信息查找某个具体的处理器执行链(处理器执行链可直接理解为处理器)。

    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    
  • AbstractHandlerMapping:

    即 HandlerMapping 接口的第一抽象实现类,其主要实现了接口中的 getHandler() 方法,其中根据请求信息查找具体处理器的功能抽象成 getHandlerInternal() 方法交由子类实现(针对不同的 HandlerMapping 实现),此外,其还组合了映射相关的其它功能,如 PathPatternParser、UrlPathHelper、PathMatcher、CorsProcessor 等。

    protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
    
  • RequestMappingHandlerMapping:

    即基于 @RequestMapping 注解及其派生注解的处理器的映射器实现。其继承自 AbstractHandlerMethodMapping 抽象类。即由 @Controller 和 @RequestMapping 配置的处理方法对应的请求都将由该映射器进行映射。

  • BeanNameUrlMapping:

    即基于 Controller 接口类的实现类处理器的映射器实现。其继承自 AbstractUrlHandlerMapping 抽象类。我们实际生产中很多时候都使用 @Controller 与 @RequestMapping 注解来配置处理请求的方法,但实际上 spring web 还为我们提供了另一种方法,即实现 Controller 接口类。该接口有一个 handleRequest() 方法,实现类则对应我们平时开发的 controller 类,实现的 handleRequest() 则对应 controller 中的某个处理方法。需要注意的是, Controller 接口的的实现类需要注入到 spring ioc 容器中,且必须为其指定以 / 开头的 bean name,如 @Bean(“/test”)。

    @Bean("/one")
    public Controller oneController() {
        return (request, response) -> {
            response.getWriter().write("That's like the wind, loveless and free!");
            return null;
        };
    }
    
  • SimpleUrlHandlerMapping:

    即基于静态资源的映射器实现。其继承自 AbstractUrlHandlerMapping 抽象类。即对于 js、css 等静态资源的方法将由该映射器进行映射。

  • WelcomePageHandlerMapping:

    即基于欢迎页的映射器实现。其继承自 AbstractUrlHandlerMapping 抽象类。即对于欢迎页的请求将由该映射器进行映射。

  • RouterFunctionMapping:

    即基于路由的函数式映射器实现。

    @Bean
    public RouterFunction<ServerResponse> threeRouter() {
      	return route(POST("/three"), request -> ok().body("That's like the wind, loveless and free!"));
    }
    
2.1.2 映射器初始化

  映射器初始化时会解析配置的请求路径与处理方法的映射关系,并缓存起来,以便在处理请求时能够快速定位到其对应的处理方法。此处只简单说明常用的 RequestMappingHandlerMapping 映射器和比较有趣的 BeanNameUrlHandlerMapping 映射器的初始化过程。

  • RequestMappingHandlerMapping 初始化

    RequestMappingHandlerMapping 初始化的主要目的是解析并缓存通过 @Controller 和 @RequestMapping 注解配置的映射关系。且其初始化实际上是发生在其父类 AbstractHandlerMethodMapping 中,通过其初始化方法 afterPropertiesSet() 方法触发。具体初始化流程如下:

    • 1、获取 spring ioc 容器中的所有 bean name,遍历获取 bean 类型。

    • 2、检查 bean 类型上是否存在 @Controller 和 @RequestMapping 注解,若存在则对其进行后续操作。

    • 3、获取该类中的所有方法,并遍历检查其是否被 @RequestMapping 及其派生注解标注,若是,则根据该类和当前方法为该方法创建 RequestMappingInfo 对象。最终,该类中所有可作为处理器方法的方法会产生一个以处理器方法为 key,以其对应的 RequestMappingInfo 为 value 的 map 集合。

    • 4、最后遍历该 map 集合,将其 key、value 值以特定格式缓存在 AbstractMappingHandlerMapping 的内部类 MappingRegistry 所维护的几个 map 缓存中。且在缓存前会查看缓存中是否存在相同 key 的 value,若存在则抛出映射已存在异常。

      // key 为 RequestMappingInfo value 为 内部类 MappingRegistration<T>
      // MappingRegistration<T> 由 RequestMappingInfo、HandlerMethod、pathLookup 的 key、nameLookup 的 key、corsLookup 的 key
      private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
      
      // key 为请求路径(如 '/api/test') value 为 RequestMappingInfo 对象
      private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
      
      // key 为处理器和处理方法简称(如处理器为 TestController 处理方法为 test() 则 key 为 'TC#test')
      // value 为 HandlerMethod 集合 HandlerMethod 由处理器对象、处理方法对象、方法参数及其它辅助信息构成
      private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
      
      // key 为 HandlerMethod value 为跨域配置信息
      private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
      
  • BeanNameUrlHandlerMapping 初始化

    BeanNameUrlHandlerMapping 初始化的主要目的是解析并缓存通过实现 Controller 接口所配置的映射器的映射关系。且其初始化实际上是发生在其父类 AbstractDetectingUrlHandlerMapping 所实现的 initApplicationContext() 方法中。其触发的时机是在 spring ioc 回调 ApplicationContextAware 接口的 setApplicationContext() 方法时。具体初始化流程如下:

    • 1、获取 spring ioc 容器中所有 bean name,遍历判断 bean name 是否以 ‘/’ 开头。
    • 2、若是,则将其缓存到其父类 AbstractUrlHandlerMapping 所维护的 handlerMap 集合中。其中 key 为 bean name(形如 ‘/test’),value 为处理器实例。
2.1.3 映射器使用

  spring web 提供了五种 HandlerMapping 的实现,即 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping、WelcomePageHandlerMapping,同时其支持开发者自定义扩展实现 HandlerMapping。

  同时 spring web 要求须为这些实现指定应用顺序,其中 spring web 默认提供了五种实现的应用顺序依次为 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping、WelcomePageHandlerMapping,可通过 @Order 注解指定,若未指定则默认排到最后。所有 HandlerMapping 的实现实例会在 DispatcherServlet 初始化时(即调用 initHandlerMappings() 方法时)被 DispatcherServlet 收集起来并按照指定的顺序排序缓存到其维护的 handlerMappings map 集合中。

  当一个请求被 tomcat 分发到 DispatcherServlet 时,其会按照 HandlerMapping 的实例顺序挨个儿应用,即挨个儿在各自缓存的映射关系进行匹配,若匹配到则返回其对应的处理器方法,若未匹配到则 404。

  • 1、请求到达 DispatcherServlet 时先调用 DispatcherServlet 实现的 doService() 方法。
  • 2、调用内部方法 doDispatch() 方法。
  • 3、调用内部方法 getHandler() 方法。该方法返回 HandlerExecutionChain 即处理器执行链。
  • 4、getHandler() 方法的逻辑是遍历 handlerMappings map 集合,在映射器缓存的映射关系中查找处理器方法。
  • 5、查找过程也很简单,即以请求信息(如请求方法、请求路径等)为 key 从映射器初始化时缓存起来的各个 map 集合中查找。
  • 6、将匹配到的处理器方法封装成 HandlerMethod 对象,并匹配相应拦截器。
  • 7、最后将 HandlerMethod 对象与拦截器封装成 HandlerExecutionChain 对象返回。

2.2 适配器

2.2.1 适配器简介

  适配器,即处理器适配器,实际表现为 HandlerAdapter 接口的实现实例。其作用是适配处理器方法的调用,表现为处理器参数解析、类型转换、处理器方法调用、处理器方法返回值处理。简单理解,当请求到达 DispatcherServlet 时,先从处理器映射器中获取到处理器,然后将处理器交给适配器,适配器会准备参数、调用处理器、处理结果等。

  spring web 中处理器适配器统一用 HandlerAdapter 接口表示,同时针对不同使用场景添加了默认实现类,常用的实现类有:RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、SimpleServletHandlerAdapter 等五大适配器。其类关系图如下:

spring-web-handler-adapter

  • HandlerAdapter:

    即 spring web 中定义的处理器适配器接口。该接口的核心功能是 supports() 和 handle() 方法。即判断当前适配器是否支持指定处理器的适配,执行处理器并返回 ModelAndView 结果。

    boolean supports(Object handler);
    
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    
  • RequestMappingHandlerAdapter:

    即针对 RequestMappingHandlerMapping 映射器对应处理器的适配器实现。其继承自 AbstractHandlerMethodAdapter 抽象类。

  • HandlerFunctionAdapter:

    即针对 RouterFunctionMapping 映射器对应处理器的适配器实现。

  • HttpRequestHandlerAdapter:

    即针对 SimpleUrlHandlerMapping 映射器对应处理器的适配器实现。

  • SimpleControllerHandlerAdapter:

    即针对 BeanNameUrlHandlerMapping 映射器对应处理器的适配器实现。

  • SimpleServletHandlerAdapter:

    即针对原生 Servlet 映射器对应处理器的适配器实现。

2.2.2 适配器初始化

  处理器适配器的初始化主要是准备处理器方法参数解析器和处理器方法返回值处理器。其初始化功能通过实现 InitializingBean 接口的 afterPropertiesSet() 方法来实现。且会将各种参数解析器或返回值处理器通过组合模式维护在当前适配器实例中。spring web 为我们提供了大量的参数解析器和返回值处理器的实现,初始化时就会创建这些实现的实例。

  以 RequestMappingHandlerAdapter 适配器初始化为例:

// 处理器方法参数解析器组合器
private HandlerMethodArgumentResolverComposite argumentResolvers;

// @InitBinder 对应的处理器方法参数解析器组合器
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

// 处理器方法返回值处理器组合器
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
2.2.3 适配器使用

  与处理器映射器 HandlerMapping 一样,spring web 也为处理器适配器 HandlerAdapter 提供了多种实现,如 RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、SimpleServletHandlerAdapter 等,同时开发者可自定义实现 HandlerAdapter。

  spring web 要求须为 HandlerAdapter 的实现指定顺序,其中 spring web 为四种常用实现指定的顺序依次为 RequestMappingHandlerAdapter、HandlerFunctionAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter。可通过 @Oerder 注解指定应用顺序,值越小应用优先级越高,若未指定则默认排在最后。DispatcherServlet 初始化时(即 调用 initHandlerAdapters() 方法时)会将 HandlerAdapter 的实现实例收集起来,排序后缓存在 handlerAdapters map 集合中。

  当某个请求到达 DispatcherServlet 时,其会先在处理器映射器中匹配到对应的处理器方法对象,然后然后挨个儿遍历收集起来的处理器适配器实例(handlerAdapters map 集合),调用其 supports() 方法判断当前适配器是否支持适配该处理器,若支持则返回适配器,卒后调用适配器的 handle() 方法处理请求。

  • 1、准备数据绑定工厂 WebDataBinderFactory,其负责数据类型转换、数据绑定。
  • 2、准备模型工厂 ModelFactory,用来处理模型数据。
  • 3、准备 mvc 容器 ModelAndViewContainer,用来存放 model 数据。
  • 4、根据处理器方法对象(HandlerMethod)创建 ServletInvocableHandlerMethod 对象,并为其设置数据绑定工厂、参数名解析器等属性。
  • 5、获取请求携带的参数列表。
  • 6、使用参数名发现器 ParameterNameDiscoverer 解析处理器方法参数名。
  • 7、使用参数解析器组合器 HandlerMethodArgumentResolverComposite 中的参数解析器解析参数,解析时会使用数据绑定工厂 WebDataBinderFactory 中的类型转换器对参数进行必要的类型转换,然后将其绑定到处理器方法要求的入参类型上。最后返回参数列表。
  • 8、使用反射方式调用处理器方法。
  • 9、使用返回值处理器组合器 HandlerMethodReturnValueHandlerComposite 中的返回值处理器对调用结果进行处理。最后得到 ModelAndView 结果。
  • 10、此时,若 ModelAndView 为空则说明处理器方法的返回结果不是 ModelAndAview,可能是个 json,则使用消息转换器 HttpMessageConvertor 对结果进行转换,然后返回。
  • 11、若 ModelAndView 不为空,则使用视图解析器 ViewResolver 进行视图解析,然后进行视图渲染,最后返回。
  • 12、若则处理请求的整个过程中出现异常,则会由异常解析器 ExceptionResolver 进行一场解析并处理。

3 参数解析器与类型转换器

3.1 参数解析

  参数解析的主要作用是将请求中携带的参数解析成符合处理器方法行参的格式。这其中主要包括参数名解析和数据类型转换。其中参数名解析的目的是实参与行参的正确匹配,数据类型转换的目的是实参与行参类型的匹配。

3.1.1 参数解析器

  spring web 中定义了 HandlerMethodArgumentResolver 处理器方法参数解析器接口来提供参数解析的功能。且 spring web 默认提供了多种参数解析器实现。常用实现类关系图如下:

handler-method-argutment-resolver

  • HandlerMethodArgumentResolver:

    即 spring web 中处理器方法参数解析器接口,该接口负责两件事,一是判断当前参数解析器是否支持解析指定的参数,二是解析参数。

    boolean supportsParameter(MethodParameter parameter);
    
    // 返回解析出来的参数值
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
    
  • RequestParamMethodArgumentResolver:即解析被 @RequestParam 注解标注的参数,useDefaultResolution 为 true 表示该注解可以省略。

  • PathVariableMethodArgumentResolver:即解析被 @PathVariable 注解标注的参数。

  • RequestHeaderMethodArgumentResolver:即解析被 @RequestHeader 注解标注的参数。

  • ServletCookieValueMethodArgumentResolver:即解析被 @CookieValue 注解标注的参数。

  • ExpressionValueMethodArgumentResolver:即解析被 @Value 注解标注的参数。

  • ServletRequestMethodArgumentResolver:即解析 HttpServletRequest 类型的参数。

  • ServletModelAttributeMethodProcessor:即解析被 @ModelAttribute 注解标注的参数。

  • RequestResponseBodyMethodProcessor:即解析被 @RequestBody 注解标注的参数。

  • HandlerMethodArgumentResolverComposite:即参数解析器组合器,该类中维护了一个参数解析器列表 argumentResolvers,同时实现了 HandlerMethodArgumentResolver 接口的 supportsparameter() 与 resolveArgument() 方法。在处理器适配器 HandlerAdapter 初始化时会初始化 argumentResolvers(即将默认的各种参数解析器实例维护到 argumentResolvers 中),在实际使用时会调用组合器实现的 supportsparameter() 和 resolveArgument() 方法进行参数解析。这俩方法内部会遍历 argumentResolvers,然后调用各自的实现。

注:@RequestParam、@CookieValue 等注解中的参数名可通过 ‘${}’、‘#{}’ 操作符动态指定。

3.1.2 参数名发现器

  默认情况下,java 代码在被编译后不会产生方法参数名信息,方法参数名会变成 var1、var2 的形式存在于字节码文件中,这样我们在进行实参绑定时就获取到对应的行参名了。但我们可以在编译时添加相关参数使方法参数名生成在字节码文件中。

  • -parameters:

    在编译时添加 -parameters 参数,则可生成参数名信息,这些参数名可通过反射拿到。支持类和接口(即类和接口中的方法对应的参数名都可生成)。

  • -g:

    在编译时添加 -g 参数,则可生成调试信息,其中就包含方法参数名信息,其会以本地变量表 LocalVariableTable 的方式存在,其内容可通过 asm 技术拿到。只支持类不支持接口(接口不会生本地变量表,因此不会包含方法参数名信息)。

  注:spring boot 项目会在编译时默认添加 -parameters 参数,且大部分编译器在编译时会添加 -g 参数。

  spring web 中定义了 ParameterNameDiscoverer 接口来提供获取参数名的功能,且提供了三种默认的实现,其分别是:

  • StandardReflectionParameterNameDiscoverer:即以反射来获取参数名。
  • LocalVariableTableParameterNameDiscoverer:即以 asm 技术获取参数名。
  • DefaultParameterNameDiscoverer:整合了以上两种实现。

  注:mybatis 中的 mapper 是接口,不会生成本地变量表,因此需要使用 @Param 注解来协助获取参数名。

3.2 类型转换器

  spring web 中使用了 spring 核心模块提供的数据类型转换接口。spring 中共有两种数据类型转换接口,分别是 spring 提供的 ConversionService 系列和 jdk 提供的 PropertyEditorRegistry 系列。且 spring 又对外提供了一种综合类型转换接口 TypeConverter,该接口整合了 ConversionService 与 PropertyEditorRegistry 接口的功能。且 PropertyEditorRegistry 与 ConversionService 可通过 FormatterPropertyEditorAdapter 适配器进行转换。其相关了关系图如下所示:

type-converter

  • spring 提供的 ConversionService

    • Printer:将其它类型转换为 String 类型。
    • Parser:将 String 类型解析为其它类型。
    • Formatter:整合了 Printer 和 Parser 的功能。
    • Converter:将 S 类型转换为 T 类型。
    • Converters:维护了 GenericConverter 集合,GenericConverter 是由 Printer、Parser、Converter 通过适配器转化而来。
    • FormattingConversionService:ConversionService 接口的实现类。
  • jdk 提供的 PropertyEditorRegistry

    • PropertyEditor:将 String 与其它类型进行相互转换。
    • PropertyEditorRegistry:维护了多个 PropertyEditor 类型转换器。
  • spring 提供的 TypeConverter

    • SimpleTypeConverter:进行简单的数据类型转换。
    • BeanWrapperImpl:为 bean 的属性进行赋值,通过 property 实现(即依赖于 set 方法)。
    • DirectFieldAccessor:为 bean 的属性进行赋值,通过 field 实现(即直接对 field 进行赋值)。
    • DataBinder:为 bean 进行数据绑定,当需要进行必要的类型转换时,由 directFieldAccessor 属性决定使用 property 实现还是 field 实现。
  • TypeConverterDelegate

    类型转换委托器,当 TypeConverter 进行实际的类型转换时实际上是委托给 TypeConverterDelegate 委托器实现的。该委托器聚合了 ConversionService 和 PropertyEditorRegistry。而委托器在进行具体的转换操作时会按照以下顺序来选择具体的类型转换器:

    • 首先检查是否存在自定义类型转换器。
    • 其次检查是否存在 ConversionService 类型转换器。
    • 然后使用默认的 PropertyEditor 转换器。
    • 最后使用特殊的类型转换器。

  spring web 提供了一个数据绑定工厂 WebDataBinderFactory 来进行类型转换和数据绑定。且该工厂可添加指定的类型转换器(包括自定义的)。扩展类型转换器可通过以下方式:

  • 扩展 PropertyEditor,需要配合 @InitBinder 注解来实现。
  • 扩展 ConversionService。
  • 同时扩展 PropertyEditor 个 ConversionService,此时将会使用扩展的 PropertyEditor 转换器进行转换。

4 返回值处理器与消息转换器

  返回值处理器的作用是将处理器方法调用的返回值处理成处理器方法要求的格式。因为处理器方法执行的结果是 ModelAndView 对象,假如处理器方法要求的返回值为 String、HttpEntity、HttpHeaders、自定义对象、json 时,就需要对执行结果 ModelAndView 作出进一步的处理。且,若处理器方法被 @ResponseBody 注解标注时,此时需要消息转换器 HttpMessageConverter 的协助,将执行结果转换为 json 串。

4.1 返回值处理器

  spring web 定义了处理器方法返回值处理器接口 HandlerMethodReturnValueHandler 来提供了返回值处理的功能。同时,spring web 提供了多种默认实现,其类关系图如下:

handler-method-return-value-handler

  • HandlerMethodReturnValueHandler:

    即处理器方法返回值处理器接口。其只做两件事,即判断当前返回值处理器是否支持处理指定的返回值类型;处理返回值。

    boolean supportsReturnType(MethodParameter returnType);
    
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
    
  • ModelAndViewMethodReturnValueHandler:处理 ModelAndView 类型返回值。

  • ViewNameMethodReturnValueHandler:处理 String 类型返回值,该返回值会被当作视图名处理。

  • ServletModelAttributeMethodProcessor:处理被 @ModelAttribute 注解标注的处理器方法的返回值。annotationNotRequired 属性为 true 时表示该注解可省略。

  • HttpEntityMethodProcessor:处理 HttpEntity<> 类型返回值。该返回值处理器需要消息转换器的配合。ModelAndViewContainer 中的 requestHandled 属性表示请求是否已处理,当其为 true 时,则表示请求已处理完成,则不会继续后续的视图解析流程。因为当返回值类型为 HttpEntity<> 时不需要进行视图解析处理,所以在该返回值处理器处理返回值时会将 requestHandled 设置为 true。

  • HttpHeadersReturnValueHandler:处理 HttpHeaders 类型返回值。同 HttpEntity 一样,其也不需要视图解析,而是进行消息转换,所以会将 ModelAndViewContainer#requestHandled 设置为 true。

  • RequestResponseBodyMethodProcessor:处理被 @ResponseBody 注解标注的处理器方法的返回值。其不需要视图解析,而是进行消息转换,故会将 ModelAndViewContainer#requestHandled 设置为 true。

  • HandlerMethodReturnValueHandlerComposite:即返回值处理器组合器,其使用了组合模式,维护了返回值处理器集合 returnValueHandlers,在处理器适配器初始化时会创建默认的返回值处理器实例,以及自定义的返回值处理器实例,将其维护到该集合中。进行返回值处理时会调用该组合器实现的 supportsReturnType() 方法和 handleReturnValue() 方法,在这俩方法内在遍历调用具体的返回值处理器进行返回值处理。

4.2 消息转换器

  消息转换器,其作用是 java 对象与 json 的相互转换,底层依赖于 jackson。

  spring web 定义了 HttpMessageConverter 接口来提供消息转换的功能。

  部分返回值处理器需要消息转换器的配合使用,如 RequestResponseBodyMethodProcessor,该返回值处理器负责解析 @RequestBody 注解和 @ResponseBody 注解,而消息转换器(实际为 MappingJackson2HttpMessageConverter 实现)则负责消息转换。在消息转换时,会涉及到 MediaType 的选择顺序问题:

  • 首先看 @RequestMapping 注解上是否指定(如 produces = “application/json”),亦或是 response 的 ContentType 属性。
  • 其次看 request 的 header 中是否指定 Accept。
  • 最后看 HttpMessageConverter 的先后顺序,谁支持就使用谁。

5 异常处理器

  spring web 中定义了处理器一场解析器接口 HandlerExceptionResolver 来处理 spring web 中的异常。同时 spring web 提供了多种默认实现,其类关系如下:

handler-exception-resolver

  • HandlerExceptionResolver:

    即处理器异常解析器,其负责处理处理 spring web 中的异常。

    ModelAndView resolveException(
    			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
    
  • ExceptionHandlerExceptionResolver:

    其负责处理通过 @ExceptionHandler 注解指定的异常。

  • ResponseStatusExceptionHandler:

    其负责处理 @ResponseStatus 注解相关。

  • DefaultHandlerExceptionResolver:

    即默认异常解析器实现,其负责处理 spring web 中的大部分异常,如 HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException、MissingPathVariableException、MissingServletRequestParameterException 等等等。

  • HandlerExceptionResolverComposite:

    即异常解析器组合器,其使用组合模块,收集异常解析器实例(包括自定义)维护在 resolvers 集合中。当处理某个异常时,会调用该组合器实现的 resolveException() 方法来处理异常。该方法中会遍历所有异常解析器,调用其各自的实现,知道异常处理为止。在 DispatcherServlet 初始化时会初始化异常解析器(即调用 initHandlerExceptionResolvers() 方法时),且会按照指定顺序排序,处理异常时也会按照一定顺序应用。其默认顺序为 ExceptionHandlerExceptionResolver、ResponseStatusExceptionHandler、DefaultHandlerExceptionResolver。

  ExceptionHandlerExceptionResolver 是用来处理 @ExceptionHandler 注解指定异常的实现。其初始化时(即调用 afterPropertiesSet() 方法时)会初始化默认的参数解析器和返回值处理器,用来解析和处理 resolveException() 方法的入参和返回值。同时会解析 ControllerAdviceBean(即被 @ControllerAdvice 注解标注的 bean)中通过 @ExceptionHandler 注解定义的异常处理器,并将解析结果添加到异常解析器集合中(即 resolvers)。

6 @ControllerAdvice

  @ControllerAdvice,其作用是对所有 controller 进行增强(并非是通过 aop 式增强)。其提供了四种增强点,分别是:

  • @InitBinder:该注解可对所有控制器或部分控制器补充数据类型转换器。
  • @ModelAttribute:被该注解标注的处理器入参或返回值会被当作模型数据补充到控制器的执行过程中。
  • Request/ResponseBodyAdvice:该接口可对处理器方法入参 body 或返回值 body 进行特殊处理。
  • @ExceptionHandler:异常处理。

6.1 @InitBinder

  该注解可对所有控制器或部分控制器补充数据类型转换器(如自定义的数据类型转换器)。该注解由 RequestMappingHandlerAdapter 解析。

  该注解可作用于:

  • @ControllerAdvice:

    可作用于 @ControllerAdvice 注解标注的类中的方法上,此时,RequestMappingHandlerAdapter 会在初始化时对其解析并记录在 initBinderAdviceCache map 缓存中,在调用 getDataBinderFactory() 方法获取 WebDataBinderFactory 实例时会对 initBinderAdviceCache 缓存进行再处理,并将处理结果和 initBinderCache 缓存一同绑定到 WebDataBinderFactory 工厂中。

  • @Controller:

    可作用于被 @Controller 注解标注的类的处理器方法上。此时,在该处理器方法在第一次被调用时会由 RequestMappingHandlerAdapter 对其进行解析和缓存,且会直接缓存到 initBinderCache map 缓存中,同时将 initBinderCache 绑定到 WebDataBidnerFactory 工厂中。

6.2 @ModelAttribute

  被该注解标注的处理器方法参数或返回值会被当作模型数据补充到控制器执行过程中。该注解在 RequestMappingHandlerAdapter 初始化由 ServletModelAttributeMethodProcessor(参数解析器,亦为返回值处理器)进行解析处理。

  该注解可作用于:

  • @ControllerAdvice:

    可作用于被 @ControllerAdvice 注解标注的类中中的方法上,此时,RequestMappingHandlerAdapter 会在初始化时对其解析并记录在 modelAttributeAdviceCache map 缓存中,在调用 getModelFactory() 方法获取 ModelFactory 实例时会对 modelAttributeAdviceCache 缓存进行再处理,并将处理器结果和 modelAttributeCache 缓存一同绑定到 ModelFactory 中。

  • @Controller:

    可作用于被 @Controller 注解标注的控制器中的处理器方法的入参和返回值上,此时,在该处理器方法在第一次被调用时会由 RequestMappingHandlerAdapter 对其进行解析和缓存,且会直接缓存到 modelAttributeCache map 缓存中,同时会将其绑定到 ModelFactory 工厂实例上。

6.3 Request/ResponseBodyAdvice

  Request/ResponseBodyAdvice 即 RequestBodyAdvice 接口和 ResponseBodyAdvice 接口,这两个接口的作用分别是对处理器方法入参 body 和返回值 body 进行特殊处理,如添加全局参数、统一响应返回值对象等。该接口的实现实例会在 RequestMappingHandlerAdapter 初始化时被收集到 requestResponseBodyAdvice 集合中。

6.4 @ExceptionHandler

   被该注解标注的方法可作用 spring web 中的异常处理器。该注解由 ExceptionHandlerExceptionResolver 进行解析并记录。

  该注解可作用于:

  • @ControllerAdvice:

    可作用于被 @ControllerAdvice 注解标注的类中的方法上,此时,ExceptionHandlerExceptionResolver 在初始化时会对其进行解析并记录到 exceptionHandlerAdviceCache map 缓存中,在调用 getExceptionHandlerMethod() 方法获取遗产处理方法时会对其进行再处理。

  • @Controller:

    可作用于被 @Controller 注解标注的控制器的方法上,此时,在 ExceptionHandlerExceptionResolver 第一次处理异常时会对其进行解析并记录。注,控制器中的异常处理方法值负责处理该控制器中产生的异常。

  @ExceptionHandler 处理异常的工作流程:

  • 首先看当前处理器中是否存在异常处理方法(即被 @ExceptionHandler 注解标注的方法)。
  • 其次看解析到的 exceptionHandlerCache 缓存中的异常处理方法。
  • 处理异常。

  @ControllerAdvice 的四种增强方式在适配器处理请求过程中的时间点如图所示:

spring-web-mvc-process

7 spring web 工作流程

  spring web mvc 的工作流程大致如图所示:

spring-web-mvc
// ...

由…这一分钟开始计起春分秋雨间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18023.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python调用海康sdk报错问题

sdk参考&#xff1a; (68条消息) Python调用海康威视网络相机_调用海康SDK_python 海康威视_有一点点麻瓜的博客-CSDN博客https://blog.csdn.net/yinweizhehd/article/details/118722052 报错1&#xff1a; 生成解决方案的时候&#xff0c;显示LNK2001&#xff1a;无法解析的…

如果你访问了某个网站,又不想让人知道怎么办?

问大家一个问题&#xff1a;如果你访问了某个网站&#xff0c;又不想让人知道怎么办&#xff1f; 你可能会说&#xff0c;把浏览器浏览历史记录清除&#xff0c;或者直接用无痕模式。 如果你只能想到这一层&#xff0c;那只能说图young&#xff01; 这么说吧&#xff0c;理论…

基于RK3588的8K智能摄像机方案设计

设计了一款基于石墨烯散热的8 K智能摄像头&#xff0c;主控采用瑞芯微RK3588&#xff0c;传感器采用索尼IMX435&#xff0c; 通过HDMI2.1将传感器采集到的图像发送到8 K显示器&#xff0c;实现端到端的8 K呈现&#xff0c;为了确保摄像头性能稳定&#xff0c;本 设计采用石墨烯…

计算机网络安全--期末

计算机网络安全绪论 计算机网络实体是什么 计算机网络中的关键设备&#xff0c;包括各类计算机、网络和通讯设备、存储数据的媒体、传输路线…等 典型的安全威胁有哪些 ★ ⋆ \bigstar\star ★⋆ 窃听(敏感信息被窃听)重传(被获取在传过来)伪造(伪造信息发送&#xff09;篡…

《花雕学AI》30:ChatGPT的资料来源比例排名前20名是什么?

引言&#xff1a;ChatGPT是一款由OpenAI开发的人工智能聊天机器人&#xff0c;它可以回答各种问题&#xff0c;并生成创意内容&#xff0c;如诗歌、故事、代码等。 ChatGPT的核心技术是基于GPT-3.5和GPT-4的大型语言模型&#xff0c;它可以利用从网路上收集的大量文本资料来进行…

MySQL执行顺序

MySQL执行顺序 MySQL语句的执行顺序也是在面试过程中经常问到的问题&#xff0c;并且熟悉执行顺序也有助于SQL语句的编写。 SELECT FROM JOIN ON WHERE GROUP BY HAVING ORDER BY LIMIT执行顺序如下&#xff1a; FROM ON JOIN WHERE GROUP BY # (开始使用别名) SUM # SUM等…

备战2个月,四轮面试拿下字节offer...

背景 菜 J 一枚&#xff0c;本硕都是计算机&#xff08;普通二本&#xff09;&#xff0c;2021 届应届硕士&#xff0c;软件测试方向。个人也比较喜欢看书&#xff0c;技术书之类的都有看&#xff0c;最后下面也会推荐一些经典书籍。 先说一下春招结果&#xff1a;拿下了四个…

vmware 安装Kylin-Desktop-V10-SP1-General-Release-2203-X86_64.iso

下载 官网&#xff1a;国产操作系统、银河麒麟、中标麒麟、开放麒麟、星光麒麟——麒麟软件官方网站 (kylinos.cn) 点击桌面操作系统 选择No1 点击申请试用 填写相关信息&#xff0c;点击立即提交&#xff0c;就会获取到下载连接&#xff0c; 点击下载按钮等待下载完成即可 安…

Go有序map:orderedmap

有序映射 与传统的无序映射&#xff08;Map&#xff09;不同&#xff0c;orderedmap包中的有序映射&#xff08;OrderedMap&#xff09;可以记录键值对的插入顺序。orderedmap提供了一些有用的API&#xff0c;用来存储、删除、查询和遍历键值对。 获取OrderedMap 你可以通过Ord…

编译安装最新的Linux系统内核

现在还有不少机器是CentOS8 Stream系统&#xff0c;虽然上了贼船&#xff0c;不影响用就是了。8的编译和7大同小异&#xff0c;只是踩了更多的坑在这里记录一下&#xff0c;或许会帮到看到的朋友。 安装编译环境 CentOS8安装必要的包 yum groupinstall "Development Too…

2022年NOC大赛编程马拉松赛道复赛图形化高年级A卷-正式卷,包含答案

目录 单选题: 多选题: 编程题: 下载打印文档做题: 2022年NOC大赛编程马拉松赛道复赛图形化高年级A卷-正式卷,包含答案 单选题:<

《Netty》从零开始学netty源码(五十三)之PoolThreadCache的功能

allocateNormal 在前面分析PoolArena的分配内存的方法中&#xff0c;每次分配都是先从本地线程缓存中分配&#xff0c;本地线程缓存PoolThreadCache的分配方法如下&#xff1a; 分配过程主要有两步&#xff1a; 从PoolThreadCache的缓存数组中获取相应大小的缓存cache将需要…

桌面虚拟化的优势

启用基于云的虚拟桌面基础架构 &#xff08;VDI&#xff09; OpenText™ Exceed TurboX™ &#xff08;ETX&#xff09; 长期以来一直是虚拟化在 Linux 主机上运行的图形要求苛刻的软件的黄金标准。ETX 最新版本&#xff08;12.5&#xff09;增加了许多Microsoft Windows功能&…

智能座舱的“宏大蓝图”和“残酷现实”

配图来自Canva可画 2023年上海车展各大车企发布新车、新配置和新战略好不热闹&#xff0c;“智能驾驶”、“智能座舱”等关键词频频出现&#xff0c;智能化已然成为车企技术比拼的关键。 Unity中国发布最新智能座舱解决方案&#xff0c;可为车企提供成熟、可量产落地的HMI&…

什么是点对点传输?什么是点对多传输

点对点技术&#xff08;peer-to-peer&#xff0c; 简称P2P&#xff09;又称对等互联网络技术&#xff0c;是一种网络新技术&#xff0c;依赖网络中参与者的计算能力和带宽&#xff0c;而不是把依赖都聚集在较少的几台服务器上。P2P网络通常用于通过Ad Hoc连接来连接节点。这类网…

深度学习 - 46.DIN 深度兴趣网络

目录 一.引言 二.摘要 ABSTRACT 三.介绍 INTRODUCTION 1.CTR 在广告系统的作用 2.传统 MLP 存在的问题 3.DIN 的改进 四.近期工作 RELATEDWORK 1.传统推荐算法 2.用户行为抽取 五.背景 BACKGROUD 六.深度兴趣网络 DEEP INTEREST NETWORK 1.特征表示 Feature Repres…

triton 疑难手册

config.pbtxt 配置参数手册 backend或platform参数用于指示nvidia triton用对应的backend加载模型参数,它的使用示例如下: name: "xxx" platform: "pytorch_libtorch"max_batch_size: 8 input [ {name: "input0"data_type: TYPE_UINT8dims: …

ansible常用命令

目录 1、列出默认清单文件中的所有受管主机 2. 列出自定义清单文件中的所有受管主机&#xff08;自定义清单文件&#xff1a;inventory&#xff09; 3、运行playbook 4、创建需要输入文件密码的加密的文件 5、创建用密码文件的加密的文件 6、查看加密的文件内容 7、向已有…

助力工业物联网,工业大数据之ODS层构建:申明分区代码及测试【十】

文章目录 知识点13&#xff1a;ODS层构建&#xff1a;申明分区代码及测试知识点14&#xff1a;ODS层与DWD层区别知识点15&#xff1a;DWD层构建&#xff1a;需求分析知识点16&#xff1a;DWD层构建&#xff1a;建库实现测试知识点17&#xff1a;DWD层构建&#xff1a;建表实现测…

【NLP开发】Python实现聊天机器人(ChatterBot,集成web服务)

&#x1f37a;NLP开发系列相关文章编写如下&#x1f37a;&#xff1a; &#x1f388;【NLP开发】Python实现词云图&#x1f388;&#x1f388;【NLP开发】Python实现图片文字识别&#x1f388;&#x1f388;【NLP开发】Python实现中文、英文分词&#x1f388;&#x1f388;【N…