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

news2024/11/14 1:08:53

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

  • 引言
  • RequestMappingHandlerAdapter
    • 方法参数解析器
    • 方法参数名解析器
    • 类型转换体系
      • 简单的使用演示
    • 数据绑定器工厂
      • 定制化修改DataBinder
    • 获取泛型参数
    • @ControllerAdvice与@InitBinder注解
    • 控制器方法执行流程
    • @ControllerAdvice 之 @ModelAttribute
    • 返回值处理器
  • 小结


引言

前面已经详细介绍过了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");
        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
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对象获取属性值的功能
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的接口,我们看下面这个场景:

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可以获取到绑定的结果,绑定结果中会记录下绑定过程中出现的错误:

        BindingResult bindingResult = dataBinder.getBindingResult();
        System.out.println(bindingResult);

在这里插入图片描述


定制化修改DataBinder

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

  1. 使用@InitBinder注解: RequestMappingHandlerAdapter的初始化方法被调用时,会搜集容器中所有标注了@ControllerAdvice的Bean,然后解析其中标注了@InitBinder注解的方法,然后缓存起来,此种方式属于全局有效的定制化方式
    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 方式扩展的"));
        }
    }

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

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实现,完成定制化修改操作
    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注解的方法后被调用,后者会覆盖前者配置
    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注解

    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);
    }

在这里插入图片描述


获取泛型参数

在这里插入图片描述

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注解的方法,并缓存起来:
在这里插入图片描述


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

   //注册的类型转换器,对所有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核心方法为:
在这里插入图片描述

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


实例演示:

  • 准备一个配置类
@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方法执行流程
    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 方法创建模型工厂

返回值处理器

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

        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

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

    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;
        ..
    }

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

    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对象去执行视图渲染逻辑:
在这里插入图片描述


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

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        // 1. 测试返回值类型为 ModelAndView
          test1(context);
    }
  1. 测试返回值类型为 ModelAndView
    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 时, 把它当做视图名
    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属性集合中的请求路径
    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 注解且返回非简单类型时, 此时需找到默认视图名
    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 时, 此时不走视图流程
    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 时, 此时不走视图流程 ,此时只返回响应头,不返回响应体
    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格式
    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内容过多,所以本文先讲到返回值处理部分,我们下一篇文章再见。

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

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

相关文章

C语言(可变参数:stdarg.h)

目录 一.使用可变参数 1.提供一个使用省略号的函数原型 2.在函数定义中创建一个va_list类型的变量 3.用宏把该变量初始化为一个参数列表 4.用宏访问参数列表 5.使用宏完成清理工作 二.va_copy 三.实例 一.使用可变参数 该头文件提供类似接受可变数量参数的宏&#xf…

【招聘】永善县正向社会工作服务中心招聘2名工作人员

永善县正向社会工作服务中心面向社会公开招聘2名工作人员 永善县正向社会工作服务中心是2019年9月在永善县民政局注册登记成立的民办非企业单位。业务范围主要为&#xff1a;面向社会提供各类专业社工服务&#xff0c;开展活动策划、个案援助等社会服务项目&#xff1b;承接个人…

基于WEB的小型公司人事管理系统设计

技术&#xff1a;Java、JSP等摘要&#xff1a;随着当代各类企业公司员工数量越来越多、分工越来越细化、各行各业之间的联系越发密切&#xff0c;对人事管理的要求也不断提高。实现企业人事管理计算机化&#xff0c;毫无疑问会让企业的人事管理变得更加高效化和智能化。企业要生…

c/c++开发,无可避免的函数参数实践

一、函数参数表 函数由函数名以及一组操作数类型唯一地表示。函数的操作数&#xff0c;也即形参&#xff0c;在一对圆括号中声明&#xff0c;形参与形参之间以逗号分隔。每一个函数都有一个相关联的返回类型。 int setval(int val) {//函数体 }; 这里&#xff0c;定义了一个名…

Vulkan教程(14): Graphics pipeline之Fixed functions(固定管线功能)

Vulkan官方英文原文&#xff1a;https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Fixed_functions对应的Vulkan技术规格说明书版本&#xff1a; Vulkan 1.3.2The older graphics APIs provided default state for most of the stages of the graphic…

个人如何获得免费的VMware Fusion Player?在macOS上虚拟化系统

不管是开发还是测试&#xff0c;有时候都需要虚拟机。比如虚拟化一台Linux&#xff0c;部署Web服务进行服务器仿真操作。亦或者&#xff0c;macOS上虚拟化Windows&#xff0c;进行Windows环境模拟。 VMware这家公司&#xff0c;大家应该都比较熟悉。旗下的VMware Workstation在…

LabVIEW NI网络设备在MAX中不显示或未识别

LabVIEW NI网络设备在MAX中不显示或未识别有一个NI设备通过网络连接到主机。发生以下情况之一&#xff1a;尝试在Measurement&#xff06;AutomationExplorer&#xff08;MAX&#xff09;中配置设备。设备未显示在“远程系统”下。NIMAX中未检测到CompactRIO&#xff08;cRIO&a…

