Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上相关的知识,希望对你有一定的参考价值。

Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上


引言

前面已经详细介绍过了RequestMappingHandlerMapping是如何在初始化方法中搜集容器中所有标注了@Controller或者@RequestMapping注解的Bean的,然后解析将映射关系保存到映射中心。

在请求到来时,通过request请求对象提供的信息,去注册中心匹配获取到合适的,分别经过精确匹配或者模糊匹配,然后再进行最佳匹配,最终返回一个匹配上的HandlerMethod,交给父类AbstractHandlerMapping包装为HandlerExecutionChain,内部添加合适的拦截器。

无论是URL的精确匹配还是模糊匹配,最终都要交给对应的HandlerMethod持有的RequestMappingInfo,进行条件匹配,如果不满足,则返回null。

如果没有HandlerMethod能够处理当前请求,那么再判断是否部分匹配,即请求路径匹配上了,但是不满足RequestMappingInfo中其他限制条件,如请求头限制等,此时检查不满足条件的请求,抛出对应的异常。如果是URL没有匹配上,则返回给AbstractHandlerMapping的handler结果为null,最终在doDispatch方法中的noHandlerFound方法中抛出404异常。

Spring MVC注解Controller源码流程解析–映射建立

Spring MVC注解Controller源码流程解析–定位HandlerMethod

Spring MVC注解Controller源码流程解析—请求匹配中的容错处理

本文将对RequestMappingHandlerAdapter如何调用执行RequestMappingHandlerMapping返回的handler,即HandlerMethod的过程做出详细分析。


RequestMappingHandlerAdapter

RequestMappingHandlerAdapter调用执行HandlerMethod方法的过程比较复杂,这里先对RequestMappingHandlerAdapter调用执行HandlerMethod方法中涉及到的组件和思路进行讲解,最终再走一遍源码,大家就会非常清晰了。

对于RequestMappingHandlerAdapter来说,它的主要职责有以几个:

  • 解析控制器方法的参数列表,并从request请求对象中获取到相关参数值,并保存起来 (这个过程还涉及到参数类型转换问题,需要求助Spirng提供的类型转换模块支持)
  • 反射执行控制器方法,将先前准备好的参数值列表传入
  • 控制器方法执行完毕后,处理返回结果,并将返回结果统一转换为ModelAndView,供SpringMVC后续视图渲染组件使用

方法参数解析器

对于控制器方法参数解析而言,由于Spring支持多种注解形式来提示从哪里获取参数值,参数key是什么等等,因此如果使用一个参数解析器完成所有注解的解析,那么就成狗屎代码了,因此Spring采用一个参数解析器负责解析一个注解的形式:

spring常见的方法参数解析器有:

        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@abbc908
        org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@44afefd5
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@9a7a808
        org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@72209d93
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@2687f956
        org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@1ded7b14
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@29be7749
        org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@5f84abe8
        org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@4650a407
        org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver@30135202
        org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver@6a4d7f76
        org.springframework.web.servlet.mvc.method.annotation.ServletCookieValueMethodArgumentResolver@10ec523c
        org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver@53dfacba
        org.springframework.web.servlet.mvc.method.annotation.SessionAttributeMethodArgumentResolver@79767781
        org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver@78411116
        org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver@aced190
        org.springframework.web.servlet.mvc.method.annotation.ServletResponseMethodArgumentResolver@245a060f
        org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@6edaa77a
        org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver@1e63d216
        org.springframework.web.method.annotation.ModelMethodProcessor@62ddd21b
        org.springframework.web.method.annotation.MapMethodProcessor@16c3ca31
        org.springframework.web.method.annotation.ErrorsMethodArgumentResolver@2d195ee4
        org.springframework.web.method.annotation.SessionStatusMethodArgumentResolver@2d6aca33
        org.springframework.web.servlet.mvc.method.annotation.UriComponentsBuilderMethodArgumentResolver@21ab988f
        org.springframework.web.servlet.mvc.method.annotation.PrincipalMethodArgumentResolver@29314cc9
        org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@4e38d975
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@35f8a9d3

