记录一下SpringMVC部分基本原理,防止以后老年痴呆。。。。

我将介绍以下原理:

  • 请求映射原理
  • 控制器方法参数绑定原理
  • HttpMessageConverter原理
  • 内容协商原理
  • 自定义类型转换器

前端页面

<form action=" test1" method="post">
    <input type="text" name="studentInfo" placeholder="输入学生信息以,间隔"><br>
    <input type="submit" value="自定义类型转换器转换">
</form>

控制器方法

@Controller
public class StudentController {
    @ResponseBody
    @PostMapping("/test1")
    public Student test1(@RequestParam("studentInfo")Student student,
                         Model model,Map map,
                        @RequestHeader("accept")String accept){
        /*
            请求映射原理
            控制器方法参数绑定原理
            自定义类型转换器原理
            参数Model,Map原理
         */
        return student;
    }
}

自定义类型转换器

public class MyConverter implements Converter<String, Student> {
    @Override
    public Student convert(String source) {
        Student student = new Student();
        if (source.contains(",")){
            String[] strings = source.split(",");
            student.setName(strings[0]);
            student.setAge(Integer.valueOf(strings[1]));
            student.setClassName(strings[2]);
        }
        System.out.println("类型转换器中"+student);
        return student;
    }
}

配置WebMvcConfigurer,定制化SpringMVC

@Component
public class MyConfig {

    /*配置WebMvcConfigurer,定制化SpringMVC的功能*/
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            /*配置自定义WebConversionService,做自定义类型转换器*/
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new MyConverter());
            }
        };
    }
}

请求过程

  • 请求映射原理
    1. 本案例中,当提交请求时,使用Post方式,在请求体中以studentInfo=数据,提交到后台
      请求进入DispatcherServlet类中doDispatch方法中,在该方法中对请求进行处理

      第一个重要方法
      就是getHandler方法,在该方法中根据请求找到能处理该请求的处理器,在其中就涉及到请求映射的原理

    2. 进入getHandler方法,在该方法中,遍历容器启动时就已经自带的5个映射处理器,HandlerMapping
      映射器中存储着请求路径与控制器方法之间的映射。
      例如:我们使用的最常用的@RequestMapping注解,凡是使用这个注解,就由RequestMappingHandleMapping处理器映射器进行处理,@RequestMapping注解的value值
      与控制器方法名就以Key和Value的形式存储在RequestMappingHandleMapping处理器映射器之中。

    3. 通过处理器映射器,找到能处理请求的处理器方法,将处理器方法和与请求匹配的拦截器封装成处理器执行链,HandlerExecutionChain,将封装的处理器执行链返回给DispatcherServlet

  • 控制器方法参数绑定和自定义类型转换器原理
    1. 第二个比较重要的方法是getHandlerAdapter方法,在该方法中,通过遍历找到能支持执行该处理器方法的处理器适配器,HandlerAdapter,容器在启动时会有4个处理器适配器,最常用的处理器适配器是RequestMappingHandleAdapter,在该适配器中存储着非常多的组件对象,用于参数绑定,返回值处理等等。

      我们来看看RequestMappingHandlerAdapter中有多少个组件,以及这些组件的作用

      1
    2. 找到处理器映射器之后,执行与请求匹配到的拦截器的preHandle方法,当然,拦截器不是我们讨论的内容,就略微提一下。

    3. 执行完拦截器的preHandle方法后,处理器映射器执行控制器方法,返回给DispatcherServlet一个ModelAndView对象,在该对象中存储着模型数据和视图名,我们将进入该方法中,探究,如何进行参数解析,参数绑定以及返回值的处理

      再次进入handleInternal方法,在handleInternal方法中执行invokeHandlerMethod方法

    4. 来到invokeHandlerMethod方法,我们可以看到,在其中创建了一个ServletInvocableHandlerMethod对象,该对象是真正执行处理器方法的对象,并往该对象中设置了很多组件,例如数据绑定器工厂,参数解析器,返回值处理器等等。还创建了一个ModelAndViewContainer对象,该对象用于存储模型数据和视图名

      1
    5. 我们从上图中看到invocableMethod调用了invokeAndHandle方法,来到该方法中,首先是调用invokeForRequest方法执行目标方法,得到方法返回值,所以我们可以确定,是在invokeForRequest方法中完成参数绑定,目标方法执行的,这正是我们要找的参数绑定的原理探究,得到返回值后,调用了returnValueHandles属性的HandleReturnValue方法,看名字就知道,使用返回值处理器处理目标方法返回值了,这个之后再谈。

    6. 来到invokeForRequest方法中,果然如我们所料,先是调用getMethodArgumentValues方法确定每一个参数的值,再调用do Invoke方法执行目标方法。

    7. 进入getMethodArgumentValues方法之中,我们可以看到,先是确定目标方法中的每一个参数,再遍历每一个参数对象MethodParameter,这个参数对象是对目标方法参数进行了封装,其中有关于参数的类型,参数有什么注解等等,然后在循环中,遍历容器中所有的参数解析器,看哪一个参数解析器支持解析该参数,找到参数解析器后,调用参数解析器的resolveArgument方法解析参数。

      1

      目标方法参数数组

      1

      容器启动时所拥有的参数解析器

      1
    8. 由于本次案例第一个参数是Student且该参数使用@RequestParam注解,所以找到
      RequestParamMethodArgumentResolver参数解析器,调用该解析器的resolveArgument
      方法对目标方法参数进行绑定封装

      1 1 1
    9. 我们已经知道当binder.convertIfNecessary方法调用后,参数就已经封装完毕了,在该方法底层其实是使用WebDataBinder对象的ConversionService对象中众多的类型转换器。下面是WebDataBinder对象底层进行转换操作

      1 1 1

      OK,以上就是各个参数被绑定封装的过程,实际上就是通过WebDataBinder中的转换器进行参数类型转换,因为本案例中,容器中并没有从string转到Student类型的转换器,所以我们自定义了类型转换器为目标方法的Student参数封装。

  • HttpMessageConverter和内容协商原理
    1. 我们还是跟着上述请求过程看看,什么是内容协商,什么是HttpMessageConverter,以及他们的作用
      首先当我们根据参数解析器解析出所有的参数并封装成Object数组后,我们就开始真正执行目标方法了
      我们回到了上面第6步,进入doInvoke方法看看
