接口 EmbeddedServletContainerFactory简介

位于包

org.springframework.boot.context.embedded

接口定义如下 :

/**
 * 用于创建EmbeddedServletContainer的工厂接口定义。可能的话,建议实现类扩展自
 * AbstractEmbeddedServletContainerFactory 。
 * 
 * Factory interface that can be used to create EmbeddedServletContainers.
 * Implementations are encouraged to extend
 * AbstractEmbeddedServletContainerFactory when possible.
 *
 * @author Phillip Webb
 * @see EmbeddedServletContainer
 * @see AbstractEmbeddedServletContainerFactory
 * @see JettyEmbeddedServletContainerFactory
 * @see TomcatEmbeddedServletContainerFactory
 */
public interface EmbeddedServletContainerFactory {

	/**
	 * 获取一个新的已经完全配置但是出于暂停状态的EmbeddedServletContainer实例。
	 * 
	 * 在该方法返回的EmbeddedServletContainer的方法start()调用前客户端不能连接到服务器。
	 * 
	 * EmbeddedServletContainer的方法start()会在ApplicationContext的完全刷新后被调用。
	 * 
	 * Gets a new fully configured but paused EmbeddedServletContainer instance.
	 * Clients should not be able to connect to the returned server until
	 * EmbeddedServletContainer#start() is called (which happens when the
	 * ApplicationContext has been fully refreshed).
	 * @参数 initializers ServletContextInitializers that should be applied as
	 * the container starts 容器启动时需要被应用的ServletContextInitializer
	 * @返回值 a fully configured and started EmbeddedServletContainer 
	 * 一个被完全配置和已经被启动的EmbeddedServletContainer实例
	 * @参考 EmbeddedServletContainer#stop()
	 */
	EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers);

}

实际上,为了支持几种常见的 Servlet 容器, Springboot 提供了针对他们的EmbeddedServletContainerFactory 实现,如下所示 (这些实现类位于包org.springframework.boot.context.embedded的某个子包下面):

Servlet容器EmbeddedServletContainerFactory实现类
TomcatTomcatEmbeddedServletContainerFactory
JettyJettyEmbeddedServletContainerFactory
UndertowUndertowEmbeddedServletContainerFactory

我们再来看看他们之间的类继承关系:

EmbeddedServletContainerFactory及其实现类

EmbeddedServletContainerFactory的应用

上面这些类又是怎么被应用于生成相应的Servlet容器呢 ?在包spring-boot-autoconfigureMETA-INF/spring.factories中,有这么一行 :
EmbeddedServletContainerAutoConfiguration

该自动配置类是如何被使用的,可以参考Spring EnableAutoConfigurationImportSelector 是如何工作的 ?

EmbeddedServletContainerAutoConfiguration自动配置

接下来我们来看这个配置类是如何识别和注册相应的 Servlet 容器的 :

package org.springframework.boot.autoconfigure.web;

// 这里仅仅保留了方便理解本文重点的一些导入行,删除了其他import导入行。
import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;

