当前位置: 首页 > 编程笔记 >

详解springMVC容器加载源码分析

樊烨烨
2023-03-14
本文向大家介绍详解springMVC容器加载源码分析,包括了详解springMVC容器加载源码分析的使用技巧和注意事项,需要的朋友参考一下

springmvc是一个基于servlet容器的轻量灵活的mvc框架,在它整个请求过程中,为了能够灵活定制各种需求,所以提供了一系列的组件完成整个请求的映射,响应等等处理。这里我们来分析下springMVC的源码。

首先,spring提供了一个处理所有请求的servlet,这个servlet实现了servlet的接口,就是DispatcherServlet。把它配置在web.xml中,并且处理我们在整个mvc中需要处理的请求,一般如下配置:

<servlet>
    <servlet-name>spring-servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>spring-servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

DispatcherServlet也继承了FrameworkServlet抽象类,这个抽象类为整个servlet的处理提供了spring容器的支持,让原本servlet容器的DispatcherServlet拥有能够利用spring容器组件的能力。上面servlet的初始化参数contextConfigLocation就是DispatcherServlet获取spring容器的配置环境。FrameworkServlet继承自org.springframework.web.servlet.HttpServletBean。HttpServletBean实现了servlet容器初始化会调用的init函数。这个init函数会调用FrameworkServlet的初始化加载spring容器方法。方法源码:

@Override
  protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
     // 初始化spring-servlet容器
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
    }
    catch (ServletException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
    }
    catch (RuntimeException ex) {
      this.logger.error("Context initialization failed", ex);
      throw ex;
    }

    if (this.logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
          elapsedTime + " ms");
    }
  }

可以看到这里就触发了spring的对web支持的容器初始化,这里使用的容器为WebApplicationContext.接下来我们就来分析一下整个容器的初始化过程:

protected WebApplicationContext initWebApplicationContext() {
// 查看是否在servlet上下文有所有容器需要继承的根Web容器
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    // 如果容器已经加载
    if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
        if (!cwac.isActive()) {
          // The context has not yet been refreshed -> provide services such as
          // setting the parent context, setting the application context id, etc
          if (cwac.getParent() == null) {
            // The context instance was injected without an explicit parent -> set
            // the root application context (if any; may be null) as the parent
            cwac.setParent(rootContext);
          }
          configureAndRefreshWebApplicationContext(cwac);
        }
      }
    }
    if (wac == null) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      wac = findWebApplicationContext();
    }
    if (wac == null) {
      // 创建容器
      wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      onRefresh(wac);
    }

    if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
            "' as ServletContext attribute with name [" + attrName + "]");
      }
    }

    return wac;
  }

如果已经有容器被创建那就初始化。否则创建容器,创建逻辑:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
      this.logger.debug("Servlet with name '" + getServletName() +
          "' will try to create custom WebApplicationContext context of class '" +
          contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    // 判断是否是可配置的容器
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
          "Fatal initialization error in servlet with name '" + getServletName() +
          "': custom WebApplicationContext class [" + contextClass.getName() +
          "] is not of type ConfigurableWebApplicationContext");
    }
    // 如果找到目标容器,并且可配置,然后就开始获取配置文件并开始初始化
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());
    // 这里是获取配置文件和完成初始化web容器的入口
    configureAndRefreshWebApplicationContext(wac);

    return wac;
  }

这里完成了 web容器的相关准备工作,开始正式读取配置文件加载和初始化容器。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 给容器设置一个id
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      if (this.contextId != null) {
        wac.setId(this.contextId);
      }
      else {
        // Generate default id...
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
            ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    /* 这里在容器被激活后,
    并且容器还没完成初始化之前可以对容器的相关配置做一些修改,
    默认给了空实现,
    这里子类可以去重写,能够获得在容器初始化之前做  一些处理*/
    postProcessWebApplicationContext(wac);
    // 这里讲容器的初始化信息放到一个列表
    applyInitializers(wac);
    // 这里就开始web容器的初始化
    wac.refresh();
  }

容器的初始化在AbstractApplicationContext,无论是其他的容器,最终都会调用到refresh()函数,这个函数基本定义了整个容器初始化的整个脉络,这里不展开讲,本博客之后应该会详细分析这块的逻辑,这里大概的注释一下每一个函数完成的操作:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // 这里主要加载了容器当中一些从其他配置文件读取的变量
      prepareRefresh();

      // 获取容器本身
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 这里完成一些基础组件的依赖
      prepareBeanFactory(beanFactory);

      try {
        // 添加 容器初始化之前的前置处理
        postProcessBeanFactory(beanFactory);

        // 调用 前置处理器,其中包含invokeBeanDefinitionRegistryPostProcessors与invokeBeanFactoryPostProcessors两类前置处理器的调用
        invokeBeanFactoryPostProcessors(beanFactory);

        // 注册bean被创建之前的前置处理器
        registerBeanPostProcessors(beanFactory);

        // 初始化容器的编码源        
        initMessageSource();

        // 初始化一些事件监听器
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        onRefresh();

        // 注册容器监听器
        registerListeners();

        // 初始化所有非懒加载的beans
        finishBeanFactoryInitialization(beanFactory);

        // Last step: 事件通知关心容器加载的相关组件
        finishRefresh();
      }

      // 部分代码省略
      }
    }
  }

