springboot作为web容器,其实现的主要是业务层框架,在http解析完成之后,而对于http服务器,还是集成的三方实现如tomcat,Undertow,Jetty等等,在于classpath下配置了哪些实现。
今天研究一下基于tomcat的实现,springboot最大的好处就是自动装配,那么,tomcat如何被自动装配的呢,我们知道只需要配置了spring-boot-starter-web坐标就可以直接使用tomcat了。所以不难猜出这个maven中可能存在tomcat的自动装配类(或间接导入其他maven坐标,完成了自动装配),幸运的是,就在这个包下。EmbeddedServletContainerAutoConfiguration就是自动装配类

  1. public class EmbeddedServletContainerAutoConfiguration {
  2. // tomcat自动装配
  3. @Configuration
  4. @ConditionalOnClass({ Servlet.class, Tomcat.class })
  5. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  6. public static class EmbeddedTomcat {
  7. @Bean
  8. public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
  9. return new TomcatEmbeddedServletContainerFactory();
  10. }
  11. }
  12. // Jetty的自动装配
  13. @Configuration
  14. @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
  15. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  16. public static class EmbeddedJetty {
  17. @Bean
  18. public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
  19. return new JettyEmbeddedServletContainerFactory();
  20. }
  21. }
  22. // Undertow的自动装配
  23. @Configuration
  24. @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
  25. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  26. public static class EmbeddedUndertow {
  27. @Bean
  28. public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
  29. return new UndertowEmbeddedServletContainerFactory();
  30. }
  31. }
  32. }

ServletContainer UML图

对于tomcat的自动装配就是向容器中添注册了一个bean new TomcatEmbeddedServletContainerFactory()我们来看看这个类的整体类体系图
image.png
可以看到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容器时。
image.png

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相关配置
可用的协议
image.png
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类下定义的配置
image.png

tomcat启动调用链路

bind()绑定端口调用链路,监听端口。
image.png
image.png
从以上两张截图可以看出tomcat的数据结构
image.png
Springboot会在启动完成时完成对tomcat的启动。