Spring Web - WebServer 自动配置的实现及嵌入式 Tomcat 的启动过程

本文通过对spring-boot-starter-web自动配置类的梳理,结合Spring Boot Application的启动过程,明确了Tomcat Web Server的创建和启动过程。

1. AutoConfiguration相关类

AutoConfiguration类主要有WebMvcAutoConfiguration,DispatcherServletAutoConfiguration, ServletWebServerFactoryAutoConfiguration。
在这里插入图片描述

2. Embedded Tomcat启动过程

2.1 注册ServletContextAwareProcessor

注册WebApplicationContextServletContextAwareProcessor的Bean到Bean Factory,该PostProcessorBean为后续创建的实现ServletContextAware接口的bean设置ServletContext对象,为实现ServletConfigAware接口的bean设置ServletConfig对象。

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#postProcessBeanFactory

/**
 * Register ServletContextAwareProcessor.
 * @see ServletContextAwareProcessor
 */
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	beanFactory.addBeanPostProcessor(
			// org.springframework.boot.web.servlet.context.WebApplicationContextServletContextAwareProcessor
			new WebApplicationContextServletContextAwareProcessor(this));
	beanFactory.ignoreDependencyInterface(ServletContextAware.class);
	registerWebApplicationScopes();
}

org.springframework.web.context.support.ServletContextAwareProcessor#postProcessBeforeInitialization

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	if (getServletContext() != null && bean instanceof ServletContextAware) {
		((ServletContextAware) bean).setServletContext(getServletContext());
	}
	if (getServletConfig() != null && bean instanceof ServletConfigAware) {
		((ServletConfigAware) bean).setServletConfig(getServletConfig());
	}
	return bean;
}

2.2 创建TomcatWebServer

通过自动配置类ServletWebServerFactoryAutoConfiguration创建TomcatServletWebServerFactory。
通过调用TomcatServletWebServerFactory的getWebServer方法获取TomcatWebServer。

如果使用可执行jar启动,servletContext为空,创建ServletWebServerFactory,生成WebServer对象。
如果使用war部署,servletContext不为空,只进行servlet context初始化

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	// 如果使用可执行jar启动,servletContext为空,创建ServletWebServerFactory,生成WebServer对象。
	if (webServer == null && servletContext == null) {
		// 获取ServletWebServerFactory,如果底层容器是tomcat,返回TomcatServletWebServerFactory。
		ServletWebServerFactory factory = getWebServerFactory();
		// 通过ServletWebServerFactory创建WebServer
		this.webServer = factory.getWebServer(getSelfInitializer());
	}
	// 如果使用war部署,servletContext不为空,只进行servlet context初始化
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context",
					ex);
		}
	}
	initPropertySources();
}

以下代码只有在使用可执行jar(嵌入式tomcat)时才会执行
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory
			: createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)

/**
 * Create a new {@link TomcatWebServer} instance.
 * @param tomcat the underlying Tomcat server
 * @param autoStart if the server should be started
 */
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
	Assert.notNull(tomcat, "Tomcat Server must not be null");
	this.tomcat = tomcat;
	this.autoStart = autoStart;
	initialize();
}

private void initialize() throws WebServerException {
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	synchronized (this.monitor) {
		try {
			addInstanceIdToEngineName();

			Context context = findContext();
			context.addLifecycleListener((event) -> {
				if (context.equals(event.getSource())
						&& Lifecycle.START_EVENT.equals(event.getType())) {
					// Remove service connectors so that protocol binding doesn't
					// happen when the service is started.
					removeServiceConnectors();
				}
			});

			// Start the server to trigger initialization listeners
			this.tomcat.start();

			// We can re-throw failure exception directly in the main thread
			rethrowDeferredStartupExceptions();

			try {
				ContextBindings.bindClassLoader(context, context.getNamingToken(),
						getClass().getClassLoader());
			}
			catch (NamingException ex) {
				// Naming is not enabled. Continue
			}

			// Unlike Jetty, all Tomcat threads are daemon threads. We create a
			// blocking non-daemon to stop immediate shutdown
			startDaemonAwaitThread();
		}
		catch (Exception ex) {
			stopSilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex);
		}
	}
}

2.3 启动Tomcat Web Server

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#start