/**
 * 内置 Servlet 容器的自动配置类
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Ivan Sopov
 * @author Stephane Nicoll
 */
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 * 针对 Tomcat 的配置类,用于定义一个bean TomcatEmbeddedServletContainerFactory,
	 * 也就是针对 Tomcat 的 EmbeddedServletContainerFactory 接口实现类。
	 * 
	 * 此配置类仅在开发人员打算使用Tomcat时起作用。
	 * 
	 * 那什么算是开发人员打算使用 Tomcat 呢 ?
	 * 答 : classpath 上如果存在类 Servlet, Tomcat ,则意味着引入了包 
	 * spring-boot-starter-tomcat ,并且如果没有 bean EmbeddedServletContainerFactory 
	 * 已经存在于容器,则可以认为开发人员准备使用 Tomcat 作为内置的 Servlet 容器了,此时
	 * 该配置类生效,他会定义一个bean TomcatEmbeddedServletContainerFactory。
	 * Servlet 容器
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 * 针对 Jetty 的配置类,用于生成一个bean JettyEmbeddedServletContainerFactory,
	 * 也就是针对 Jetty 的 EmbeddedServletContainerFactory 接口实现类。
	 * 
	 * 这里使用了类似上面的方法,先检查 classpath 上是否包含针对 Jetty 的某些关键类是否存在,
	 * 然后再看是否已经存在一个 bean EmbeddedServletContainerFactory,如果这些关键类存在
	 * 并且不存在 bean EmbeddedServletContainerFactory,这该配置类生效,会定义一个bean 
	 * JettyEmbeddedServletContainerFactory。
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
			return new JettyEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
 	 * 针对 Undertow 的配置类,用于生成一个bean UndertowEmbeddedServletContainerFactory,
	 * 也就是针对 Undertow 的 EmbeddedServletContainerFactory 接口实现类。
	 * 
	 * 这里使用了类似上面的方法,先检查 classpath 上是否包含针对 Undertow 的某些关键类是否存在,
	 * 然后再看是否已经存在一个 bean EmbeddedServletContainerFactory,如果这些关键类存在
	 * 并且不存在 bean EmbeddedServletContainerFactory,这该配置类生效,会定义一个bean 
	 * UndertowEmbeddedServletContainerFactory。
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
			return new UndertowEmbeddedServletContainerFactory();
		}

	}

	/**
	 * Registers a EmbeddedServletContainerCustomizerBeanPostProcessor. Registered
	 * via ImportBeanDefinitionRegistrar for early registration.
	 */
	public static class BeanPostProcessorsRegistrar
			implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			if (beanFactory instanceof ConfigurableListableBeanFactory) {
				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
			}
		}

		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
			registerSyntheticBeanIfMissing(registry,
					"embeddedServletContainerCustomizerBeanPostProcessor",
					EmbeddedServletContainerCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry,
					"errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
				String name, Class<?> beanClass) {
			if (ObjectUtils.isEmpty(
					this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
				beanDefinition.setSynthetic(true);
				registry.registerBeanDefinition(name, beanDefinition);
			}
		}

	}

}

AbstractEmbeddedServletContainerFactory的工作

最后,我们再来看看AbstractEmbeddedServletContainerFactory的工作又有哪些。


package org.springframework.boot.context.embedded;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.ApplicationHome;
import org.springframework.boot.ApplicationTemp;
import org.springframework.util.Assert;

/**
 * Abstract base class for EmbeddedServletContainerFactory implementations.
 * EmbeddedServletContainerFactory 实现类的抽象基类,用于提供一些公共方法或者属性。
 *
 * @author Phillip Webb
 * @author Dave Syer
 */
