缺省配置Springboot Web应用启动中准备 DispatcherServlet

DispatcherServlet 注册到Spring容器

在一个Springboot Web应用中,Spring MVCDispatcherServlet是通过Springboot autoconfigure机制注册进来的。

jarspring-boot-autoconfigure-xxx.jarorg.springframework.boot.autoconfigure.web下面存在autoconfigureDispatcherServletAutoConfiguration,通过该类 :

  1. 首先,DispatcherServlet被作为一个单例bean被定义和注册到容器;
  2. 然后,又定义了另外一个ServletRegistrationBean bean用来添加该DispatcherServlet beanServletContext;

其具体实现逻辑如下所示 :

/*
	 * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
	 */
	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

	/*
	 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
	 */
	public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = 
		"dispatcherServletRegistration";

	@Configuration
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration {

		private final WebMvcProperties webMvcProperties;

		public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
			this.webMvcProperties = webMvcProperties;
		}

		// 定义一个Bean到Spring IoC容器,名称为dispatcherServlet,对应为DispatcherServlet
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet() {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(
					this.webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(
					this.webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(
					this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
			return dispatcherServlet;
		}

		@Bean
		@ConditionalOnBean(MultipartResolver.class)
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
			// Detect if the user has created a MultipartResolver but named it incorrectly
			return resolver;
		}

	}
	

	@Configuration
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		private final ServerProperties serverProperties;

		private final WebMvcProperties webMvcProperties;

		private final MultipartConfigElement multipartConfig;

		public DispatcherServletRegistrationConfiguration(
				ServerProperties serverProperties, WebMvcProperties webMvcProperties,
				ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
			this.serverProperties = serverProperties;
			this.webMvcProperties = webMvcProperties;
			this.multipartConfig = multipartConfigProvider.getIfAvailable();
		}

		// 定义另外一个bean ServletRegistrationBean,名字为dispatcherServletRegistration,
		// 这个ServletRegistrationBean有点特殊,它不是个普通意义上的Bean,它实现了Spring SCI
		// 接口,而对于此类实现了Spring SCI接口的bean定义,在内置的Tomcat servlet容器启动阶段,
		// 严格地讲,是其中相当于web app的StarndartContext的启动阶段,会被逐个实例化并调用其
		// onStartup()方法
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, 
			name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public ServletRegistrationBean dispatcherServletRegistration(
				DispatcherServlet dispatcherServlet) {
			ServletRegistrationBean registration = new ServletRegistrationBean(
					dispatcherServlet, this.serverProperties.getServletMapping());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(
					this.webMvcProperties.getServlet().getLoadOnStartup());
			if (this.multipartConfig != null) {
				registration.setMultipartConfig(this.multipartConfig);
			}
			return registration;
		}

	}

WebApplicationContext启动过程指定Tomcat Servlet容器执行一些Spring SCI