我们经常在Controller代码中使用的形式有:

    static class Controller 
        public void test(
                @RequestParam("name1") String name1, // name1=张三
                String name2,                        // name2=李四
                @RequestParam("age") int age,        // age=18
                @RequestParam(name = "home", defaultValue = "$JAVA_HOME") String home1, // spring环境上下文中获取数据
                @RequestParam("file") MultipartFile file, // 上传文件
                @PathVariable("id") int id,               //  /test/124   /test/id
                @RequestHeader("Content-Type") String header, //请求头获取参数值
                @CookieValue("token") String token,  //cookie中获取参数值
                @Value("$JAVA_HOME") String home2, // spring环境上下文中获取参数值  $ #
                HttpServletRequest request,          // request, response, session ...
                @ModelAttribute("abc") User user1,          //非简单对象类型的数据封装: name=zhang&age=18
                User user2,                          // name=zhang&age=18
                @RequestBody User user3              // json
        ) 
        
    
    
    static class User 
        private String name;
        private int age;

        public String getName() 
            return name;
        

        public void setName(String name) 
            this.name = name;
        

        public int getAge() 
            return age;
        

        public void setAge(int age) 
            this.age = age;
        

        @Override
        public String toString() 
            return "User" +
                   "name='" + name + '\\'' +
                   ", age=" + age +
                   '';
        
    

参数方法解析器单独使用案例:

  • 准备controller控制器对象-上面给出的
  • 准备mockRequest
 
    public static void main(String[] args) throws Exception 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // 准备测试 Request
        HttpServletRequest request = mockRequest();

        // 要点1. 控制器方法被封装为 HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        // 要点2. 准备对象绑定与类型转换
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        // 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        //多个解析器组合--组合模式体系
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                //  false 表示必须有 @RequestParam
                new RequestParamMethodArgumentResolver(beanFactory, false),
                new PathVariableMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(beanFactory),
                new ServletCookieValueMethodArgumentResolver(beanFactory),
                new ExpressionValueMethodArgumentResolver(beanFactory),
                new ServletRequestMethodArgumentResolver(),
                new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
                new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
                new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
        );

        // 要点4. 解析每个参数值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) 
            //获取当前方法参数上的注解名
            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
            //设置好spring提供的方法参数名解析器
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (composite.supportsParameter(parameter)) 
                // 支持此参数
                Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
                System.out.println("模型数据为:" + container.getModel());
             else 
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            
        
    


说明:


又添加了一遍@RequestParam和@ModelAttribute相关的注解解析器,并且设置对省略注解情况的参数解析,同时必须放在参数解析器列表的末尾。

参数解析器列表挑选的原理是找到第一个能够support支持处理当前参数的,然后直接返回该参数处理器进行解析处理。


如果要添加自定义的参数解析器:

自定义参数解析器优先于默认参数解析器被调用。

参数解析器小结:

  1. 初步了解 RequestMappingHandlerAdapter 的调用过程
    1. 控制器方法被封装为 HandlerMethod
    2. 准备对象绑定与类型转换
    3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
    4. 解析每个参数值
  2. 解析参数依赖的就是各种参数解析器,它们都有两个重要方法
    • supportsParameter 判断是否支持方法参数
    • resolveArgument 解析方法参数,返回参数值
  3. 常见参数的解析
    • @RequestParam
    • 省略 @RequestParam
    • @RequestParam(defaultValue)
    • MultipartFile
    • @PathVariable
    • @RequestHeader
    • @CookieValue
    • @Value
    • HttpServletRequest 等
    • @ModelAttribute
    • 省略 @ModelAttribute
    • @RequestBody
  4. 组合模式在 Spring 中的体现
  5. @RequestParam, @CookieValue 等注解中的参数名、默认值, 都可以写成活的, 即从 $ # 中获取

方法参数名解析器

正常情况下,java的class文件中是不会保存方法参数名相关信息的,如果要保留有以下两种方式:

  • 编译时加上-parameters参数,此时javac编译器在编译时,会在class文件中生成对应的参数表,此时我们通过反射就可以直接拿到参数名
        // 1. 反射获取参数名
        Method foo = Bean2.class.getMethod("foo", String.class, int.class);
        for (Parameter parameter : foo.getParameters()) 
            System.out.println(parameter.getName());
        

  • 如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
    1. 普通类, 会包含局部变量表, 用 asm 可以拿到参数名
    2. 接口, 不会包含局部变量表, 无法获得参数名 (这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名)
        // 2. 基于 LocalVariableTable 本地变量表--这里借助Spring提供的方法参数名解析器来操作asm解析局部变量表获取参数名
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(foo);
        System.out.println(Arrays.toString(parameterNames));