自此加载完毕核心容器,然后回到FramewordServlet的initWebApplicationContext函数,在调用createWebApplicationContext完成一系列上面的操作之后,需要mvc servlet组件,入口就是onRefresh(ApplocationContext context)方法。它会调用另一个方法initStrategies(ApplicationContext context)。该方法如下:

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    // 获取所有的RequestMappings
    initHandlerMappings(context);
    // 不同handler的适配器
    initHandlerAdapters(context);
    // 异常解析器
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
  }

这里我们重点讲解initHandlerMappings与initHandlerAdapters函数,因为这两个是处理servlet请求的入口。

在spring mvc中任何类都可以处理request请求,因为DispacherServlet也是实现了HttpServlet的接口,所以处理请求也是doService里。doService会将请求交给doDispatcher函数处理。然后doDispacher的源码:

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

        // 这里获取当前请求的处理handler
        mappedHandler = getHandler(processedRequest, false);
        if (mappedHandler == null || mappedHandler.getHandler() == null) {
          noHandlerFound(processedRequest, response);
          return;
        }

        /* 因为handler可以是任何类,
        但是我们的DispacherServlet需要一个统一的处理接口,这个接口就是HandlerAdapter,
        不同的HandlerMapping可以获取到各种各样的Handler,
        这个handler最后都必须得到HandlerAdapter的支持才能被DispacherServlet所调用。
        扩充一个新的HandlerMapping只需要添加一个新的HandlerAdatper即可,有点抽象工厂模式的味道*/
        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()) {
            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
          }
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }

        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }

        // Actually invoke the handler.
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        if (asyncManager.isConcurrentHandlingStarted()) {
          return;
        }

        applyDefaultViewName(request, mv);
        mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
        dispatchException = ex;
      }
      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
        if (mappedHandler != null) {
          mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        }
      }
      else {
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
          cleanupMultipart(processedRequest);
        }
      }
    }
  }

但是我们发现获取到的handler并不是Object而是一个HandlerExecutionChain,这个类可以进去查看发现是一堆拦截器和一个handler,主要就是给每一个请求一个前置处理的机会,这里值得一提的是一般来说拦截器和过滤器的区别就是拦截器可以终止后续执行流程,而过滤器一般不终止。过滤器一般是容器级别的,这个handler前置拦截器可以做到更细级别的控制,例如过滤器只定义了init和doFilter,但是这个handler拦截器定义了preHandle和postHandle还有afterCompletion函数,不难理解分别对应不同请求阶段的拦截粒度,更加灵活。

获取处理handler的getHandler代码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这里获取多个映射方式,一旦找到合适的处理请求的handler即返回
    for (HandlerMapping hm : this.handlerMappings) {
      if (logger.isTraceEnabled()) {
        logger.trace(
            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
      }
      // HandlerExecutionChain包含了
      HandlerExecutionChain handler = hm.getHandler(request);
      if (handler != null) {
        return handler;
      }
    }
    return null;
  }

HandlerAdapter处理后返回统一被封装成ModelAndView对象,这个就是包含试图和数据的对象,在经过适当的处理:

processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)

将页面与返回的数据返回给浏览器完成整个请求过程。以上就是springMVC大概的启动过程和请求的处理过程,之后本博客还会陆续分析核心的spring源码,博主一直认为精读著名框架源码是在短时间提升代码能力的捷径,因为这些轮子包含太多设计思想和细小的代码规范,这些读多了都会潜移默化的形成一种本能的东西。设计模式也不断贯穿其中。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍代码详解javascript模块加载器,包括了代码详解javascript模块加载器的使用技巧和注意事项,需要的朋友参考一下 定义 使用 以上就是本次分享的全部代码内容,大家可以测试下,如果还有任何不明白的地方可以在下方的留言区讨论,感谢大家对呐喊教程的支持。

  • 本文向大家介绍SpringMVC源码解读之HandlerMapping,包括了SpringMVC源码解读之HandlerMapping的使用技巧和注意事项,需要的朋友参考一下 概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根据请求url到控制器的映射(Handle

  • 本文向大家介绍详解springmvc拦截器拦截静态资源,包括了详解springmvc拦截器拦截静态资源的使用技巧和注意事项,需要的朋友参考一下 springmvc拦截器interceptors springmvc拦截器能够对请求的资源路径进行拦截,极大的简化了拦截器的书写。但是,千万千万要注意一点:静态资源的放行。 上代码: 问题来了,在请求jsp页面的时候,你的静态资源的访问仍然会被自定义拦截器

  • 本文向大家介绍java 源码分析Arrays.asList方法详解,包括了java 源码分析Arrays.asList方法详解的使用技巧和注意事项,需要的朋友参考一下 最近,抽空把java Arrays 工具类的asList 方法做了源码分析,在网上整理了相关资料,记录下来,希望也能帮助读者! Arrays工具类提供了一个方法asList, 使用该方法可以将一个变长参数或者数组转换成List 。

  • 本文向大家介绍java 中modCount 详解及源码分析,包括了java 中modCount 详解及源码分析的使用技巧和注意事项,需要的朋友参考一下 modCount到底是干什么的呢 在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,但为什么要记录modCount的修改次数呢? 大家发现一

  • 本文向大家介绍nginx源码分析线程池详解,包括了nginx源码分析线程池详解的使用技巧和注意事项,需要的朋友参考一下 nginx源码分析线程池详解 一、前言      nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响。但是经常会有人问道,nginx为什么不采用多线程模型(这个除了之前一篇文章讲到的情况,别的只有去问作