Springmvc from entry to source code analysis topic 3_tomcat service startup process of DispatcherServlet initialization process

Return to the topic list

Springmvc from entry to source code analysis topic 3_tomcat service startup process of DispatcherServlet initialization process

In the last article on springmvc from entry to source code analysis topic 2_tomcat service startup process how to load ContextLoaderListener article, we analyzed how ContextLoaderListener is initialized, and explained how springmvc creates the root ApplicationContext process. In this article, we will analyze the initialization of DispatcherServlet process

Web.xml configuration

<!-- 
	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>

Inheritance of DispatcherServlet

First, let's look at the inheritance relationship of DispatcherServlet, and then we will analyze it step by step.
[External link image transfer failed, the source site may have anti-leeching mechanism, it is recommended to save the image and upload it directly
Insert picture description here

Detailed explanation of important interface 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();
}

Detailed explanation of important interface 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 interface abstract realization class GenericServlet detailed

/**
 *
 * 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;
}

Initialization process of DispatcherServlet

  • We can see from the image above DispatcherServletindirectly inherited Servlet, and springmvc the HttpServletBeanreplication of the Servlet#init()method, this init()is the entrance to initiate the process of DispatcherServlet, we take a look at HttpServletBean#init()methods
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()method
/**
 * 设置任何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;
}

Under normal circumstances, create a webApplicationContext by yourself, let’s look at the creation process

```
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());
}
```
  • Let's see what the card DispatcherServlet#onRefresh()method has done

    /**
     * 此实现调用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);
    }
    

At last

At this point, the analysis of the DispatcherServlet initialization process has been completed. We can see that many detailed knowledge points in this article have not been expanded, because if these knowledge points are explained in depth, a blog post or even a few blog posts can be used to explain clearly. , As the topic goes deep, I will explain them one by one. In the next article, we will explain how springmvc processes this request after initiating a request from the browser.

Return to the topic list

Guess you like

Origin blog.csdn.net/u013328649/article/details/111870725