1
  1. 当我们执行完控制器方法后,我们回到了上面的第5步,得到了方法返回值,本次案例中方法返回值是Student对象,下面一个重要方法就是找到匹配的返回值处理器,然后对返回值进行解析处理。

    1 1

    其实就是遍历容器中所有返回值处理器,看哪一个能解析返回值类型,即Student类型

    1 1
  2. OK,当我们找到了匹配的返回值处理器,接下来就是调用返回值处理器来解析返回值了

    1 1 1
  3. 内容协商的意思就是获取客户端可接收的媒体类型和服务端能够生产的媒体类型,进行匹配,匹配成功后就给客户端输出相应的媒体类型,看起来就像协商了一样,那么springmvc是如何知道客户端能够接收哪些媒体类型呢?实际上是根据内容协商策略管理器来获取的。
    获取前端可接收的媒体类型方法是:getAcceptableMediaTypes方法

    1 1

    看看默认的内容协商策略是如何获取客户端可接收的媒体类型吧

    1
  4. 我们已经知道,获取客户端可接收的媒体类型靠的是内容协商策略,那怎么知道服务器能生产什么样的媒体类型呢?getProducibleMediaTypes方法就是获取服务器能生产什么样的媒体类型

    1 1
  5. 找到客户端可接收媒体类型List,和服务器能提供的媒体类型List,通过双重for循环,找到
    相同的媒体类型,再调用匹配的消息转换器的write方法向response对象中写数据

    1
  6. 数据输出完毕后,回到讲参数绑定原理的第4步,通过调用getModelAndView方法向dispatcherServlet对象返回ModelAndView对象,该对象的模型数据和视图名都是从ModelAndViewContainer对象中获取的,模型数据就保存在该对象的defaultModel属性中,而视图或者视图名保存在view属性或者viewName属性中

    1
  • 总结

    至此,我们已经走过了整个请求响应的过程,需要注意的是,本案例的请求响应过程是不包括视图解析原理的
    我将会在下一个案例中讲关于视图解析器的原理,经过分析我们知道,

    • 参数绑定的原理就是使用参数解析器调用各种类型转换器来对请求参数进行转换和绑定。
    • 内容协商原理就是通过返回值处理器,找出满足客户端和服务器要求的媒体类型,再通过消息转换器写给
      客户端响应的媒体类型。
    • 其实还有很多细节原理,我还不甚了解,可能越往后学才能知道的更多吧。