zoukankan      html  css  js  c++  java
  • Spring MVC的工作原理,我们来看看其源码实现

    前言

      开心一刻

        晚上陪老丈人吃饭,突然手机响了,我手贱按了免提……哥们:快出来喝酒!哥几个都在呢!我:今天不行,我现在陪老丈人吃饭呢。哥们:那你抓紧喝,我三杯白酒,把我岳父放倒了才出来的,你也快点。看着我老丈人的脸,我不知道该怎么回了……

    猪一样的队友

    遗留问题

      在关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题中,我遗留了一个问题:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个作用域,这里的作用域指的是Servlet的四大作用域;不了解问题背景的可以回过头去看看我的上篇博文。

      明确的解答我会放到最后,在解答问题之前,我先和大家一起来捋一捋Spring mvc的工作原理。废话不多说,开始我们神秘的探险之旅!

    应用示例

      在讲工作原理之前,我们先看一个简单的spring mvc(ssm)示例,以及实现的效果

      工程代码地址:ssm-web 

      工程结构与效果如上所示,我们不做过多的探究,我们打起精神往下看本篇的重点

    工作原理

      准备 - 资源的加载与初始化

        1、DispatcherServlet 静态初始化

          DispatcherServlet中有如下静态块

    static {
            // Load default strategy implementations from properties file.
            // This is currently strictly internal and not meant to be customized
            // by application developers.
            try {
                ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
                defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
            }
            catch (IOException ex) {
                throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
            }
        }

          这里会将DispatcherServlet.properties中的内容读取到DispatcherServlet的属性:private static final Properties defaultStrategies中,DispatcherServlet.properties内容如下

    # Default implementation classes for DispatcherServlet's strategy interfaces.
    # Used as fallback when no matching beans are found in the DispatcherServlet context.
    # Not meant to be customized by application developers.
    
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
        org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
        org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    View Code

          指定了DispatcherServlet策略接口的默认实现,后续DispatcherServlet初始化策略的时候会用到

        2、interceptor定义的加载

          spring启动过程中会调用InterceptorsBeanDefinitionParser的parse方法来解析出我们自定义的interceptor定义,封装成MappedInterceptor类型的bean定义,并放到spring容器中;我们可以简单的认为spring容器中已经存在了我们自定义的interceptor的bean定义

        3、DispatcherServlet初始化策略:initStrategies

          DispatcherServlet的继承图如下

          DispatcherServlet是一个Servlet,tomcat启动过程中会调用其init方法,一串的调用后,会调用DispatcherServlet的initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

          实例化步骤1中的默认实现,并填充到DispatcherServlet各个属性值中

        4、DefaultAnnotationHandlerMapping的拦截器初始化

          DispatcherServlet.properties种指定了两个默认的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,这两者的类继承图如下(我们暂时只关注DefaultAnnotationHandlerMapping)

          DefaultAnnotationHandlerMapping间接实现了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping实例初始化过程中,会调用setApplicationContext(ApplicationContext applicationContext)方法,一串调用后,会来到AbstractUrlHandlerMapping的initApplicationContext()

    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        detectMappedInterceptors(this.mappedInterceptors);
        initInterceptors();
    }

          初始化了DefaultAnnotationHandlerMapping的拦截器:interceptor

        我们来看下具体的初始化过程,看看上面的顺序是否只是我个人的臆想?

        可以看到,初始化顺序就是我们上面说的,不是我个人的意淫;此时的DefaultAnnotationHandlerMapping中有我们自定义的MyInterceptor。初始化过程我们需要关注的就是上述这些,下面我们一起看看具体请求的过程

      请求的处理

        请求从servlet的service开始,一路到DispatcherServlet的doDispatch,如下图

        doDispatch

    /**
     * Process the actual dispatching to the handler. 将请求分发到具体的handler,也就是我们的controller
     * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
     * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
     * to find the first that supports the handler class.
     * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
     * themselves to decide which methods are acceptable.
     * @param request current HTTP request
     * @param response current HTTP response
     * @throws Exception in case of any kind of processing failure
     */
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
    
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
    
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
    
                // Determine handler for the current request. 决定哪个handler来处理当前的请求
                // mappedHandler是由handler和interceptor集合组成的一个执行链,有点类似FilterChain
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
    
                // Determine handler adapter for the current request. 决定哪个adapter来处理当前的请求
                // handlerMapping是找出适配的handler,而真正回调handler的是adapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
    
                // handler的前置处理,也就是调用适配当前url的interceptor的preHandler方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
    
                try {
                    // Actually invoke the handler. 真正调用handler
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }
    
                applyDefaultViewName(request, mv);
                // handler的后置处理,也就是调用适配当前url的interceptor的postHandler方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            // 处理handler返回的结果,会调用适配当前url的interceptor的afterCompletion方法
            // 这里会将响应结果返回给请求者
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
    View Code

          handlerMapping具体如何找到匹配当前url的handler(一般而言就是我们的controller)、handlerAdapter具体如何回调真正的handler,有兴趣的可以自行去跟下,我就不跟了。我们具体看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 这个与我们最初的疑问有关

        processDispatchResult

          可以看到model中的persons会被设置到request的attributes中,然后转发请求到show_person.jsp,转发过程中request作用域的变量仍然有效,所以show_person.jsp中的jstl标签和el表达式能够取到persons变量,最后将show_person.jsp中的内容填充好之后的静态内容返回给请求者;至此就完成了一次请求的响应

    问题解答

      回到我们开篇的疑问:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个作用域?想必大家已经知道答案了

      Controller中的model、ModelMap的注入由spring mvc完成,这个不是请求传入的参数,用于绑定变量到Servlet作用域;默认情况下,在DispatcherServlet调用了真正的handler之后,将结果返回给请求者的过程中,将model、modelMap中的变量设置到了request的attributes中,转发的过程中,request中的变量仍然有效,所以show_person.jsp中能取到persons这个变量,自此疑问得到解答

    总结

      1、Spring MVC工作原理图

        图是用的别人的,具体是谁的我也不记得了(捂脸)

      2、DefaultAnnotationHandlerMapping在spring3.2中被废弃,替换成了RequestMappingHandlerMapping

  • 相关阅读:
    vue element 关于表单数组循环、对象数据校验
    vue 解耦双向数据绑定,new Set() 去重数组/对象
    Vue-cli3.0搭建和一些坑
    微信小程序入门学习小结--全局设置、数据请求、双向绑定、页面路由跳转、模板抽取
    Vuex简单入门实例
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    浏览器知识小整理
    HTML DOM textContent 与 innerHTML的区别
    T-SQL查询进阶--SQL Server中的事务与锁
  • 原文地址:https://www.cnblogs.com/youzhibing/p/10695012.html
Copyright © 2011-2022 走看看