// WebApplicationContext启动过程中创建嵌入式Servlet的方法,
// 具体实现类是 EmbeddedWebApplicationContext
private void createEmbeddedServletContainer() {
		EmbeddedServletContainer localContainer = this.embeddedServletContainer;
		ServletContext localServletContext = getServletContext();
		if (localContainer == null && localServletContext == null) {
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			// 这里getSelfInitializer()返回一个实现了SCI接口的实例,目的是
			// 让嵌入式Tomcat Servlet容器在相应的SCI执行阶段执行其逻辑。
			// getSelfInitializer()的具体实现见下面的分析。
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		}
		else if (localServletContext != null) {
			try {
				getSelfInitializer().onStartup(localServletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}
	/**
	 * Returns the ServletContextInitializer that will be used to complete the
	 * setup of this WebApplicationContext.
	 * 返回一个SCI实例,让内置Servlet容器在ServletContext就绪时执行相应的SCI逻辑。
	 * 这个SCI实例的逻辑就是当前WebApplicationContext的selfInitialize()方法。
	 * @return the self initializer
	 * @see #prepareEmbeddedWebApplicationContext(ServletContext)
	 */
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return new ServletContextInitializer() {
			@Override
			public void onStartup(ServletContext servletContext) throws ServletException {
				selfInitialize(servletContext);
			}
		};
	}		

从上面的分析可以看出,在一个Springboot Web应用中,DispatcherServlet是作为一个bean首先被注册到Spring容器中的,并且Springboot还注册了一个向Servlet容器中的Web应用注册DispatcherServlet的注册器ServletRegistrationBean

作为一个Servlet,被定义时还有一个属性load-on-startup可以被设置用来指示该Servlet是否需要在容器初始化过程中被初始化。从上面的分析可以看出,缺省Springboot Web应用并没有设置load-on-startup属性,也就是说,该Servlet不会在容器启动过程中被初始化。

DispatcherServlet注册到Web应用

//调用链 : 
Tomcat StandardContxt.start()
=>startInternal()
 => TomcatStarter.onStartup()
  => EmbeddedWebApplicationContext.selfInitialize()

Tomcat StandardContext启动时调用SCIonStartup方法

这里TomcatStarter是一个SpringServlet API标准SCI接口的一个实现,Tomcat Servlet容器根据Servlet规范的要求,会在容器启动过程中被调用其onStartup()方法。

     // Call ServletContainerInitializers
     // 在采用缺省配置的Springboot web应用中,下面的initializers其实就包含一个TomcatStarter对象
            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                    entry.getKey().onStartup(entry.getValue(),
                            getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"), e);
                    ok = false;
                    break;
                }
            }

TomcatStarteronStartup方法执行指定的Spring SCI实例的onStartup方法

	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
			throws ServletException {
		try {
			for (ServletContextInitializer initializer : this.initializers) {
				initializer.onStartup(servletContext);
			}
		}
		catch (Exception ex) {
			this.startUpException = ex;
			// Prevent Tomcat from logging and re-throwing when we know we can
			// deal with it in the main thread, but log for information here.
			if (logger.isErrorEnabled()) {
				logger.error("Error starting Tomcat context. Exception: "
						+ ex.getClass().getName() + ". Message: " + ex.getMessage());
			}
		}
	}

EmbeddedWebApplicationContext的方法selfInitialize

	// 根据上面的分析,该方法会在内置Tomcat Servlet容器的ServletContext就绪后的相应时机被回调。
	private void selfInitialize(ServletContext servletContext) throws ServletException {
		// 主要是将当前Spring EmbeddedWebApplicationContext实例和ServletContext互相关联:
		// 1.将当前EmbeddedWebApplicationContext实例登记为ServletContext对象的属性
		// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
		// 2.将ServletContext对象设置到当前EmbeddedWebApplicationContext实例的成员变量
		// servletContext。
		prepareEmbeddedWebApplicationContext(servletContext);
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
				beanFactory);
		WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
				getServletContext());
		existingScopes.restore();
		WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
				getServletContext());
		// 从Bean容器中获取所有是SCI的bean,并调用其onStartup()方法
		// 而通过上面的分析可知,用于注册DispatcherServlet bean的ServletRegistrationBean,
		// 正是这样一个SCI,所以这里会尝试从容器中获取这个bean并执行其onStartup()方法,此过程
		// 也首先触发了DispatchServlet bean的创建。
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}	
	/**
	 * Returns ServletContextInitializers that should be used with the embedded
	 * Servlet context. By default this method will first attempt to find
	 * ServletContextInitializer, Servlet, Filter and certain EventListener beans.
	 * return the servlet initializer beans
	 */
	protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
		return new ServletContextInitializerBeans(getBeanFactory());
	}	

DispatcherServlet被注册到ServletContext

	// ServletRegistrationBean类的方法
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		Assert.notNull(this.servlet, "Servlet must not be null");
		String name = getServletName();
		if (!isEnabled()) {
			logger.info("Servlet " + name + " was not registered (disabled)");
			return;
		}
		logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
		// 将一个Servlet添加到Tomcat的ServletContext对象,在本文中,我们指的这个Servlet实例
		// 就是Spring MVC核心的前端控制器DispatcherServlet
		Dynamic added = servletContext.addServlet(name, this.servlet);
		if (added == null) {
			logger.info("Servlet " + name + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
		configure(added);
	}

到此为止,整个Springboot Web应用启动过程中对DispatcherServlet bean的准备工作就完全结束了。

然而,DispatcherServlet是一个Servlet,按说它生命周期中的初始化方法init()应该被调用却没有在上述应用启动过程中发生,这又是为什么呢 ? 其原因其实也很简单。如上分析所说,因为DispatcherServlet的属性load-on-startup没有被设置,所以该DispatcherServlet的初始化,也就是init()方法的调用,不是在容器启动过程中,而是在容器接收到第一个来自客户端针对该Servlet的请求时发生。

相关文章

缺省配置Springboot Web应用中tomcat的启动过程
缺省配置Springboot Web应用启动过程中Bean定义的登记

你可能感兴趣的:(Tomcat,Spring,Boot)