美文网首页
springmvc从入门到源码分析专题3_tomcat服务启动过

springmvc从入门到源码分析专题3_tomcat服务启动过

作者: wulin_challenge | 来源:发表于2020-12-28 17:21 被阅读0次

返回专题目录

springmvc从入门到源码分析专题3_tomcat服务启动过程中DispatcherServlet初始化过程

在上一篇 springmvc从入门到源码分析专题2_tomcat服务启动过程中如何加载ContextLoaderListener的 文章中我们分析了 ContextLoaderListener 是如何初始化的,讲解了springmvc是如何创建根ApplicationContext的过程,本篇文章我们来分析DispatcherServlet初始化过程

web.xml的配置

<!-- 
    SpringMVC的前端控制器,当DispatcherServlet载入后,它将从一个XML文件中
    载入Spring的应用上下文,该XML文件的名字取决于这里DispathcerServlet将试图从一个
   叫做Springmvc-servlet.xml的文件中载入应用上下文
 -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
</servlet>

DispatcherServlet的继承关系

首先我们先看下DispatcherServlet的继承关系,然后我们再来逐步分析。


image.png

重要接口Servlet详解

/**
* 该接口定义了初始化Servlet,处理请求以及从服务器中删除Servlet的方法。这些称为生命周期方法,按以下顺序调用:
* 1.构建servlet,然后使用init方法初始化。
* 2.处理客户对服务方法的任何调用。
* 3.停止使用Servlet,然后使用destroy方法将其销毁,然后对垃圾进行收集并最终确定。
* 
* 除了生命周期方法外,此接口还提供了Servlet可用于获取任何启动信息的getServletConfig方法,以及允许Servlet返回有关自身的基本信息(如作者,版本和版本)的getServletInfo方法。版权。
*/
public interface Servlet {
   /**
    * 在servlet的配置当中,<load-on-startup>1</load-on-startup>的含义是:
    * <p> 当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
    * <p> 当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
    * <p> 正数的值越小,启动该servlet的优先级越高。
    * 
    * <p> 如果我们在web.xml中设置了多个servlet的时候,可以使用load-on-startup来指定servlet的加载顺序,服务器会根据load-on-startup的大小依次对servlet进行初始化。不过即使我们将load-on-startup设置重复也不会出现异常,服务器会自己决定初始化顺序
    * 
    * <p> 配置load-on-startup后,servlet在startup后立即加载,但只是调用servlet的init()方法,用以初始化该servlet相关的资源。初始化成功后,该servlet可响应web请求;如未配置load-on-startup,容器一般在第一次响应web请求时,会先检测该servlet是否初始化,如未初始化,则调用servlet的init()先初始化,初始化成功后,再响应请求。
    * 
    *<p> 同时调用servlet的init()方法,它有一个参数ServletConfig,是容器传进来的,表示的是这个Servlet的一些配置,比如DispatcherServlet配置的<init-param>
    */
   public void init(ServletConfig config) throws ServletException;
   /**
    * 获取Servlet的配置
    */
   public ServletConfig getServletConfig();
   /**
    * 最重要的一个方法,是具体处理请求的地方
    */
   public void service(ServletRequest req, ServletResponse res)
           throws ServletException, IOException;
   /**
    * 获取Servlet的一些信息,比如作者、版本、版权等,需要子类实现
    */
   public String getServletInfo();
   /**
    * 用于Servlet销毁(主要指关闭容器)时释放一些资源,只会调用一次
    */
   public void destroy();
}

重要接口ServletConfig详解

/**
 * servlet容器使用的servlet配置对象,用于在初始化期间将信息传递给servlet。
 */
public interface ServletConfig {
    /**
     * 返回Servlet的名字,就是<servlet-name>中配置的名字
     */
    public String getServletName();
    /**
     * 返回应用本身的一些配置
     */
    public ServletContext getServletContext();
    /**
     * 返回<init-param>配置的参数
     */
    public String getInitParameter(String name);
    /**
     * 返回<init-param>配置的参数的名字
     */
    public Enumeration<String> getInitParameterNames();
}

Servlet接口的抽象实现类 GenericServlet 详解

/**
 *
 * GenericServlet实现Servlet和ServletConfig接口。 可以直接由servlet扩展GenericServlet,尽管扩展特定于协议的子类(例如HttpServlet)更为常见。
 * 
 * <p> GenericServlet使编写servlet更容易。 它提供了生命周期方法init和destroy以及ServletConfig接口中的方法的简单版本。 GenericServlet还实现了在ServletContext接口中声明的log方法。
 * 要编写通用servlet,您只需要重写抽象服务方法即可。
 */
public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable
{
    private transient ServletConfig config;
    
    
    /**
     * <p> 获取初始化参数,返回一个字符串,其中包含命名的初始化参数的值;如果该参数不存在,则返回null。
     * <p> 提供此方法是为了方便。 它从Servlet的ServletConfig对象获取命名参数的值。
     */ 
    public String getInitParameter(String name) {...}
    
    /**
     * 返回此Servlet的ServletConfig对象。
     */    
    public ServletConfig getServletConfig() {...}
 
    
    /**
     * 返回对该ServletContext的引用,它从Servlet的ServletConfig对象获取上下文。
     */
    public ServletContext getServletContext() {...}

    /**
     * 可以重写的一种便捷方法,因此无需调用super.init(config)。
     * 
     * <p> 不必重写init(ServletConfig),只需重写此方法,它将由GenericServlet.init(ServletConfig config)调用。 仍然可以通过getServletConfig检索ServletConfig对象。
     */
    public void init() throws ServletException {...}
    
    /**
     * 由Servlet容器调用,以允许Servlet响应请求。
     * 将此方法声明为抽象方法,因此子类(例如HttpServlet)必须覆盖它。
     */

    public abstract void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException;
}

