前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上

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

作者头像
大忽悠爱学习
发布2023-02-13 09:07:03
7290
发布2023-02-13 09:07:03
举报
文章被收录于专栏:c++与qt学习c++与qt学习

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常见的方法参数解析器有:

代码语言:javascript
复制
        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代码中使用的形式有:

代码语言:javascript
复制
    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
代码语言:javascript
复制
    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文件中生成对应的参数表,此时我们通过反射就可以直接拿到参数名
代码语言:javascript
复制
        // 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 注解来辅助获得参数名)
代码语言:javascript
复制
        // 2. 基于 LocalVariableTable 本地变量表--这里借助Spring提供的方法参数名解析器来操作asm解析局部变量表获取参数名
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(foo);
        System.out.println(Arrays.toString(parameterNames));
在这里插入图片描述
在这里插入图片描述

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


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

在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
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: 仅支持简单的类型转换
代码语言:javascript
复制
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方法,则会抛出异常
代码语言:javascript
复制
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: 基于反射获取字段设置值
代码语言:javascript
复制
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");
        accessor.setPropertyValue("c", "1999/03/04");
        System.out.println(target);
    }

    static class MyBean {
        private int a;
        private String b;
        private Date c;
    }
}
  • DataBinder: 底层依靠BeanWrapperImpl和DirectFieldAccessor完成属性绑定和类型转换工作 , 默认采用BeanWrapperImpl
代码语言:javascript
复制
public class TestDataBinder {

    public static void main(String[] args) {
        // 执行数据绑定
        MyBean target = new MyBean();
        DataBinder dataBinder = new DataBinder(target);
        //开启反射获取字段并设置值,默认采用getter或者setter方法
        dataBinder.initDirectFieldAccess();
        MutablePropertyValues pvs = new MutablePropertyValues();
        pvs.add("a", "10");
        pvs.add("b", "hello");
        pvs.add("c", "1999/03/04");
        dataBinder.bind(pvs);
        System.out.println(target);
    }

    static class MyBean {
        private int a;
        private String b;
        private Date c;

        @Override
        public String toString() {
            return "MyBean{" +
                   "a=" + a +
                   ", b='" + b + '\'' +
                   ", c=" + c +
                   '}';
        }
    }
}
  • ServletRequestDataBinder: 在dataBinder的基础上,增加了通过request对象获取属性值的功能
代码语言:javascript
复制
public class TestServletDataBinder {

    public static void main(String[] args) {
        // web 环境下数据绑定
        MyBean target = new MyBean();
        ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(target);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("a", "10");
        request.setParameter("b", "hello");
        request.setParameter("c", "1999/03/04");

        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }

    static class MyBean {
        private int a;
        private String b;
        private Date c;
        ...
    }
}

数据绑定器工厂

WebDataBinderFactory负责提供对Web环境下DataBinder的创建,即WebDataBinder ,Web环境下的DataBinder,用于绑定属性值的数据来源于Request请求对象中。

使用WebDataBinderFactory创建DataBinder的好处在于,我们可以利用工厂实现类提供的相关扩展回调来给用户提供定制化DataBinder的机会。

具体的扩展定制化DataBinder的方式有@InitBinder注解方式,WebBindingInitializer回调类方式。

为什么需要提供定制化DataBinder的接口,我们看下面这个场景:

代码语言:javascript
复制
public class TestServletDataBinderFactory {
    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");
       
        User target = new User();
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }

    public static class User {
        private Date birthday;
        private Address address;
        //省略getter,setter方法
        ... 
    }

    public static class Address {
        private String name;
        //省略getter,setter方法
        ...
    }
}

我们期望的DataBinder在数据绑定过程中可以识别1999|01|02格式的日期字符串,并将其转换为Date类型,但是默认底层的类型转换器是无法识别这种类型的日期格式,因此最终赋值失败。

在这里插入图片描述
在这里插入图片描述

通过dataBinder可以获取到绑定的结果,绑定结果中会记录下绑定过程中出现的错误:

代码语言:javascript
复制
        BindingResult bindingResult = dataBinder.getBindingResult();
        System.out.println(bindingResult);
在这里插入图片描述
在这里插入图片描述

