springboot作为web容器,其实现的主要是业务层框架,在http解析完成之后,而对于http服务器,还是集成的三方实现如tomcat,Undertow,Jetty等等,在于classpath下配置了哪些实现。
今天研究一下基于tomcat的实现,springboot最大的好处就是自动装配,那么,tomcat如何被自动装配的呢,我们知道只需要配置了spring-boot-starter-web
坐标就可以直接使用tomcat了。所以不难猜出这个maven中可能存在tomcat的自动装配类(或间接导入其他maven坐标,完成了自动装配),幸运的是,就在这个包下。EmbeddedServletContainerAutoConfiguration
就是自动装配类
public class EmbeddedServletContainerAutoConfiguration {
// tomcat自动装配
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
// Jetty的自动装配
@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();
}
}
// Undertow的自动装配
@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();
}
}
}
ServletContainer UML图
对于tomcat的自动装配就是向容器中添注册了一个bean new TomcatEmbeddedServletContainerFactory()
我们来看看这个类的整体类体系图
可以看到tomcat
,jetty
,undertow
都继承至AbstractEmbeddedServletContainerFactory
错误页面配置
public interface ErrorPageRegistry {
void addErrorPages(ErrorPage... errorPages);
}
Servlet容器相关配置
public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry {
// Context访问路径(即在tomcat中Context的名称)
void setContextPath(String contextPath);
// 监听端口配置
void setPort(int port);
void setRegisterDefaultServlet(boolean registerDefaultServlet);
// 添加容器上下文监听器(j2ee规范)
void setInitializers(List<? extends ServletContextInitializer> initializers);
void addInitializers(ServletContextInitializer... initializers);
// ssl配置
void setSsl(Ssl ssl);
void setSslStoreProvider(SslStoreProvider sslStoreProvider);
void setJspServlet(JspServlet jspServlet);
void setCompression(Compression compression);
void setServerHeader(String serverHeader);
void setLocaleCharsetMappings(Map<Locale, Charset> localeCharsetMappings);
}
Servlet容器的基础实现
然后是Servlet容器的最基础实现AbstractConfigurableEmbeddedServletContainer
,其最主要的就是定义了一个容器相关属性的基类,用于保存容器相关属性。
public abstract class AbstractConfigurableEmbeddedServletContainer
implements ConfigurableEmbeddedServletContainer {
private static final int DEFAULT_SESSION_TIMEOUT = (int) TimeUnit.MINUTES.toSeconds(30);
private String contextPath = "";
private String displayName;
private boolean registerDefaultServlet = true;
private int port = 8080;// 端口
// Context监听器
private List<ServletContextInitializer> initializers = new ArrayList<ServletContextInitializer>();
private File documentRoot;
private Set<ErrorPage> errorPages = new LinkedHashSet<ErrorPage>();
private MimeMappings mimeMappings = new MimeMappings(MimeMappings.DEFAULT);
private InetAddress address;// 启动绑定地址
private int sessionTimeout = DEFAULT_SESSION_TIMEOUT;
private boolean persistSession;
private File sessionStoreDir;// session会话存放路径
private Ssl ssl;// ssl属性POJO实体类
private SslStoreProvider sslStoreProvider;
private JspServlet jspServlet = new JspServlet();
private Compression compression;
private String serverHeader;
private Map<Locale, Charset> localeCharsetMappings = new HashMap<Locale, Charset>();
}
嵌入式Servlet容器基础类AbstractEmbeddedServletContainerFactory
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" };
// 查找一个可以用来作为tomcat工作根目录的路径
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();
return file;
}
}
可以看到这里配置了静态资源存放路径(这也就是我们将html放在static
目录下的原因)。
在idea中启动时getValidDocumentRoot()
基本返回是null的,所以就会在用户目录下创建临时目录,例如在TomcatEmbeddedServletContainerFactory
实现中创建Servlet容器时。
Tomcat实现TomcatEmbeddedServletContainerFactory
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private static final Set<Class<?>> NO_CLASSES = Collections.emptySet();
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private File baseDirectory;
// 引擎阀门
private List<Valve> engineValves = new ArrayList<Valve>();
// 自定义阀门
private List<Valve> contextValves = new ArrayList<Valve>();
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<LifecycleListener>();
// 自定义修改Connector的钩子方法
private List<TomcatContextCustomizer> tomcatContextCustomizers = new ArrayList<TomcatContextCustomizer>();
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<TomcatConnectorCustomizer>();
// 自定义的Connector,如将http端口重定向到https端口
private List<Connector> additionalTomcatConnectors = new ArrayList<Connector>();
private ResourceLoader resourceLoader;
// 使用的Tomcat IO模型,默认NIO
// 我们当然可以改成BIO实现org.apache.coyote.http11.Http11Protocol
private String protocol = DEFAULT_PROTOCOL;
private Set<String> tldSkipPatterns = new LinkedHashSet<String>(TldSkipPatterns.DEFAULT);
private Charset uriEncoding = DEFAULT_CHARSET;
private int backgroundProcessorDelay;
}
应用中我们可以不使用springboot的自动装配,或者在自动装配基础上进一步修正AbstractEmbeddedServletContainerFactory
中的相关属性,如修改使用NIO或BIO模型,或者https相关配置。
可用的协议
Connector钩子方法接口
public interface TomcatConnectorCustomizer {
void customize(Connector connector);
}
在属性配置中,spring提供了很多我们可以自定义修改的属性
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
// 端口
private Integer port;
private String contextPath;
// Servlet初始化参数
private final Map<String, String> contextParameters = new HashMap<String, String>();
@NestedConfigurationProperty
private ErrorProperties error = new ErrorProperties();
// path
private String servletPath = "/";
private Integer connectionTimeout;
private Session session = new Session();
// ssl相关配置
@NestedConfigurationProperty
private Ssl ssl;
// 压缩相关配置
@NestedConfigurationProperty
private Compression compression = new Compression();
@NestedConfigurationProperty
private JspServlet jspServlet;
// tomcat的定制配置
private final Tomcat tomcat = new Tomcat();
// jetty定制配置
private final Jetty jetty = new Jetty();
// undertow定制配置
private final Undertow undertow = new Undertow();
private Environment environment;
}
tomcat启动调用链路
bind()绑定端口调用链路,监听端口。
从以上两张截图可以看出tomcat的数据结构
Springboot会在启动完成时完成对tomcat的启动。