SpringMVC 底层实现及执行流程详解

SpringMVC 底层实现

1 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter

1.1 DispatcherServlet初始化时机

  • DispatcherServlet 初始化默认是在第一次调用 DispatcherServlet 时初始化,而不是 tomcat 启动时初始化
  • 要想在 tomcat 启动时初始化,可以在 setLoadOnStartup() 传入大于0的整数,值的大小代表如有多个 servlet 初始化的优先级,值越小优先级越高
  • 以下使用配置文件方式实现,可以解耦

编写实现:

@Configuration
@ComponentScan
//普通的spring项目,要读取配置文件,需要配置,可以使用 key value 的形式单个读取,也可以批量读取
@PropertySource("classpath:application.properties")
//如果想批量读取 WebMvcProperties 观察源码可知可以读取以 "spring.mvc" 前缀开头的配置
//ServerProperties 可以读取以 "server" 前缀开头的配置
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {

    //1.内嵌 web 容器
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ServerProperties serverProperties) {
        //获取配置文件的容器端口号
        Integer port = serverProperties.getPort();
        return new TomcatServletWebServerFactory(port);
    }

    //2.创建DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    //3.注册DispatcherServlet 到 web 容器
    //DispatcherServlet 初始化默认是在第一次调用 DispatcherServlet 时初始化,而不是 tomcat 启动时初始化
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        // 传入dispatcherServlet 和 匹配路径
        DispatcherServletRegistrationBean registrationBean =
                new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        //1.1 要想在 tomcat 启动时初始化,可以传入大于0的整数,值的大小代表如有多个 servlet 初始化的优先级,值越小优先级越高
        //registrationBean.setLoadOnStartup(1);
        //1.2 初始化时机也可以配置在配置文件中
        int loadOnStartup = webMvcProperties.getServlet().getLoadOnStartup();
        registrationBean.setLoadOnStartup(loadOnStartup);
        return registrationBean;
    }
}
public class A20 {
    private static final Logger log = LoggerFactory.getLogger(A20.class);

    public static void main(String[] args) {

        //创建Spring容器,并且支持内嵌web容器 如tomcat
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}
server.port=9090
logging.level.com.wang=debug
# 初始化时机
spring.mvc.servlet.load-on-startup=1

1.2 DispatcherServlet 初始化执行的逻辑

观察源码可知 DispatcherServlet 的初始化主要是其中的 onRefresh() 方法

DispatcherServlet 部分源码如下

    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context); //文件上传解析器
        this.initLocaleResolver(context); //本地化解析器,会解析不同国家地区的语言
        this.initThemeResolver(context);
        this.initHandlerMappings(context);//路径映射,映射到不同控制器Controller
        this.initHandlerAdapters(context);//适配不同形式的控制器方法
        this.initHandlerExceptionResolvers(context);//异常解析
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

1.3 RequestMappingHandlerMapping 基本用途

测试用的Controller:

@Controller
public class Controller1 {

    private static final Logger log = LoggerFactory.getLogger(Controller1.class);

    @GetMapping("/test1")
    public ModelAndView test1() {
        log.debug("test1");
        return null;
    }

    @PostMapping("/test2")
    public ModelAndView test2(@RequestParam("name") String name) {
        log.debug("test2({})",name);
        return null;
    }
}

加入 RequestMappingHandlerMapping 的Bean:

    //如果用 DispatcherServlet 初始化添加的组件,并不会作为Bean,给测试带来困扰
    //1.加入RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }
public class A20 {
    private static final Logger log = LoggerFactory.getLogger(A20.class);

    public static void main(String[] args) throws Exception {

        //创建Spring容器,并且支持内嵌web容器 如tomcat
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        //作用 :解析 @Request 注解以及派生注解,生成路径与控制器方法的映射关系。在初始化时
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        //获取映射信息,key:路径信息 ,value:映射的方法
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println(k + " = " + v);
        });

        //请求来了,怎么获取控制器的方法,返回处理器执行链对象
        HandlerExecutionChain chain = handlerMapping
            .getHandler(new MockHttpServletRequest("GET", "/test1"));
        System.out.println(chain);
    }
}

打印输出:

{GET [/test1]} = com.wang.a20.Controller1#test1()
{POST [/test2]} = com.wang.a20.Controller1#test2(String)
14:25:41.892 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.wang.a20.Controller1#test1()
HandlerExecutionChain with [com.wang.a20.Controller1#test1()] and 0 interceptors

1.4 RequestMappingHandlerAdapter

RequestMappingHandlerAdapter 源码中有一个重要的方法,部分源码截图:

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);

        Object result;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }

            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

