Spring Web MVC 启动过程与组件初始化分析

Spring Web MVC 启动过程与组件初始化分析


写在前面

作者本科在读,见解尚浅,水平有限,缺少阅读源码经验,不当之处请批评指正

因为博客涉及的各个部分的内容都是及其深奥和庞大的,这里只做简单的介绍和分析,后续还会随着学习的深入进行更正和补充

SpringMVC 的设计理念

简单来说,就是将 Spring的IoC 容器与 Servlet 结合起来,从而在 IoC 容器中维护 Servlet 相关对象的生命周期,同时将 Spring 的上下文注入到 Servlet 的上下文中。依靠 Servlet 的事件和监听机制来操作和维护外部请求,以及组装和执行请求对应的响应

Web 应用部署初始化过程 (Web Application Deployement)

参考官方文档Java Servlet Specification,可知Web应用部署的相关步骤如下:

WebApplicationDeployment

当一个Web应用部署到容器内时(eg.tomcat),在Web应用开始响应执行用户请求前,以下步骤会被依次执行:

  • 部署描述文件中 (eg.tomcat 的 web.xml) 由<listener>元素标记的事件监听器会被创建和初始化
  • 对于所有事件监听器,如果实现了ServletContextListener接口,将会执行其实现的contextInitialized()方法
  • 部署描述文件中由<filter>元素标记的过滤器会被创建和初始化,并调用其init()方法
  • 部署描述文件中由<servlet>元素标记的servlet会根据<load-on-startup>的权值按顺序创建和初始化,并调用其init()方法

可以发现,在 Tomcat 下 web 应用的初始化流程为:

  • 初始化 listener
  • 初始化 filter
  • 初始化 servlet

Listener 初始化

TODO

Filter 初始化

TODO

DispatcherServlet 初始化

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Spring MVC框架是围绕DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染和文件上传等功能

DispatcherServlet 本质上是一个 Servlet,下层的子类不断对 HTTPServlet 父类的方法进行扩展

public class DispatcherServlet extends FrameworkServlet{...}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {...}
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {...}

HttpServlet 有两大核心方法:init() 和 service() 方法

HttpServletBean 重写了 init() 方法,service() 方法在 HttpServlet 中定义

public final void init() throws ServletException {
    PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
            this.initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        } catch (BeansException var4) {
            if (this.logger.isErrorEnabled()) {
                this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
            }
            throw var4;
        }
    }
    this.initServletBean();
}

protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
        }

        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }

        if (this.logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
            this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
        }

        if (this.logger.isInfoEnabled()) {
            this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }

    }

init() 方法使用 final 修饰,说明这个方法不能被子类继承,所以 HttpServletBean 中的 init() 方法就是 servlet 入口,结尾的 initServletBean() 方法在其子类 FrameworkServlet 中重写,可以看到在此方法中,对 Web 对应的 Spring Application Context 进行了初始化,通过 initWebApplicationContext() 和 initFrameworkServlet() 两个方法对 java bean 和 Spring Application Context 进行了整合

ServletConfigPropertyValues 是 HttpServletBean 的内部静态类,在构造过程会通过传入的 servletconfig 对象将 web.xml 的配置的参数设置到 ServletConfigPropertyValues 的内部

通过 BeanWrapper 来构建 DispatcherServlet,BeanWrapper 是 spring 提供的一个来操作 javabean 的属性的工具,使用它可以直接修改 javabean 的属性

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }

                this.configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }

    if (wac == null) {
        wac = this.findWebApplicationContext();
    }

    if (wac == null) {
        wac = this.createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        synchronized(this.onRefreshMonitor) {
            this.onRefresh(wac);
        }
    }

    if (this.publishContext) {
        String attrName = this.getServletContextAttributeName();
        this.getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

initWebApplicationContext() 表示初始化 WebApplicationContext 属性,WebApplicationContext 是继承ApplicationContext 接口的接口,该属性也是 ApplicationContext 容器的上下文

initWebApplicationContext() 方法首先创建父容器,并将父容器和子容器做关联,随后创建 WebApplicationContext 对象,并把根上下文设置为 WebApplicationContext 的父上下文

initFrameworkServlet() 未做任何处理,主要是由子类继承来做一些操作

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);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

WebApplicationContext 创建成功后,就会进行 onRefresh() 操作,此方法在子类 DispatchServlet 中实现(竟然最后又回到了 DispatchServlet ,有趣),最后进入到 initStrategies() 方法中,这个时候 SpringMVC 就开始去加载对应的一些模块中主要的组件,例如:

  • initMultipartResolver 用来处理文件上传

  • initLocaleResolver(context) 用来处理国际话语言相关的一些操作

  • initThemeResolver() 这个是用来处理一些有关动态更换样式的支持(主题)

  • initHandlerMappings() 处理我们常听到的有关 url 和 controller 的映射关系

  • initHandlerAdapters() 处理映射有关的适配相关

  • initHandlerExceptionResolvers(context) 处理有关异常

  • initRequestToViewNameTranslator(context) 处理请求到视图名称的一个转换

  • initViewResolvers() 处理视图

    初始化操作到这里基本已经结束

DispatcherServlet 处理请求

DispatcherServlet 本质就是一个 Servlet,所以处理的请求的方法就是 service() 方法,在其父类 FrameworkServlet 中实现了 service() 方法

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
        super.service(request, response);
    } else {
        this.processRequest(request, response);
    }

}

service() 方法中调用了 processRequest() 方法,进而调用 doService() 方法

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = this.buildLocaleContext(request);
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
    this.initContextHolders(request, localeContext, requestAttributes);
    try {
        this.doService(request, response);
    } catch (IOException | ServletException var16) {
        failureCause = var16;
        throw var16;
    } catch (Throwable var17) {
        failureCause = var17;
        throw new NestedServletException("Request processing failed", var17);
    } finally {
        this.resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        this.logResult(request, response, (Throwable)failureCause, asyncManager);
        this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
    }

}

doService() 方法在 FrameworkServlet 中是抽象方法,所以在子类(DispatcherServlet)中会对他重写。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();
            label95:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label95;
                    }
                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        if (this.flashMapManager != null) {
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            if (inputFlashMap != null) {
                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
            }
            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
        }
        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }

doService() 方法调用了 doDispatch() 方法,这里就是真正处理请求的方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
        }
    }
mappedHandler = this.getHandler(processedRequest);

此行代码打上断点,开始调试,项目启动后可以进入主页,在进行页面跳转时,触发断点,进入方法内发现有3个 HandlerMapping,这是因为 Spring MVC 提供了许多 HandlerMapping 的实现,默认使用的是BeanNameUrlHandlerMapping,可以根据 Bean 的 name 属性映射到 URL 中

handlerMappings断点调试

通过 HandlerMapping 来查找 Handler 并返回一个 HandlerExcutionChain(执行链,包括 handler 和interceptor )对象

HandlerExcutionChain断点调试

由此可以看出返回的 HandlerExcutionChain 中包含一个处理器和拦截器,返回 HandlerExcutionChain 给前端控制器之后,前端控制器会调用处理器适配器,进入 getHandlerAdpter() 方法发现有4个 handlerAdapter

handlerAdapters断点调试

跳出此方法,继续执行,可以发现 HandlerAdapter 赋值为 RequestMappingHandlerAdapter

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值