史上最烂 spring web 原理分析

news2024/11/22 16:52:10

盗引·下篇·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
// ...

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关键词高亮显示浏览器 Edge 插件开发 源码

插件功能 将网页中的关键词高亮显示 项目结构 $ tree . |-- content # 注入到网页中的 js 与 css | |-- content.css | -- content.js |-- icons # 插件用到的图标 | |-- icon128.png | |-- icon16.png | |-- icon32.png | -- icon48.png |-- manifest.json # …

Python+Yolov5电梯口跌倒识别

程序示例精选 PythonYolov5电梯口跌倒识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov5电梯口跌倒识别>>编写代码&#xff0c;代码整洁&#xff0c;规则&#x…

【AGC】applinking服务接入产生崩溃问题

【关键字】 AGC、applinking、崩溃 【问题描述】 开发者反馈在应用中集成了AGC的applinking服务&#xff0c;在打开app时遇到了一些问题。具体如下所述&#xff1a; 在接入app linking后&#xff0c;从浏览器中访问短链接&#xff0c;能正常唤起app, 但app启动后就崩溃了&am…

UAD142A01 3BHE012551R0001使用以太网交叉电缆,您也可以直接连接。

​ UAD142A01 3BHE012551R0001使用以太网交叉电缆&#xff0c;您也可以直接连接。 如何将 MicroLogix PLC 连接到计算机并将程序下载到 MicroLogix 1100 MicroLogix PLC由美国罗克韦尔自动化旗下知名工业自动化厂商Allen-Bradley设计。MicroLogix 1100 主要用于小型工业。我们在…

【视频解读】动手学深度学习V2_02深度学习介绍

1.AI地图 人工智能的地图&#xff0c;x轴是不同模式&#xff0c;由符号学到概率模型 到机器学习&#xff0c;Y轴是我想做的东西&#xff0c;最底下的是感知&#xff0c;我得了解这是什么东西&#xff0c;然后做推理&#xff0c;形成自己的知识&#xff0c;最后做规划。最底层的…

07-Vue技术栈之(组件之间的通信方式)

目录 1、组件的自定义事件1.1 绑定自定义事件&#xff1a;1.1.1 第一种方式1.1.2 第二种方式1.1.3 自定义事件只触发一次 1.2 解绑自定义事件1.3绑定原生DOM事件1.4 总结 2、全局事件总线&#xff08;GlobalEventBus&#xff09;2.1 应用全局事件总线 3、 消息订阅与发布&#…

SwiftUI 极简实现文本摆动弹性动画

概览 SwiftUI 为我们来了界面设计和调试上的便利&#xff0c;只需几行代码我们就能实现一个不错的文本动画效果&#xff1a; 如上图所示&#xff0c;我们在 SwiftUI 中基本还没发力&#xff0c;就实现了文本摆动弹性动画。 这究竟是怎么做到的呢&#xff1f; 无需等待&#…

英文论文(sci)解读复现【NO.7】基于注意机制的改进YOLOv5s目标检测算法

此前出了目标检测算法改进专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读发表高水平学术期刊中的 SCI论文&a…

Loadrunner性能测试(一)

备注&#xff1a;电脑最好安装有IE浏览器 一、下载安装包 链接&#xff1a;https://pan.baidu.com/s/1f5Sw0QK5zrLCU1EbN01evg?pwdbite 提取码&#xff1a;bite 包含的文件有&#xff1a; 二、安装loadrunner 注意&#xff0c;以下教程仅展示需要特别注意的步骤&#x…

PHP学习笔记第一天

前言 作者简介&#xff1a;不知名白帽&#xff0c;网络安全学习者。 博客主页&#xff1a;不知名白帽的博客_CSDN博客-网络安全,CTF,内网渗透领域博主 网络安全交流社区&#xff1a;https://bbs.csdn.net/forums/angluoanquan 目录 PHP语法 基本的PHP语法 PHP的数据类型 PH…