@Override
public void start() throws WebServerException {
	synchronized (this.monitor) {
		if (this.started) {
			return;
		}
		try {
			addPreviouslyRemovedConnectors();
			Connector connector = this.tomcat.getConnector();
			if (connector != null && this.autoStart) {
				performDeferredLoadOnStartup();
			}
			checkThatConnectorsHaveStarted();
			this.started = true;
			logger.info("Tomcat started on port(s): " + getPortsDescription(true)
					+ " with context path '" + getContextPath() + "'");
		}
		catch (ConnectorStartFailedException ex) {
			stopSilently();
			throw ex;
		}
		catch (Exception ex) {
			throw new WebServerException("Unable to start embedded Tomcat server",
					ex);
		}
		finally {
			Context context = findContext();
			ContextBindings.unbindClassLoader(context, context.getNamingToken(),
					getClass().getClassLoader());
		}
	}
}

2.4 执行ServletContextInitializer

2.4.1 ServletContextInitializer相关类图

通过下面的类图可以发现可以通过DispatcherServletRegistrationBean注册DispatcherServlet,通过FilterRegistrationBean注册Filter。
在这里插入图片描述
org.springframework.boot.web.servlet.ServletRegistrationBean

A ServletContextInitializer to register Servlets in a Servlet 3.0+ container. Similar to the registration features provided by ServletContext but with a Spring Bean friendly design.
The servlet must be specified before calling onStartup. URL mapping can be configured used setUrlMappings or omitted when mapping to ‘/*’ (unless alwaysMapUrl is set to false). The servlet name will be deduced if not specified.

org.springframework.boot.web.servlet.ServletContextInitializer

Interface used to configure a Servlet 3.0+ context programmatically. Unlike WebApplicationInitializer, classes that implement this interface (and do not implement WebApplicationInitializer) will not be detected by SpringServletContainerInitializer and hence will not be automatically bootstrapped by the Servlet container.
This interface is primarily designed to allow ServletContextInitializers to be managed by Spring and not the Servlet container.

2.4.2 获取ServletContext实例

org.apache.catalina.core.StandardContext#getServletContext

/**
  * @return the servlet context for which this Context is a facade.
  */
 @Override
 public ServletContext getServletContext() {
     if (context == null) {
         context = new ApplicationContext(this);
         if (altDDName != null)
             context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
     }
     return context.getFacade();
 }

2.4.3 初始化ServletContext

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize

private void selfInitialize(ServletContext servletContext) throws ServletException {
	// 将当前ApplicationContext设置到ServletContext
	// servletContext.setAttribute(
	//				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
	prepareWebApplicationContext(servletContext);
	// ServletContextScope: Scope wrapper for a ServletContext, i.e. for global web application attributes.
	// This scope is registered as default scope with key "application".
	registerApplicationScope(servletContext);
	// Register web-specific environment beans ("contextParameters", "contextAttributes") with the given BeanFactory, as used by the WebApplicationContext.
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
			servletContext);
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		// 调用onStartUp
		beans.onStartup(servletContext);
	}
}

org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	this.initializerTypes = (initializerTypes.length != 0)
			? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
    // 添加Servlet
	addServletContextInitializerBeans(beanFactory);
	// 添加Filter等
	addAdaptableBeans(beanFactory);
	List<ServletContextInitializer> sortedInitializers = this.initializers.values()
			.stream()
			.flatMap((value) -> value.stream()
					.sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}

执行完毕后initializers字段的值。
在这里插入图片描述

2.5 实例化Spring Web MVC相关Bean

可参考 org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

附录

1. About javax.servlet.ServletContext

Defines a set of methods that a servlet uses to communicate with its servlet container, for example, to get the MIME type of a file, dispatch requests, or write to a log file.
There is one context per “web application” per Java Virtual Machine. (A “web application” is a collection of servlets and content installed under a specific subset of the server’s URL namespace such as /catalog and possibly installed via a .war file.)

2. 那些*Initializer们

  1. org.springframework.context.ApplicationContextInitializer
  2. org.springframework.boot.web.servlet.ServletContextInitializer
  3. javax.servlet.ServletContainerInitializer
  4. org.springframework.web.SpringServletContainerInitializer(an implementation of javax.servlet.ServletContainerInitializer)
  5. org.springframework.web.WebApplicationInitializer
  6. org.springframework.boot.web.support.SpringBootServletInitializer ( an implementation of org.springframework.web.WebApplicationInitializer)

3. TomcatServletWebServerFactory相关类

在这里插入图片描述

参考

SpringBoot深入(一)–SpringBoot内置web容器及配置
spring-boot定制和优化内嵌的Tomcat

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值