Springboot 接口 EmbeddedServletContainerFactory : 嵌入式servlet容器工厂

本文解析了Spring Boot中EmbeddedServletContainerFactory接口及其实现类,包括Tomcat、Jetty和Undertow等常见Servlet容器的支持方式。介绍了这些容器如何通过自动配置被识别和注册。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接口 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 是如何工作的 ?

### Servlet 容器的工作原理、配置与使用 #### 什么是 Servlet 容器Servlet 容器是实现 Java Servlet 规范的软件组件,负责处理 HTTP 请求并将请求传递给相应的 Servlet。常见的 Servlet 容器有 Tomcat、Jetty 和 Undertow 等[^3]。 --- #### Servlet 容器工作原理 1. **加载 Web 应用** 当 Servlet 容器启动时,会读取 `WEB-INF/web.xml` 文件(如果存在)以及其他配置资源,解析其中定义的 Servlet、Filter 和 Listener,并初始化它们。 2. **接收 HTTP 请求** Servlet 容器监听指定端口上的 HTTP 请求,接收到请求后将其封装为 `HttpServletRequest` 对象,并创建对应的响应对象 `HttpServletResponse`。 3. **分发请求到对应 Servlet** 根据 URL 映射规则,容器将请求转发至匹配的 Servlet 实例。如果没有找到匹配项,则返回 404 错误页面。 4. **调用生命周期方法** - 初始化阶段:当首次访问某 Servlet 或者按照 `<load-on-startup>` 参数提前加载时,容器会调用其 `init()` 方法完成初始化操作[^4]。 - 处理请求阶段:每次客户端发起新的请求都会触发 `service()` 方法执行业务逻辑。 - 销毁阶段:在服务器关闭或者卸载应用之前销毁所有已实例化的 Servlet 并释放相关资源。 5. **发送响应数据回客户端** 最终由 Servlet 构建的内容通过 HttpServletResponse 流形式传输回去展示给用户浏览器。 --- #### 配置嵌入式 Servlet 容器 Spring Boot 提供了一种简单的方式来配置和使用嵌入式 Servlet 容器,无需单独部署 WAR 包到外部环境即可运行完整的 Web 应用程序。 ##### 默认设置 默认情况下,Spring Boot 使用 Apache Tomcat 作为内置 Servlet 容器。可以通过依赖管理工具 Maven 添加如下依赖自动引入: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` ##### 自定义配置 要调整嵌入式容器的行为,比如更改端口号、上下文路径等属性,可以直接编辑 application.properties 或 yml 文件: ```properties server.port=8090 server.servlet.context-path=/myapp ``` 对于更复杂的场景可能涉及覆盖默认 Factory Bean 类型从而替换整个引擎版本甚至切换其他品牌替代品如 Jetty/Undertow等等[^5]: ```java @Bean public EmbeddedServletContainerFactory servletContainer() { TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); // 设置额外选项... factory.addConnectorCustomizers(connector -> connector.setPort(8081)); return factory; } ``` --- #### 注册三大组件 除了基本的服务外还可以灵活添加 Filter、Listener 来增强功能满足不同需求: - **Filters**: 过滤链允许开发者拦截传入传出的数据流做预处理或后修饰动作; - **Listeners**: 能够感知应用程序生命周期事件的发生进而采取行动; - **Servlets**: 主要是用来映射特定 URI 模式的处理器函数体。 示例代码片段展示了如何声明这些结构并通过 spring boot 方便快捷地注入进去: ```java @Component public class ExampleFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; System.out.println("Filtered Request: "+httpRequest.getRequestURI()); chain.doFilter(request,response); } } @SpringBootApplication public class DemoApplication extends SpringBootServletInitializer{ public static void main(String[] args){ SpringApplication.run(DemoApplication.class,args); } @Bean public ServletRegistrationBean<MyOwnServlet> myServlet(){ return new ServletRegistrationBean<>(new MyOwnServlet(),"/*"); } } ``` --- #### 修改成其他 Servlet 容器 假如不想继续沿用 tomcat ,只需排除原有插件再加入目标产品的新坐标就可以了 。例如迁移到 jetty 上去 : ```xml <!-- 移除tomcat --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!-- 引入jetty --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值