由于该方法是 protected 修饰的,我们可以编写一个 MyRequestMappingHandlerAdapter 类去继承 RequestMappingHandlerAdapter ,并重写 invokeHandlerMethod() 方法

public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                            HttpServletResponse response,
                                            HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}

测试用的Controller:

@Controller
public class Controller1 {

    private static final Logger log = LoggerFactory.getLogger(Controller1.class);

    @GetMapping("/test1")
    public ModelAndView test1() {
        log.debug("test1");
        return null;
    }

    @PostMapping("/test2")
    public ModelAndView test2(@RequestParam("name") String name) {
        log.debug("test2({})",name);
        return null;
    }
}

1 RequestMappingHandlerAdapter 调用并执行对应的Controller

//请求来了,怎么获取控制器的方法,返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

System.out.println("-----------------------------");
//获取 RequestMappingHandlerAdapter,去执行对应的Controller
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request,response, (HandlerMethod) chain.getHandler());

输出打印:

-----------------------------
14:42:10.952 [main] DEBUG com.wang.a20.Controller1 - test1

2 RequestMappingHandlerAdapter 参数和返回值解析器

  • 当遇到如下情况需要传参的情况,如何解析对应的参数注解:
//请求来了,怎么获取控制器的方法,返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name","wang");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

System.out.println("-----------------------------");
//获取 RequestMappingHandlerAdapter,去执行对应的Controller
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request,response, (HandlerMethod) chain.getHandler());

输出打印:

-----------------------------
14:44:44.932 [main] DEBUG com.wang.a20.Controller1 - test2(wang)

RequestMappingHandlerAdapter 的参数解析器:

System.out.println(">>>>>>>>>>>>>>>>>所有的参数解析器:");
List<HandlerMethodArgumentResolver> argumentResolvers = handlerAdapter.getArgumentResolvers();
for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) {
    System.out.println(argumentResolver);
}

输出部分打印:

>>>>>>>>>>>>>>>>>所有的参数解析器:
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver@209775a9
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver@18e7143f
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver@f9b7332
org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver@74cec793
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMethodArgumentResolver@6fefce9e
org.springframework.web.servlet.mvc.method.annotation.MatrixVariableMapMethodArgumentResolver@4f8969b0
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor@1bdf8190
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor@192f2f27
org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver@8a589a2
..........

如:RequestParamMethodArgumentResolver就是解析 @RequestParam 的

  • 返回值解析器:
 System.out.println(">>>>>>>>>>>>>>>>>所有的返回值解析器:");
 List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
 for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
 System.out.println(returnValueHandler);
 }

输出部分打印:

>>>>>>>>>>>>>>>>>所有的返回值解析器:
org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler@6bab2585
org.springframework.web.method.annotation.ModelMethodProcessor@74bdc168
org.springframework.web.servlet.mvc.method.annotation.ViewMethodReturnValueHandler@644c78d4
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler@532a02d9
org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler@611f8234
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor@7bb3a9fe
...........

如:ModelAndViewMethodReturnValueHandler 就是返回 ModelAndView 的解析器

1.5 自定义参数解析器

  1. 自定义注解

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Token {
    }
    
  2. 编写参数解析器

    public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    
        //是否支持某个参数
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            Token token = parameter.getParameterAnnotation(Token.class);
            return token != null;
        }
    
        //解析参数
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            String token = webRequest.getHeader("token");
            return token;
        }
    }
    
  3. 将自定义参数解析器设置到 RequestMappingHandlerAdapter 中

    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
        List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>();
        argumentResolverList.add(tokenArgumentResolver);
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        //设置自定义参数解析器
        handlerAdapter.setCustomArgumentResolvers(argumentResolverList);
        return handlerAdapter;
    }
    
  4. 编写测试 Controller

    @PutMapping("/test3")
    public ModelAndView test3(@Token String token) {
        log.debug("test3({})", token);
        return null;
    }
    
  5. 测试:

    //请求来了,怎么获取控制器的方法,返回处理器执行链对象
    MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
    request.setParameter("name", "wang");
    request.addHeader("token", "令牌信息");
    MockHttpServletResponse response = new MockHttpServletResponse();
    HandlerExecutionChain chain = handlerMapping.getHandler(request);
    System.out.println(chain);
    
    System.out.println("-----------------------------");
    //获取 RequestMappingHandlerAdapter,去执行对应的Controller
    MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    
  6. 输出打印:

    -----------------------------
    15:21:01.410 [main] DEBUG com.wang.a20.Controller1 - test3(令牌信息)
    