Kubernetes 集群中某个节点出现 Error querying BIRD: unable to connect to BIRDv4 socket

1. 问题描述 Readiness probe failed: calico/node is not ready: BIRD is not ready: Error querying BIRD: unable to connect to BIRDv4 socket: dial unix /var/run/calico/bird.ctl: connect: connection refusedReadiness probe failed: 2023-05-04 22:13:23.706 [INFO]…

LiangGaRy-学习笔记-Day10

1、知识回顾 1.1、rpm依赖报错问题 rpm安装的时候&#xff0c;会有依赖报错rpm安装httpd服务&#xff0c;体现报错 #rpm安装httpd [rootNode1 ~]# rpm -ivh /mnt/cdrom/Packages/httpd-2.4.6-88.el7.centos.x86_64.rpm warning: /mnt/cdrom/Packages/httpd-2.4.6-88.el7.ce…

自己组装的电脑怎么用U盘安装系统操作教学

自己组装的电脑怎么用U盘安装系统操作教学分享。有的用户使用台式机的时候&#xff0c;会自己去进行硬件的组装&#xff0c;但是这样的电脑在安装好了之后&#xff0c;里面还没有系统&#xff0c;需要进行安装。如果你不知道怎么去安装&#xff0c;可以来看看以下的操作方法。 …

HummerRisk 使用教程:源码检测

HummerRisk 是开源的云原生安全平台&#xff0c;以非侵入的方式解决云原生环境的安全和治理问题。核心能力包括混合云的安全治理和云原生安全检测。 本文将介绍HummerRisk中「源码检测模块」的功能&#xff0c;包括如何配置项目源码&#xff0c;以及使用源码检测规则进行安全检…

香港top5功能完善炒期货投资app软件排名(最新评测)

选择一款合适的炒期货投资软件对于投资者来说至关重要。考虑软件稳定、交易流畅度、交易品种、数据可靠性、而且还要考虑费用等多方面因素。 首先&#xff0c;软件的稳定性很重要。选用稳定性高的软件可以避免如断电、手机或电脑死机等突发状况&#xff0c;保证交易安全顺畅。…

FreeRTOS 低功耗 Tickless 模式

文章目录 一、低功耗模式1. 睡眠(Sleep)模式2. 停止(Stop)模式3. 待机(Standby)模式 二、Tickless 模式详解1. 如何降低功耗&#xff1f;2. Tickless 具体实现 一、低功耗模式 STM32 本身就支持低功耗模式&#xff0c;有三种低功耗模式&#xff1a; ● 睡眠(Sleep)模式。 ● 停…

5_服务编排_docker-compose

服务编排之Docker Compose 微服务架构的应用系统中一般包含若干个微服务&#xff0c;每个微服务一般都会部署多个实例&#xff0c;如果每个微服务都要手动启停&#xff0c;维护的工作量会很大。 要从Dockerfile build image 或者去dockerhub拉取image 要创建多个container 要…

GB/T25915.1法规基本标准-洁净室按粒子浓度划分洁净

《GB/T25915.1-2021洁净室及相关受控环境 第一部分&#xff1a;按粒子浓度划分空气洁净度等级》等4部国家标准。 今天小编跟大家分享一下参编的GB/T25915.1-2021相关法规文件内容&#xff0c;帮助大家更好的了解相关法规知识。 1 范围 本文件规定了按空气中悬浮粒子浓度划分洁…

9:00进去,9:05就出来了,这问的也太···

从外包出来&#xff0c;没想到死在另一家厂子了。 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推…

纯前端 根据目录解析word,拆分不同段落

前期回顾 两分钟学会 制作自己的浏览器 —— 并将 ChatGPT 接入_0.活在风浪里的博客-CSDN博客自定义浏览器&#xff0c;并集合ChatGPT&#xff0c;源码已公开https://blog.csdn.net/m0_57904695/article/details/130467253?spm1001.2014.3001.5501 &#x1f44d; 本文专栏…