定制化修改DataBinder

要解决上面场景中提供的问题,我们需要使用WebDataBinderFactory给我们提供的定制化DataBinder的两种方式,添加或修改底层某个日期类型转换器,让其支持我们这种格式的日期类型转换:

  1. 使用@InitBinder注解: RequestMappingHandlerAdapter的初始化方法被调用时,会搜集容器中所有标注了@ControllerAdvice的Bean,然后解析其中标注了@InitBinder注解的方法,然后缓存起来,此种方式属于全局有效的定制化方式
代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        User target = new User();
        // "2. 用 @InitBinder 转换"    PropertyEditorRegistry PropertyEditor
       //我们手动封装一个标注了@InitBinder注解的方法
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), null);
        //createBinder方法中,再创建完DataBinder示例后,会分别调用WebBindingInitializer和InvocableHandlerMethod集合
        //尝试对DataBinder进行定制化操作
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }
    
     static class MyController {
        @InitBinder
        public void aaa(WebDataBinder dataBinder) {
            // 扩展 dataBinder 的转换器
            dataBinder.addCustomFormatter(new MyDateFormatter("用 @InitBinder 方式扩展的"));
        }
    }
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
public class MyDateFormatter implements Formatter<Date> {
    private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
    private final String desc;

    public MyDateFormatter(String desc) {
        this.desc = desc;
    }

    @Override
    public String print(Date date, Locale locale) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.format(date);
    }

    @Override
    public Date parse(String text, Locale locale) throws ParseException {
        log.debug(">>>>>> 进入了: {}", desc);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
        return sdf.parse(text);
    }
}
在这里插入图片描述
在这里插入图片描述

  1. 通过提供一个DataBinderFactory内部的WebBindingInitializer实现,完成定制化修改操作
代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        User target = new User();
        // "3. 用 ConversionService 转换"    ConversionService Formatter
        FormattingConversionService service = new FormattingConversionService();
        service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(service);
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }
在这里插入图片描述
在这里插入图片描述

  1. 同时提供@InitBinder和WebBindingInitializer: WebBindingInitializer优先被调用,标注了@InitBinder注解的方法后被调用,后者会覆盖前者配置
代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        User target = new User();
        // "4. 同时加了 @InitBinder 和 ConversionService"
        InvocableHandlerMethod method = new InvocableHandlerMethod(new MyController(), MyController.class.getMethod("aaa", WebDataBinder.class));

        FormattingConversionService service = new FormattingConversionService();
        service.addFormatter(new MyDateFormatter("用 ConversionService 方式扩展转换功能"));
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(service);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(List.of(method), initializer);
        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }
在这里插入图片描述
在这里插入图片描述

4.通过@DateTimeFormat指定日期格式,使用spring提供的ApplicationConversionService,内置相关日期类型转换器来解析字段上的@DateTimeFormat注解

代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("birthday", "1999|01|02");
        request.setParameter("address.name", "西安");

        User target = new User();
        // "5. 使用默认 ConversionService 转换"
        ApplicationConversionService service = new ApplicationConversionService();
        ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
        initializer.setConversionService(service);

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, initializer);

        WebDataBinder dataBinder = factory.createBinder(new ServletWebRequest(request), target, "user");
        dataBinder.bind(new ServletRequestParameterPropertyValues(request));
        System.out.println(target);
    }
在这里插入图片描述
在这里插入图片描述

获取泛型参数

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
public class TestGenericType {
    public static void main(String[] args) {
        // 小技巧
        // 1. java api
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Type type = TeacherDao.class.getGenericSuperclass();
        System.out.println(type);

        if (type instanceof ParameterizedType parameterizedType) {
            System.out.println(parameterizedType.getActualTypeArguments()[0]);
        }

        // 2. spring api 1
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Class<?> t = GenericTypeResolver.resolveTypeArgument(TeacherDao.class, BaseDao.class);
        System.out.println(t);

        // 3. spring api 2
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(ResolvableType.forClass(TeacherDao.class).getSuperType().getGeneric().resolve());
    }
}
在这里插入图片描述
在这里插入图片描述

@ControllerAdvice与@InitBinder注解