1.6 自定义返回值解析器

案例:输出 yaml 格式的字符串

  1. 自定义注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Yml {
    }
    
  2. 自定义返回值解析器

    public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
        @Override
        public boolean supportsReturnType(MethodParameter returnType) {
            Yml yml = returnType.getMethodAnnotation(Yml.class);
            return yml != null;
        }
    
        @Override
        public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                      ModelAndViewContainer mavContainer, 
                                      NativeWebRequest webRequest) throws Exception {
            //1.转化结果为 yaml 样式的字符串
            String str = new Yaml().dump(returnValue);
            //2.将 yaml 字符串写入响应
            HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
            response.setContentType("text/plain;charset=utf-8");
            response.getWriter().println(str);
            //3.设置请求已经处理完毕,不需要springmvc做视图解析
            mavContainer.setRequestHandled(true);
        }
    }
    
  3. 将自定义返回值解析器设置到 RequestMappingHandlerAdapter 中

    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
        List<HandlerMethodArgumentResolver> argumentResolverList = new ArrayList<>();
        argumentResolverList.add(tokenArgumentResolver);
    
        YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
        List<HandlerMethodReturnValueHandler> returnValueHandlerList = new ArrayList<>();
        returnValueHandlerList.add(ymlReturnValueHandler);
    
        MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
        //设置自定义参数解析器
        handlerAdapter.setCustomArgumentResolvers(argumentResolverList);
        //设置自定义返回值
        handlerAdapter.setCustomReturnValueHandlers(returnValueHandlerList);
        return handlerAdapter;
    }
    
  4. 编写测试 Controller

    @RequestMapping("test4")
    @Yml
    public User test4() {
        log.debug("test4");
        return new User("wang", 23);
    }
    
  5. 测试自定义返回值解析器

    /请求来了,怎么获取控制器的方法,返回处理器执行链对象
    MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test4");
    MockHttpServletResponse response = new MockHttpServletResponse();
    HandlerExecutionChain chain = handlerMapping.getHandler(request);
    System.out.println(chain);
    
    System.out.println("-----------------------------");
    //获取 RequestMappingHandlerAdapter,去执行对应的Controller
    MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
    handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());
    //检查响应对象
    byte[] content = response.getContentAsByteArray();
    System.out.println(new String(content, StandardCharsets.UTF_8));
    
  6. 输出打印

    -----------------------------
    17:27:48.116 [main] DEBUG com.wang.a20.Controller1 - test4
    !!com.wang.a20.Controller1$User {age: 23, name: wang}
    

2 mvc处理流程

当浏览器发送请求后,请求到达服务器:

  1. 服务器提供了DispatcherServlet,它使用的是标准的 Servlet 技术
    • 路径:默认路径为 /,即会匹配到所有的 URL ,可以作为请求的统一入口,也被称为:前控制器;例外的是:jsp不会匹配到DispatcherServlet
    • 创建:在 Boot 中,有DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 Bean
    • 初始化:DispatcherServlet 初始化时会优先到容器中去寻找各个组件,作为他的成员变量
      • HandlerMapping,初始化时记录映射关系
      • HandlerAdaptor,初始化时准备参数解析器、返回值处理器、消息转换器
      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
      • ViewResolver
  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
    • 例如根据/hello路径找到@RequestMapping(“/hello”) 对应的控制器方法
    • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给DispatcherServlet
    • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
  3. DispatcherServlet 接下来会:
    1. 调用拦截器的 preHandle 方法
    2. RequestMappingHandlerAdaptor 调用 handler 方法,准备数据绑定工厂、模型工厂、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
      • @ControllerAdvice增强1:补充模型数据
      • @ControllerAdvice增强2:补充自定义类型转换器
      • 使用 HandlerMethodArgumentResolver 准备参数
      • @ControllerAdvice增强3:RequestBody 增强
      • 调用 ServletInvocableHandlerMethod
      • 使用HandlerMethodReturnValueHandler 处理返回值
        • 如果返回的 ModelAndView 为null,不走第四步视图解析及渲染流程,例如:有@ResponseBody 的控制器方法,调用HttpMessageConvert 将结果转换为 JSON ,这是返回的 ModelAndView 就为 null
        • 如果返回的ModelAndView 不为null,会在第四步走视图解析及渲染流程
        • @ControllerAdvice 增强4:ResponseBody 增强
    3. 调用 PostHandler 方法
    4. 处理异常或视图渲染
      • 如果 1~3 步出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice增强5:@ExceptionHandler 异常处理
      • 没有异常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值