public abstract class AbstractEmbeddedServletContainerFactory
		extends AbstractConfigurableEmbeddedServletContainer
		implements EmbeddedServletContainerFactory {

	protected final Log logger = LogFactory.getLog(getClass());

	private static final String[] COMMON_DOC_ROOTS = { "src/main/webapp", "public",
			"static" };

	public AbstractEmbeddedServletContainerFactory() {
		super();
	}

	public AbstractEmbeddedServletContainerFactory(int port) {
		super(port);
	}

	public AbstractEmbeddedServletContainerFactory(String contextPath, int port) {
		super(contextPath, port);
	}

	/**
	 * Returns the absolute document root when it points to a valid directory, logging a
	 * warning and returning {@code null} otherwise.
	 * @return the valid document root
	 */
	protected final File getValidDocumentRoot() {
		File file = getDocumentRoot();
		// If document root not explicitly set see if we are running from a war archive
		file = file != null ? file : getWarFileDocumentRoot();
		// If not a war archive maybe it is an exploded war
		file = file != null ? file : getExplodedWarFileDocumentRoot();
		// Or maybe there is a document root in a well-known location
		file = file != null ? file : getCommonDocumentRoot();
		if (file == null && this.logger.isDebugEnabled()) {
			this.logger
					.debug("None of the document roots " + Arrays.asList(COMMON_DOC_ROOTS)
							+ " point to a directory and will be ignored.");
		}
		else if (this.logger.isDebugEnabled()) {
			this.logger.debug("Document root: " + file);
		}
		return file;
	}

	private File getExplodedWarFileDocumentRoot() {
		return getExplodedWarFileDocumentRoot(getCodeSourceArchive());
	}

	protected List<URL> getUrlsOfJarsWithMetaInfResources() {
		ClassLoader classLoader = getClass().getClassLoader();
		List<URL> staticResourceUrls = new ArrayList<URL>();
		if (classLoader instanceof URLClassLoader) {
			for (URL url : ((URLClassLoader) classLoader).getURLs()) {
				try {
					if ("file".equals(url.getProtocol())) {
						File file = new File(url.getFile());
						if (file.isDirectory()
								&& new File(file, "META-INF/resources").isDirectory()) {
							staticResourceUrls.add(url);
						}
						else if (isResourcesJar(file)) {
							staticResourceUrls.add(url);
						}
					}
					else {
						URLConnection connection = url.openConnection();
						if (connection instanceof JarURLConnection) {
							if (isResourcesJar((JarURLConnection) connection)) {
								staticResourceUrls.add(url);
							}
						}
					}
				}
				catch (IOException ex) {
					throw new IllegalStateException(ex);
				}
			}
		}
		return staticResourceUrls;
	}

	private boolean isResourcesJar(JarURLConnection connection) {
		try {
			return isResourcesJar(connection.getJarFile());
		}
		catch (IOException ex) {
			return false;
		}
	}

	private boolean isResourcesJar(File file) {
		try {
			return isResourcesJar(new JarFile(file));
		}
		catch (IOException ex) {
			return false;
		}
	}

	private boolean isResourcesJar(JarFile jar) throws IOException {
		try {
			return jar.getName().endsWith(".jar")
					&& (jar.getJarEntry("META-INF/resources") != null);
		}
		finally {
			jar.close();
		}
	}

	File getExplodedWarFileDocumentRoot(File codeSourceFile) {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Code archive: " + codeSourceFile);
		}
		if (codeSourceFile != null && codeSourceFile.exists()) {
			String path = codeSourceFile.getAbsolutePath();
			int webInfPathIndex = path
					.indexOf(File.separatorChar + "WEB-INF" + File.separatorChar);
			if (webInfPathIndex >= 0) {
				path = path.substring(0, webInfPathIndex);
				return new File(path);
			}
		}
		return null;
	}

	private File getWarFileDocumentRoot() {
		return getArchiveFileDocumentRoot(".war");
	}

	private File getArchiveFileDocumentRoot(String extension) {
		File file = getCodeSourceArchive();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Code archive: " + file);
		}
		if (file != null && file.exists() && !file.isDirectory()
				&& file.getName().toLowerCase().endsWith(extension)) {
			return file.getAbsoluteFile();
		}
		return null;
	}

	private File getCommonDocumentRoot() {
		for (String commonDocRoot : COMMON_DOC_ROOTS) {
			File root = new File(commonDocRoot);
			if (root.exists() && root.isDirectory()) {
				return root.getAbsoluteFile();
			}
		}
		return null;
	}

	private File getCodeSourceArchive() {
		return getCodeSourceArchive(getClass().getProtectionDomain().getCodeSource());
	}

	File getCodeSourceArchive(CodeSource codeSource) {
		try {
			URL location = (codeSource == null ? null : codeSource.getLocation());
			if (location == null) {
				return null;
			}
			String path;
			URLConnection connection = location.openConnection();
			if (connection instanceof JarURLConnection) {
				path = ((JarURLConnection) connection).getJarFile().getName();
			}
			else {
				path = location.toURI().getPath();
			}
			if (path.contains("!/")) {
				path = path.substring(0, path.indexOf("!/"));
			}
			return new File(path);
		}
		catch (Exception ex) {
			return null;
		}
	}

	protected final File getValidSessionStoreDir() {
		return getValidSessionStoreDir(true);
	}

	protected final File getValidSessionStoreDir(boolean mkdirs) {
		File dir = getSessionStoreDir();
		if (dir == null) {
			return new ApplicationTemp().getDir("servlet-sessions");
		}
		if (!dir.isAbsolute()) {
			dir = new File(new ApplicationHome().getDir(), dir.getPath());
		}
		if (!dir.exists() && mkdirs) {
			dir.mkdirs();
		}
		Assert.state(!mkdirs || dir.exists(), "Session dir " + dir + " does not exist");
		Assert.state(!dir.isFile(), "Session dir " + dir + " points to a file");
		return dir;
	}

	/**
	 * Returns the absolute temp dir for given servlet container.
	 * @param prefix servlet container name
	 * @return The temp dir for given servlet container.
	 */
	protected File createTempDir(String prefix) {
		try {
			File tempDir = File.createTempFile(prefix + ".", "." + getPort());
			tempDir.delete();
			tempDir.mkdir();
			tempDir.deleteOnExit();
			return tempDir;
		}
		catch (IOException ex) {
			throw new EmbeddedServletContainerException(
					"Unable to create tempDir. java.io.tmpdir is set to "
							+ System.getProperty("java.io.tmpdir"),
					ex);
		}
	}

}

相关资料

缺省配置Springboot Web应用中tomcat的启动过程
Spring EnableAutoConfigurationImportSelector 是如何工作的 ?

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