InitBinderDataBinderFactory数据绑定器工厂中标注了@InitBinder的绑定器定制化方法从哪里获取?

在这里插入图片描述
在这里插入图片描述

@InitBinder 的来源有两个

  1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并缓存
  2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并缓存
在这里插入图片描述
在这里插入图片描述

我们可以通过RequestMappingHandlerAdapter设置WebBindingInitializer初始化绑定器,该初始化绑定器会应用到ServletRequestDataBinderFactory工厂中:

在这里插入图片描述
在这里插入图片描述

能应用到当前Controller上的BinderMethod方法搜集工作由RequestMappingHandlerAdapter的getDataBinderFactory负责完成:

在这里插入图片描述
在这里插入图片描述

RequestMappingHandlerAdapter会在初始化时,获取容器中所有标注了@ControllerAdvice的bean,然后解析获取其中标注了@InitBinder注解的方法,并缓存起来:

在这里插入图片描述
在这里插入图片描述

所以,如果我们想要在日常开发中注册一些自定义的类型转换器,可以这样做:

代码语言:javascript
复制
   //注册的类型转换器,对所有controller生效
    @ControllerAdvice
    static class MyControllerAdvice {
        @InitBinder
        public void binder3(WebDataBinder webDataBinder) {
            webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
        }
    }
  
  //注册的类型转换器,只对当前controller生效
    @Controller
    static class Controller1 {
        @InitBinder
        public void binder1(WebDataBinder webDataBinder) {
            webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));
        }

        public void foo() {

        }
    }

小结:

在这里插入图片描述
在这里插入图片描述
  • RequestMappingHandlerAdapter 在图中缩写为 HandlerAdapter
  • HandlerMethodArgumentResolverComposite 在图中缩写为 ArgumentResolvers
  • HandlerMethodReturnValueHandlerComposite 在图中缩写为 ReturnValueHandlers

收获💡:

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @InitBinder 方法
  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @InitBinder 方法
  3. 以上两种 @InitBinder 的解析结果都会缓存来避免重复解析
  4. 控制器方法调用时,会综合利用本类的 @InitBinder 方法和 @ControllerAdvice 中的 @InitBinder 方法创建绑定工厂

控制器方法执行流程

在这里插入图片描述
在这里插入图片描述

HandlerMethod 需要

  • bean 即是哪个 Controller
  • method 即是 Controller 中的哪个方法

ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory 负责对象绑定、类型转换
  • ParameterNameDiscoverer 负责参数名解析
  • HandlerMethodArgumentResolverComposite 负责解析参数
  • HandlerMethodReturnValueHandlerComposite 负责处理返回值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体源码会在后面的文章讲述源码流程时,带领大家过一遍。


@ControllerAdvice 之 @ModelAttribute

@ModelAttribute注解的作用是向ModelAndViewContainer中添加模型数据,@ModelAttribute可以加在以下位置:

在这里插入图片描述
在这里插入图片描述

对于标注在全局或者局部位置的@ModelAttribute注解来说,RequestMappingHandlerAdapter搜集这些方法,也是为了调用他们然后将他们的返回结果添加进行ModelAndViewContainer中:

在这里插入图片描述
在这里插入图片描述

模型工厂主要负责初始化ModelAndViewContainer,也就是搜集全局和局部的标注了@ModelAttribute注解的方法,然后执行这些方法,获取返回结果,添加进ModelAndViewContainer:

在这里插入图片描述
在这里插入图片描述

搜集标注了@ModelAttribute注解方法的过程,和@InitBinder注解一样,分别从全局和局部两个范围进行搜索:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

ModelFactory中的initModel核心方法为:

在这里插入图片描述
在这里插入图片描述

这里省略掉了很多细节问题,我们后面源码流程中会进行详细分析


实例演示:

  • 准备一个配置类
代码语言:javascript
复制
@Configuration
public class WebConfig {

    @ControllerAdvice
    static class MyControllerAdvice {
        @ModelAttribute("a")
        public String aa() {
            return "aa";
        }
    }

    @Controller
    static class Controller1 {
        @ModelAttribute("b")
        public String aa() {
            return "bb";
        }