大部分编译器在编译时都会添加-g参数


spring对以上两种方式解析获取参数名都提供了支持:

DefaultParameterNameDiscoverer 也是采用了组合模式,内部组合了多种ParameterNameDiscoverer实现类:

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer 

	public DefaultParameterNameDiscoverer() 
		if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) 
			addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
		
		//默认添加了对两种参数获取方式的支持
		addDiscoverer(new StandardReflectionParameterNameDiscoverer());
		addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
	

DefaultParameterNameDiscoverer内部会依次尝试先根据反射从方法参数表获取参数名,失败了再尝试从局部变量表获取参数名。


类型转换体系

由于历史遗留原因,Spring目前的类型转换体系结构分为了两套架构,一套是基于JDK提供的PropertyEditor接口实现的,一套是Spring自己单独开发的Converters体系。

底层第一套转换接口与实现:

  • Printer 把其它类型转为 String
  • Parser 把 String 转为其它类型
  • Formatter 综合 Printer 与 Parser 功能
  • Converter 把类型 S 转为类型 T
  • Printer、Parser、Converter 经过适配转换成 GenericConverter 放入 Converters 集合
  • FormattingConversionService 内部维护一组Converters集合,用于对外其他类型转换服务

底层第二套转换接口:

  • PropertyEditor 把 String 与其它类型相互转换
  • PropertyEditorRegistry 可以注册多个 PropertyEditor 对象
  • 与第一套接口直接可以通过 FormatterPropertyEditorAdapter 来进行适配

高层接口与实现:

  • 它们都实现了 TypeConverter 这个高层转换接口,在转换时,会用到 TypeConverter Delegate 委派ConversionService 与 PropertyEditorRegistry 真正执行转换(Facade 门面模式)
    • 首先看是否有自定义的propertyEditor类型转换器, @InitBinder 添加的即属于这种 (用了适配器模式把 Formatter 转为需要的 PropertyEditor)
    • 再看有没有 ConversionService 转换
    • 再利用默认的 PropertyEditor 转换
    • 最后有一些特殊处理
  • SimpleTypeConverter 仅做类型转换
  • BeanWrapperImpl 为 bean 的属性赋值,当需要时做类型转换,走 Property – 调用对象的getter和setter方法完成赋值
  • DirectFieldAccessor 为 bean 的属性赋值,当需要时做类型转换,走 Field — 反射调用字段完成赋值
  • ServletRequestDataBinder 为 bean 的属性执行绑定,当需要时做类型转换,根据 directFieldAccess 选择走 Property 还是 Field,具备校验与获取校验结果功能 ,还有一点很关键,对象属性值来源于request对象

简单的使用演示

  • SimpleTypeConverter: 仅支持简单的类型转换
public class TestSimpleConverter 
    public static void main(String[] args) 
        // 仅有类型转换的功能
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        Integer number = typeConverter.convertIfNecessary("13", int.class);
        Date date = typeConverter.convertIfNecessary("1999/03/04", Date.class);
        System.out.println(number);
        System.out.println(date);
    


  • BeanWrapperImpl: 基于getter和setter方法完成属性赋值,如果不提供getter和setter方法,则会抛出异常
public class TestBeanWrapper 
    public static void main(String[] args) 
        // 利用反射原理, 为 bean 的属性赋值
        MyBean target = new MyBean();
        BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
        wrapper.setPropertyValue("a", "10");
        wrapper.setPropertyValue("b", "hello");
        wrapper.setPropertyValue("c", "1999/03/04");
        System.out.println(target);
    

    static class MyBean 
        private int a;
        private String b;
        private Date c;
        //自行提供getter和setter方法
        ...
    

  • DirectFieldAccessor: 基于反射获取字段设置值
public class TestFieldAccessor 
    public static void main(String[] args) 
        // 利用反射原理, 为 bean 的属性赋值
        MyBean target = new MyBean();
        DirectFieldAccessor accessor = new DirectFieldAccessor(target);
        accessor.setPropertyValue("a", "10");
        accessor.setPropertyValue("b", "hello"<

以上是关于Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC注解Controller源码流程解析---请求匹配中的容错处理

Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上

spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析

Spring MVC注解版本--初识--12

Spring MVC 基础注解之@RequestMapping@Controller

Spring MVC 常用注解@Controller,@RequestMapping,Model和ModelAndView