2D图像处理:2D Shape_Base_Matching_缩放_旋转_ICP_显示ROI

文章目录 调试结果参考调试说明问题0:并行运行问题问题1:模板+Mask大小问题问题2:组合缩放和旋转问题3:可以直接将计算边缘的代码删除问题4:如何在原始图像上显示匹配到的ROI问题5:计算的原始旋转角度不需要判断,直接可以在ICP中使用问题6:绘制坐标轴问题7:绘制ROI调试…

图像优化篇

目录&#xff08;1&#xff09;矢量图&#xff08;2&#xff09;位图 2.1 分辨率2&#xff0c;图像格式格式选择建议&#xff1a;&#xff08;1&#xff09;矢量图 被定义为一个对象&#xff0c;包括颜色&#xff0c;大小&#xff0c;形状&#xff0c;以及屏幕位置等属性&…

Netty实现Http服务器案例

功能&#xff1a;Netty服务器在6668端口监听&#xff0c;浏览器发出请求"http://localhost:6668"服务器可以恢复消息给浏览器&#xff1a;“hello&#xff0c;我是服务器”&#xff0c;并对特定请求资源进行过滤目的&#xff1a;Netty可以做服务器端开发&#xff0c;…

家政服务小程序实战教程07-轮播图组件

小程序中首页一般显示轮播图的功能&#xff0c;点击轮播图会跳转到具体的一篇文章或者是产品&#xff0c;本篇我们就介绍一下轮播图功能的开发 01 设计数据源 我们轮播图组件需要两个字段&#xff0c;一个是展示的图片&#xff0c;一个是跳转页面传入的参数。打开数据源&…

JAVA集合专题5 ——ArrayDeque + BlockingQueue

目录ArrayDeque的特点BlockingQueue什么是BlockingQueue?什么叫阻塞队列?阻塞队列的应用场景是什么?BlockingQueue的阻塞方法是什么?BlockingQueue的四类方法codecode2ArrayDeque的特点 ArrayDeque是Deque接口子实现ArrayDeque数据结构可以表示为: 队列、双端队列、栈Arra…

【MFC】工具条(16)

创建工具条的基本步骤是&#xff1a; 1.创建工具条资源。 2.构建一个CToolBar对象。 3.调用CToolBar::Create函数创建工具条窗口。 4.调用CToolBar::LoadToolBar载入工具条资源。 使用工具条 打开资源视图&#xff0c;可视化创建或者修改工具条&#xff1a; 其中ID项一般与菜…

【计组】硬盘--《深入浅出计算机组成原理》(十二)

目录 一、机械硬盘 二、SSD硬盘 &#xff08;一&#xff09;SSD硬盘的读写原理 1、SLC、MLC、TLC 和 QLC 2、P/E 擦写问题 &#xff08;二&#xff09;SSD 读写的生命周期 &#xff08;三&#xff09;磨损均衡、TRIM 和写入放大效应 1、FTL 和磨损均衡 2、TRIM 指令的…

vueday01-脚手架安装详细

一、vue脚手架安装命令npm i -g vue/cli 或 yarn global add vue/cli安装上面的工具&#xff0c;安装后运行 vue --version &#xff0c;如果看到版本号&#xff0c;说明安装成功或 vue -V工具安装好之后&#xff0c;就可以安装带有webpack配置的vue项目了。创建项目之前&#…

用队列实现栈VS用栈实现队列

之前我们就讲过队列&#xff0c;栈的基础知识&#xff0c;笔者之前有过详细的介绍&#xff0c;感兴趣的可以根据笔者的个人主页进行查找&#xff1a;https://blog.csdn.net/weixin_64308540/?typelately225. 用队列实现栈请你仅使用两个队列实现一个后入先出&#xff08;LIFO&…

加入bing体验chatGPT大军中来吧,它来了!

1 第一步&#xff1a;加入候选名单 1、首先需要加入候选名单 https://www.microsoft.com/zh-cn/edge?formMA13FJ 2、下载最新的Edge浏览器、androd、iOS都有试用版本&#xff08;可以看到iOS加护当前已满&#xff09; 这里我下载的是dev版本&#xff0c;Canary版本由于是…

50. Pow(x, n)

50. Pow(x, n) 一、题目描述&#xff1a; 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2…

决策树分类算法(一)(信息熵,信息增益,基尼指数计算)

目录一、信息量二、信息熵三、信息增益四、基尼指数五、代码&#xff1a;信息熵&#xff0c;信息增益&#xff0c;基尼指数计算&#xff08;splitInfo.py&#xff09;例子&#xff1a; 一、信息量 : I(x)log⁡21p−log⁡2pI(x)\log_{2}{\frac{1}{p}}-\log_{2}{p}I(x)log2​p1…

傻白探索Chiplet,Modular Routing Design for Chiplet-based Systems(十一)

阅读了Modular Routing Design for Chiplet-based Systems这篇论文&#xff0c;是关于多chiplet通信的&#xff0c;个人感觉核心贡献在于实现了 deadlock-freedom in multi-chiplet system&#xff0c;而不仅仅是考虑单个intra-chiplet的局部NoC可以通信&#xff0c;具体的一些…