Spring MVC 启动流程分析

提出问题,思考问题,并提出自己的拙见。

1、启动根源web.xml的作用是什么?

   Spring MVC 启动流程先从web.xml 说起,这是 Tomcat 服务器启动时指定加载的配置文件。有兴趣的可以研究Tomcat 源码。

2、Servlet 是Java定义的一套处理网络请求的规范,那Servlet 从哪里来?到哪里去?

3、Tomcat 捕捉到Http 事件,按Servlet 协议,只能促发方法 Service(ServletRequest,ServletResponse),那这个又是怎样被Spring 解析找到我们的Controller ->Method 的?

4、Spring MVC 启动的2个步骤 , 2个WebApplicationContext 是父子关系。

 (1)启动Spring  XmlWebApplicationContext 容器,加载dao、service 等业务层配置文件,注册业务层所有的Bean, 并且实例化和生成代理对象,是 父级Web Application Context, 保存在 ServletContext, 通过ServletContext 传递给子WebApplicationContext .

(2)启动Spring MVC,加载Spring MVC 的配置文件,解析Controller层,ClassPathBeanDefinitionScanner扫描指定路径,加载Controller ,解析@Controller @RequestMapping 注解,保存 path 和对应的 method 方法之间的关系。

一、web.xml 配置文件解读

  1、 listener  ->ContextLoaderListener ->init() 初始化,  Spring 指定参数名【contextConfigLocation】需要加载解析的配置文件。解析注解,扫描指定路径,生成beanDefinition, 注册BeanDefinition, 注入bean definition, 实例化 bean definition.

 2、DispatchServlet 启动,加在 Spring-MVC.xml , 扫描解析指定的Controller 路径,同上面一样的注册Bean definition逻辑。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:/spring/spring-cache.xml
        classpath*:/spring/spring-dao.xml
        classpath*:/spring/spring-jms.xml
        classpath*:/spring/spring-security.xml
        classpath*:/spring/spring-service.xml
        classpath*:/spring/spring-task.xml
        classpath*:/spring/spring-session.xml
    </param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<session-config>
    <session-timeout>30</session-timeout>
</session-config>
<servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/spring/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <servlet-name>other</servlet-name>
    <servlet-class>com.xx.OtherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>other</servlet-name>
    <url-pattern>/other/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern> 指定“/*” 的路径 且不是 /other/* 的全部有DispatchServlet 处理。
</servlet-mapping>

二、ContextLoaderListener 启动方法是什么?干了什么事?

1、默认初始化的ApplicationContext 是 XmlWebApplicationContext (ContextLoader.properties配置的)

2、XmlWebApplicationContext 容器 和 DefaultListableBeanFactory 是一对好搭档,分工明确。

 Context 里面注入了beanFactory,管理所有bean的生命循环,解析、注册、获取等;

context 保存上下文允许环境,实现容器的LifeCycle,例如配置文件路径;容器的启动、关闭、刷新等;

contextinitlized(ServletContextEvent )初始化webApplicationContext(SerletContext sc)

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }

   Log logger = LogFactory.getLog(ContextLoader.class);
   servletContext.log("Initializing Spring root WebApplicationContext"); 开始启动Spring 容器
   if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
         this.context = createWebApplicationContext(servletContext); 
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         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 ->
               // determine parent for root web application context, if any.
               ApplicationContext parent = loadParentContext(servletContext);父级ApplicationContext 
               cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, servletContext);//初始化容器,下图分析
         }
      } // key->value 保存父级容器 Root 
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
         currentContext = this.context;
      }
      else if (ccl != null) {
         currentContextPerThread.put(ccl, this.context);
      }
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  /** 省略不重要的代码 **/
   wac.setServletContext(sc);
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
   if (configLocationParam != null) {
      wac.setConfigLocation(configLocationParam);
   }

   // 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(sc, null);
   }
   模板方法,refresh() Spring 容器构建的核心实现。
   customizeContext(sc, wac);
   wac.refresh(); 
}

3、XmlApplicationContext 类图

      在父类 AbstractApplicationContext中实现 refresh(),正常流程12个步骤;异常2个步骤; 在下篇文章具体解读每个步骤是干什么。

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();
      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);
      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

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

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory); 实例化所有的非延迟加载的bean

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

4、DispatchServlet 类图

 (1)servlet ->init() -> initServletBean() -> 在FrameWorkServlet.initServletBean() 

        初始化时,解析@Controller 和@RequestMapping注解,构建<Url, MapperHandler> 类似结构

 (2)实现Servlet 的接口,最终由 doDispatch(HttpServletRequest,HttpServletResponse) 方法处理。

         重写Servlet 接口实现,因为拥有巨大的Bean容器->BeanFactory。

 (3)怎样通过解析Request 找到对应的MapperHandler?

        url ->模式匹配->从 bean容器里面寻找MapperHandler

   

@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-mvc*.xml加载controller层定义
      this.webApplicationContext = initWebApplicationContext();
      initFrameworkServlet();
   }
   catch (ServletException | 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");
   }
}
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.
         mappedHandler = getHandler(processedRequest);传入Request,获取映射的handler,这就是Spring容器需要解决的问题。
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.适配器
         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(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", 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);
         }
      }
   }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值