Springboot 接口 EmbeddedServletContainerFactory : 嵌入式servlet容器工厂
位于包 org.springframework.boot.context.embedded接口定义如下 : /*** 用于创建EmbeddedServletContainer的工厂接口定义。可能的话,建议实现类扩展自* AbstractEmbeddedServletContainerFactory 。** Factory interface that can be used to
·
接口 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 实现类 |
---|---|
Tomcat | TomcatEmbeddedServletContainerFactory |
Jetty | JettyEmbeddedServletContainerFactory |
Undertow | UndertowEmbeddedServletContainerFactory |
我们再来看看他们之间的类继承关系:
EmbeddedServletContainerFactory
的应用
上面这些类又是怎么被应用于生成相应的Servlet
容器呢 ?在包spring-boot-autoconfigure
的META-INF/spring.factories
中,有这么一行 :
该自动配置类是如何被使用的,可以参考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 是如何工作的 ?
更多推荐
已为社区贡献14条内容
所有评论(0)