【Spring高级】SpringMVC中控制器方法HandlerMethod的工作流程

目录

流程图

先分3个部分来看下整体的流程图

图1

ServletInvocableHandlerMethod 是 Spring Framework 中对HandlerMethod的实现。

ServletInvocableHandlerMethod的整体架构图
ServletInvocableHandlerMethod +invokeAndHandle(ServletWebRequest,ModelAndViewContainer) HandlerMethod bean method WebDataBinderFactory ParameterNameDiscoverer HandlerMethodArgumentResolverComposite HandlerMethodReturnValueHandlerComposite

HandlerMethod 需要

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

ServletInvocableHandlerMethod 需要

  • WebDataBinderFactory: 负责对象绑定、类型转换
  • ParameterNameDiscoverer: 负责参数名解析
  • HandlerMethodArgumentResolverComposite: 负责解析参数
  • HandlerMethodReturnValueHandlerComposite :负责处理返回值

ServletInvocableHandlerMethod实现了HandlerMethod并扩展了以上功能。

图2

下图是RequestMappingHandlerAdapter工作的一些准备工作:

  • 初始化 advice @InitBinder,添加一些自定义类型转换器
  • 初始化advice @ModelAttribute,生产模型数据
  • 将第2步的模型数据添加到ModelAndViewContainer

RequestMappingHandlerAdapter WebDataBinderFactory ModelFactory ModelAndViewContainer 初始化(advice @InitBinder) 初始化(advice @ModelAttribute) 添加Model数据 RequestMappingHandlerAdapter WebDataBinderFactory ModelFactory ModelAndViewContainer

图3

RequestMappingHandlerAdapter的执行流程,主要做了三件事

  • 解析参数
  • 反射调用方法
  • 处理返回值

RequestMappingHandlerAdapter ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer invokeAndHandle 获取 args 有的解析器涉及 RequestBodyAdvice 有的解析器涉及数据绑定生成模型数据 args method.invoke(bean,args) 得到 returnValue 处理 returnValue 有的处理器涉及 ResponseBodyAdvice 添加Model数据,处理视图名,是否渲染等 获取 ModelAndView RequestMappingHandlerAdapter ServletInvocableHandlerMethod ArgumentResolvers ReturnValueHandlers ModelAndViewContainer

演示

首先创建WebConfig

java 复制代码
package com.cys.spring.chapter13;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;


@Configuration
public class WebConfig {

    @Controller
    static class Controller1 {
        // ResponseStatus是为了测试方便,可以先不加返回值处理器
        @ResponseStatus(HttpStatus.OK)
        public ModelAndView foo(User user) { // 省略了@ModelAttribute
            System.out.println("foo");
            return null;
        }
    }

    static class User {
        private String name;

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

        public String getName() {

            return name;
        }

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

测试类:

java 复制代码
package com.cys.spring.chapter13;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;

import java.util.Collections;

public class TestHandlerAdapter {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);

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

        // 创建HandlerMethod
        ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(
                new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo", WebConfig.User.class));

        // 创建DataBinderFactory,即类型转换器工厂
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        // 给handlerMethod设置需要的类型转换器、参数解析器等
        handlerMethod.setDataBinderFactory(factory);
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());  // 解析参数名
        handlerMethod.setHandlerMethodArgumentResolvers(getArgumentResolvers(context));  // 参数解析器

        // 创建ModelAndView容器,用于存储模型对象
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 反射调用方法
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container);

        System.out.println(container.getModel());

        context.close();

    }

    /**
     * 获取一个参数解析器组合器,解析常见的参数
     *
     * @param context
     * @return
     */
    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(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true),
                new RequestParamMethodArgumentResolver(context.getDefaultListableBeanFactory(), true)
        );
        return composite;
    }
}

运行结果如下:

复制代码
foo
{u=User{name='张三'}, org.springframework.validation.BindingResult.u=org.springframework.validation.BeanPropertyBindingResult: 0 errors}

可以看到foo方法成功被调用。并且ModelAndViewContainer含有一个mode,打印的{u=User{name='张三'},只有被@ModelAttribute或省略了此注解的参数才回呗放到ModelAndViewContainer中。

相关推荐
PC2005-cloud17 分钟前
DSH 白嫖指南:接入 Command Code Go、WorkBuddy 与 Trae 的免费额度
开发语言·后端·golang
敲代码的嘎仔17 分钟前
从集群锁失效到异步领券:我用分布式锁 + Redisson + MQ 把领券接口 RT 从 200ms 压到 15ms
java·数据库·redis·分布式·缓存·ai·wpf
步行cgn19 分钟前
Spring 的模块体系详解
java·数据库·spring
xcl092538 分钟前
智慧健身场馆系统开发实战:从架构设计到核心模块落地指南
java·spring boot
vx-程序开发40 分钟前
【计算机毕设】基于Spring Boot的古城景区管理系统88564
java·数据库·spring boot·后端·spring·elasticsearch·课程设计
鹿角片ljp42 分钟前
LeetCode 31:下一个排列|右找小,右找大,交换,右反转
java·数据结构·算法
边境悍匪1 小时前
蜗牛学苑 Java 智能体学习 Day40|Vue 工程化与 ElementPlus 思维导图复盘
java·vue.js·学习
妙码生花1 小时前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-上
前端·后端·gin
霸道流氓气质1 小时前
通义千问 VL & Audio:图像、视频、语音多模态 Java 集成深度解析
java·开发语言·音视频
lemon_sjdk1 小时前
WCPoint、WebPageClient和Accessor类源码
java·安全·webkit·javafx