        @ResponseStatus(HttpStatus.OK)
        public ModelAndView foo(@ModelAttribute("u") User user) {
            System.out.println("foo");
            return null;
        }
    }

    static class User {
        private String name;
        //省略getter,setter
        ...
    }
}
  • 测试类-- 模拟RequestMappingHandler的handler方法执行流程
代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);

        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setApplicationContext(context);
        adapter.afterPropertiesSet();

        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name", "张三");
        /*
            现在可以通过 ServletInvocableHandlerMethod 把这些整合在一起, 并完成控制器方法的调用, 如下
         */
        ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
                new Controller1(), Controller1.class.getMethod("foo", User.class));

        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        handlerMethod.setDataBinderFactory(factory);
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));

        ModelAndViewContainer container = new ModelAndViewContainer();

        // 获取模型工厂方法
        Method getModelFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
        getModelFactory.setAccessible(true);
        ModelFactory modelFactory = (ModelFactory) getModelFactory.invoke(adapter, handlerMethod, factory);

        // 初始化模型数据
        modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);

        //调用控制器方法
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);

        System.out.println(container.getModel());
        context.close();
    }

    public static HandlerMethodArgumentResolverComposite getArgumentResolvers(AnnotationConfigApplicationContext context) {
        HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
        composite.addResolvers(
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), false),
                new PathVariableMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletCookieValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ExpressionValueMethodArgumentResolver(context.getDefaultListableBeanFactory()),
                new ServletRequestMethodArgumentResolver(),
                new ServletModelAttributeMethodProcessor(false),
                new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
        );
        return composite;
    }

可以看到ModelAndViewContainer中的数据来源于三个地方: @C

在这里插入图片描述
在这里插入图片描述

关于@ModelAttribute部分的小结:

准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置:

在这里插入图片描述
在这里插入图片描述

收获💡

  1. RequestMappingHandlerAdapter 初始化时会解析 @ControllerAdvice 中的 @ModelAttribute 方法
  2. RequestMappingHandlerAdapter 会以类为单位,在该类首次使用时,解析此类的 @ModelAttribute 方法
  3. 以上两种 @ModelAttribute 的解析结果都会缓存来避免重复解析
  4. 控制器方法调用时,会综合利用本类的 @ModelAttribute 方法和 @ControllerAdvice 中的 @ModelAttribute 方法创建模型工厂

返回值处理器

常见的返回值解析处理器有以下几种:

代码语言:javascript
复制
        org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@4c9e38
        org.springframework.web.method.annotation.ModelMethodProcessor@5d1e09bc
        org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@4bdc8b5d
        org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@3bcd426c
        org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@5f14a673
        org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@726a17c4
        org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler@5dc3fcb7
        org.springframework.web.servlet.mvc.method.annotation.CallableMethodReturnValueHandler@c4c0b41
        org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler@76911385
        org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler@5467eea4
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@160396db
        org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@7a799159
        org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler@40ab8a8
        org.springframework.web.method.annotation.MapMethodProcessor@6ff37443
        org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@65cc8228

这里先给出不同返回值的处理依据:

  • ModelAndView,分别获取其模型和视图名,放入 ModelAndViewContainer
  • 返回值类型为 String 时,把它当做视图名,放入 ModelAndViewContainer
  • 返回值添加了 @ModelAttribute 注解时,将返回值作为模型,放入 ModelAndViewContainer
    • 此时需找到默认视图名
  • 返回值省略 @ModelAttribute 注解且返回非简单类型时,将返回值作为模型,放入 ModelAndViewContainer
    • 此时需找到默认视图名
  • 返回值类型为 ResponseEntity 时
    • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true
  • 返回值类型为 HttpHeaders 时
    • 会设置 ModelAndViewContainer.requestHandled 为 true
  • 返回值添加了 @ResponseBody 注解时
    • 此时走 MessageConverter,并设置 ModelAndViewContainer.requestHandled 为 true

我们来依次演示上面的各种情况,首先准备一个测试控制器:

代码语言:javascript
复制
    static class Controller {
        private static final Logger log = LoggerFactory.getLogger(Controller.class);

        public ModelAndView test1() {
            log.debug("test1()");
            ModelAndView mav = new ModelAndView("view1");
            mav.addObject("name", "张三");
            return mav;
        }

        public String test2() {
            log.debug("test2()");
            return "view2";
        }

        @ModelAttribute
//        @RequestMapping("/test3")
        public User test3() {
            log.debug("test3()");
            return new User("李四", 20);
        }

        public User test4() {
            log.debug("test4()");
            return new User("王五", 30);
        }

        public HttpEntity<User> test5() {
            log.debug("test5()");
            return new HttpEntity<>(new User("赵六", 40));
        }

        public HttpHeaders test6() {
            log.debug("test6()");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "text/html");
            return headers;
        }

        @ResponseBody
        public User test7() {
            log.debug("test7()");
            return new User("钱七", 50);
        }       
    }

    // 必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败
    public static class User {
        private String name;
        private int age;
        ..
    }

获取返回值解析器和视图渲染逻辑:

代码语言:javascript
复制
    public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }

    @SuppressWarnings("all")
    private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
                                   ServletWebRequest webRequest) throws Exception {
        log.debug(">>>>>> 渲染视图");
        FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
        String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
        log.debug("没有获取到视图名, 采用默认视图名: {}", viewName);
        // 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
        View view = resolver.resolveViewName(viewName, Locale.getDefault());
        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
        System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
    }

DispathcherServlet对于视图渲染也支持多种技术实现,例如: JSP,Freemarker等,每一种技术分别对应一个ViewResolver的实现类,每一次走到视图渲染逻辑时,DispathcherServlet都会遍历ViewResolver集合,传入视图名,判断哪一个ViewResolver能够解析成功当前视图名,如果可以,就交给他生成的View对象去执行视图渲染逻辑:

在这里插入图片描述
在这里插入图片描述

我们依次来看各种情况的执行结果:

代码语言:javascript
复制
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        // 1. 测试返回值类型为 ModelAndView
          test1(context);
    }
  1. 测试返回值类型为 ModelAndView
代码语言:javascript
复制
    private static void test1(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test1");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        //获取返回值解析器--这里使用的还是组合模式的体现
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
        //判断返回值解析器是否支持当前控制器方法返回值类型
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
          //返回值处理器处理返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
           //打印处理完后的ModelAndViewContainer中的视图名和模型数据 
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值类型为 String 时, 把它当做视图名
代码语言:javascript
复制
    private static void test2(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test2");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名,默认视图名为handlerMapping请求解析阶段缓存在request属性集合中的请求路径
代码语言:javascript
复制
    private static void test3(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test3");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        //设置请求路径
        request.setRequestURI("/test3");
        //UrlPathHelper解析请求路径并进行缓存
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值不加 @ModelAttribute 注解且返回非简单类型时, 此时需找到默认视图名
代码语言:javascript
复制
    private static void test4(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test4");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程
代码语言:javascript
复制
    private static void test5(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test5");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            //判断请求是否已经在返回值处理器中被处理掉了
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
            //如果被处理掉了,那么从response对象中获取数据
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程 ,此时只返回响应头,不返回响应体
代码语言:javascript
复制
    private static void test6(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test6");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                //打印响应头
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }
在这里插入图片描述
在这里插入图片描述

  1. 测试返回值添加了 @ResponseBody 注解时, 此时不走视图流程,相关返回值处理器处理后,会添加一个Content-type响应头表示响应内容格式为JSON,并将方法返回值输出为JSON格式
代码语言:javascript
复制
    private static void test7(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test7");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }
在这里插入图片描述
在这里插入图片描述

小结

由于RequestMappingHandlerAdapter内容过多,所以本文先讲到返回值处理部分,我们下一篇文章再见。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
  • 引言
  • RequestMappingHandlerAdapter
    • 方法参数解析器
      • 方法参数名解析器
        • 类型转换体系
          • 简单的使用演示
        • 数据绑定器工厂
          • 定制化修改DataBinder
        • 获取泛型参数
          • @ControllerAdvice与@InitBinder注解
            • 控制器方法执行流程
              • @ControllerAdvice 之 @ModelAttribute
                • 返回值处理器
                • 小结
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档