DispatcherServlet的初始化流程

  • 从上图中我们可以看到 DispatcherServlet 间接地继承于 Servlet , 而springmvc的HttpServletBean复写了 Servlet#init() 方法,这个init()就是DispatcherServlet的初始化流程的入口,我们来看看HttpServletBean#init()方法
public final void init() throws ServletException {
    ...
    try {
        //从ServletConfig中获取初始配置,比如contextConfigLocation
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        
        // 模板方法,做一些初始化的工作,bw代表DispatcherServlet,但是没有子类重写
        initBeanWrapper(bw);
        // 把初始配置设置给DispatcherServlet,比如contextConfigLocation
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // 模板方法,子类重写,做进一步初始化的工作
    initServletBean();
    ...
}
  • FrameworkServlet#initServletBean()方法
/**
 * 设置任何bean属性后调用的HttpServletBean的重写方法。 创建此Servlet的WebApplicationContext。
 */
@Override
protected final void initServletBean() throws ServletException {
    ...
    //初始化并发布此Servlet的WebApplicationContext。
    this.webApplicationContext = initWebApplicationContext();
    //设置任何bean属性并加载WebApplicationContext后,将调用此方法。 默认实现为空; 子类可以重写此方法以执行其所需的任何初始化。
    initFrameworkServlet();
    ...
}
  • FrameworkServlet#initWebApplicationContext()
/**
 * 初始化并发布此Servlet的WebApplicationContext。
 * <p> 代表createWebApplicationContext实际创建上下文
 */
protected WebApplicationContext initWebApplicationContext() {
    //从ServletContext中获取父WebApplicationContext,在上一篇文章中我们在web.xml中配置了ContextLoaderListener,那么它加载的就是父WebApplicationContext
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // 如果通过构造方法传入了webApplicationContext,就使用它
    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);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 从ServletContext中获取webApplicationContext,一般情况下是没有的
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 自己创建一个webApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // 当ContextRefreshedEvent事件没有触发时调用此方法,模板方法,子类实现,是DispatcherServlet中重要的方法
        onRefresh(wac);
    }

    if (this.publishContext) {
        // 把webApplicationContext保存到ServletContext中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

正常情况下都是自己创建一个webApplicationContext,我们看下创建的过程

```
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    // 获取创建类型
    Class<?> contextClass = getContextClass();
    // 具体创建
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // 将设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/[ServletName]-Servlet.xml
    wac.setConfigLocation(getContextConfigLocation());
    // 配置和刷新wac
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    //添加ContextRefreshListener监听器,
    wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    // 根据contextConfigLocation的值刷新webApplicationContext
    wac.refresh();
}

/**
 * ContextRefreshListener接收的ContextRefreshedEvent完成刷新事件,委托给FrameworkServlet实例上的onApplicationEvent。
 */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

/**
 * 默认实现调用onRefresh,触发此Servlet的上下文相关状态的刷新。
 */
public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    onRefresh(event.getApplicationContext());
}
```
  • 我们来看卡DispatcherServlet#onRefresh()方法都干了些什么事情

    /**
     * 此实现调用initStrategies。
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    
    /**
     * 初始化springmvc执行过程中各种解析器
     */
    protected void initStrategies(ApplicationContext context) {
        //初始化文件上传解析器
        initMultipartResolver(context);
        //初始化国际化资源解析器
        initLocaleResolver(context);
        //初始化主题管理配置
        initThemeResolver(context);
        //初始化HandlerMapping
        initHandlerMappings(context);
        //初始化HandlerAdapter
        initHandlerAdapters(context);
        //初始化HandlerException,可以扩展用于统一异常处理
        initHandlerExceptionResolvers(context);
        //初始化处理请求名称作为视图名称返回
        initRequestToViewNameTranslator(context);
        //初始化视图解析器
        initViewResolvers(context);
        //初始化FlashMapManager,重定向时一般都是不传数据的,如果一定要传数据,只能在URL中拼接字符串来传递,但是通过拼接字符串有缺点,比如长度问题,安全问题,通过FlashManager异常发送
        initFlashMapManager(context);
    }
    

最后

到这里,关于DispatcherServlet初始化过程就分析完成了,我们可以看到,本篇文章很多细节知识点都没有展开来讲,是因为这些知识点如果深入讲解得用一篇博文甚至几篇博文才能讲解清楚,随着专题的深入,我都会一一讲解,下一篇我们将会讲解从浏览器发起一个请求后springmvc是如何处理这个请求的

返回专题目录

相关文章

网友评论

      本文标题:springmvc从入门到源码分析专题3_tomcat服务启动过

      本文链接:https://www.haomeiwen.com/subject/oqjtoktx.html