Tomcat 的架构 (也叫作 Catalina),是一个精密的层级结构系统。java
/conf/server.xmlweb
<?xml version="1.0" encoding="UTF-8"?> <!-- Apache Tomcat v9.0.30 配置文件 --> <!-- Server 表明了一个 Tomcat 实例。 className 是 Tomcat 实体类,必须使用 org.apache.catalina.Server 的子类,默认 org.apache.catalina.core.StandardServer; shutdown 表明关闭的指令; address 表明关闭指令能够的来源,默认只有本地的关闭指令 Tomcat 才会采用; port 是 Tomcat 关闭自身的指令接收端口,能够经过设置为 -1 来禁止 --> <Server port="8005" shutdown="SHUTDOWN" address="localhost" className="org.apache.catalina.core.StandardServer" > <!-- 监听器配置,监听类必须是 org.apache.catalina.LifecycleListener 的子类, 这里配置的类会根据本身监听的生命周期事件,被相关的 Lifecycle 的子类存入 list 中,当发生了相关事件就执行回调方法 --> <!-- 用于在服务启动的时候打印日志的监听器 --> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <!-- 默认开启 apr 监听器 --> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <!-- 内存不够的时候调用此监听器,执行一次 full gc --> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <!-- JNDI 全局变量管理监听器 --> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <!-- 因为线程引用了 threadLocal 中的变量致使内存不够的时候调用此监听器,杀掉线程 --> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <!-- JNDI 全局资源配置, 此处为一个 UserDatabase 对象,用来存储 tomcat-users.xml 文件中的信息 --> <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <!-- Service 表明了一个 Tomcat 服务,一个 Server 能够有多个 Service, 同一个 Server 下的 Service 的 name 必须不一样, className 必须使用 org.apache.catalina.Service 的子类,默认 org.apache.catalina.core.StandardService --> <Service name="Catalina" className="org.apache.catalina.core.StandardService"> <!-- 链接池配置,能够不作配置,会有一个默认的链接池。 namePrefix 线程名称; maxThreads 最大线程数,默认 200; maxQueueSize 任务队列最大值,默认 Integer.MAX; minSpareThreads 最小的活跃线程,默认 25; maxIdleTime 线程被销毁以前的存活时间,默认 60000 ms(1 min); deamon 池内的线程是不是守护线程,默认 true --> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="200" minSpareThreads="25" maxQueueSize="100000000" deamon="true" maxIdleTime="60000"/> <!-- Connector 是相应用户请求的主体,一种 Connector 用于表明一种用户请求, 通俗来讲 Connector 是 Container 的前置工做对象,能够通俗理解为 Request 和 Response。 port 监控的端口; protocol 使用的网络 io 链接器; connectionTimeout 链接超时时间,单位毫秒; executor 配置链接池; sslEnabled 是否容许 https; maxThreads 最大链接数; redirectPort 用户用 http 请求某个资源,而该资源自己又被设置了必需要 https 方式访问,会自动重定向到这个端口 --> <!-- protocol: org.apache.coyote.http11.Http11NioProtocol http1.1 nio 链接器 org.apache.coyote.http11.Http11Nio2Protocol http1.1 aio 链接器 org.apache.coyote.http11.Http11AprProtocol http1.1 apr 链接器,须要操做系统其它支持 HTTP/1.1 http1.1 协议的自动选择选项,有 apr 就使用 apr,没有就使用 nio AJP/1.3 ajp1.3 协议的自动选择选项,有 apr 就使用 apr,没有就使用 nio --> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" SSLEnabled="false" executor="tomcatThreadPool" maxThreads="150"> <!-- http/2 配置,让这个 Connector 能够兼顾解析 http/2 --> <!-- <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/> --> <!-- 配置 https 证书 --> <!-- <SSLHostConfig> <Certificate certificateKeyFile="conf/localhost-rsa-key.pem" certificateFile="conf/localhost-rsa-cert.pem" certificateChainFile="conf/localhost-rsa-chain.pem" type="RSA" /> </SSLHostConfig> --> </Connector> <!-- Engine 是 Servlet Container 的最高层级,一个 Service 中只能有一个 Engine。 name 名称; defaultHost 默认的 host 地址 --> <Engine name="Catalina" defaultHost="localhost"> <!-- Realm 提供用户名密码的映射,不多使用到,不作展开 --> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <!-- Host 虚拟主机,用以管理一个站点,一个站点能够有多个子项目构成。 name 名称,同一个 Engine 下的 Host 不要重复便可, className 必须是 org.apache.catalina.Engine 的子类,默认 org.apache.catalina.core.StandardEngine, appBase 站点下属全部项目配置的目录; autoDeploy 是否自动扫描 appBase 目录下的全部的项目,若是配置为 true 的话,就能够无需配置 <Context></Context> 了; unpackWars 自动解压 war 包 --> <Host name="localhost" appBase="webapps" className="org.apache.catalina.core.StandardEngine" unpackWARs="true" autoDeploy="true"> <!-- 日志格式配置 --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> <!-- Context 表明一个在 host appBase 路径下的 web 应用, 通常状况下能够不配置,由于 host 会去扫描 appBase 下的全部文件夹找 web.xml。 此处将 /webapps/example 路径下的项目配置成根路由下的项目, 将 /webapps/host-manager 路径下的项目配置成 /manager 路由下的项目,以此类推。 --> <Context docBase="examples" path="/" /> <Context docBase="host-manager" path="/manager" /> <Context docBase="ROOT" path="/root" /> </Host> </Engine> </Service> </Server>
该部分的解析使用 tomcat-embed-core 进行源码观察。spring
org.apache.catalina.Lifecycle 是一个接口,org.apache.catalina.util.LifecycleBase 实现了 Lifecycle 接口,而 Server / Service / Engine / Host / Context / Wrapper 等组件都继承了 LifecycleBase,也就是说这些组件在发生生命周期事件的时候均可以被监听器监控。apache
event 是 Lifecycle 定义的可以被监控的生命周期事件。数组
// 组件初始化即将开始 public static final String BEFORE_INIT_EVENT = "before_init"; // 组件初始化完成以后 public static final String AFTER_INIT_EVENT = "after_init"; // 组件启动中 public static final String START_EVENT = "start"; // 组件即将启动 public static final String BEFORE_START_EVENT = "before_start"; // 组件启动完成 public static final String AFTER_START_EVENT = "after_start"; // 组件中止中 public static final String STOP_EVENT = "stop"; // 组件即将中止 public static final String BEFORE_STOP_EVENT = "before_stop"; // 组件中止完成 public static final String AFTER_STOP_EVENT = "after_stop"; // 组件即将被摧毁 public static final String AFTER_DESTROY_EVENT = "after_destroy"; // 组件摧毁完成 public static final String BEFORE_DESTROY_EVENT = "before_destroy"; // 组件的周期性事件 public static final String PERIODIC_EVENT = "periodic"; // 组件开始根据配置文件配置自身,在 before_start 以后,start 以前 public static final String CONFIGURE_START_EVENT = "configure_start"; // 组件配置结束,在 stop 以后,after_stop 以前 public static final String CONFIGURE_STOP_EVENT = "configure_stop";
组件状态被定义在 org.apache.catalina.LifecycleState 中,是一个枚举类。tomcat
public enum LifecycleState { // 新建 NEW(false, null), // 正在初始化 INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT), // 初始化完成 INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT), // 准备开始组件 STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT), // 正在执行 STARTING(true, Lifecycle.START_EVENT), // 执行结束了 STARTED(true, Lifecycle.AFTER_START_EVENT), // 准备中止组件 STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT), // 正在中止中 STOPPING(false, Lifecycle.STOP_EVENT), // 中止了 STOPPED(false, Lifecycle.AFTER_STOP_EVENT), // 正在被销毁 DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT), // 被销毁以后 DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT), // 失败 FAILED(false, null); private final boolean available; // 是否可控制,若是为 false private final String lifecycleEvent; // 此状态绑定的监控事件 // ... }
代码内容很简洁。服务器
LifecycleBase 是 Lifecycle 的基本实现类,以 init() 方法举例:网络
// LifecycleBase.class @Override public final synchronized void init() throws LifecycleException { // 调用 init 方法须要确保这个组件的状态是 new,若是不是的话会抛出错误 if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { // before_init 监听通知 setStateInternal(LifecycleState.INITIALIZING, null, false); // 真正的初始化逻辑 initInternal(); // after_init 监听通知 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } }
与监听器进行交互的核心方法是 addLifecycleListener(...) 与 setStateInternal(...):session
// LifecycleBase.class @Override public void addLifecycleListener(LifecycleListener listener) { // lifecycleListeners 是一个存放监听器的列表 lifecycleListeners.add(listener); } // LifecycleBase.class // 此方法用于通知全部的监听器相关逻辑 private synchronized void setStateInternal( LifecycleState state, Object data, boolean check) throws LifecycleException { // 记录日志 if (log.isDebugEnabled()) { log.debug(sm.getString("lifecycleBase.setState", this, state)); } // 是否启用状态判断 if (check) { // 传入的状态值的非空判断,非法操做,通常不会出现 if (state == null) { invalidTransition("null"); return; } // 若是状态是以下几种的话,会抛出错误 if (!(state == LifecycleState.FAILED || (this.state == LifecycleState.STARTING_PREP && state == LifecycleState.STARTING) || (this.state == LifecycleState.STOPPING_PREP && state == LifecycleState.STOPPING) || (this.state == LifecycleState.FAILED && state == LifecycleState.STOPPING)) ) { invalidTransition(state.name()); } } // 更新状态 this.state = state; String lifecycleEvent = state.getLifecycleEvent(); if (lifecycleEvent != null) { // 此处会遍历监听器 fireLifecycleEvent(lifecycleEvent, data); } } // LifecycleBase.class protected void fireLifecycleEvent(String type, Object data) { // 遍历监听器列表,执行全部监听器的 lifecycleEvent(...) 方法 LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } }
全部实现了 org.apache.catalina.LifecycleListener 接口的类均可以是监听器:架构
public interface LifecycleListener { public void lifecycleEvent(LifecycleEvent event); }
以最简单的 org.apache.catalina.startup.VersionLoggerListener 为例子:
// VersionLoggerListener.class @Override public void lifecycleEvent(LifecycleEvent event) { // Tomcat 中的 Listener 通常都使用 if 判断进行事件的筛选 // Tomcat 没有在接口层面做出更增强制的监听逻辑判断 if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) { // 打印日志 log(); } }
Spring boot 中与此相关的配置比较多,只列举部分笔者在项目中经常使用的部分。
server: # 端口 port: 8080 # http 协议头的大小 max-http-header-size: 8KB # servlet 设置 servlet: session: # session 失效时常,默认 30min timeout: 30m # 是否持久化 session,持久化以后 session 不会由于服务的重启而丢失 persistent: false # 若是设置为须要持久化,那么能够指定存储的目录 # store-dir: classpath:session # 配置 Tomcat 相关的参数 tomcat: # 最大线程数 max-threads: 2 # 最大链接数,默认 200 max-connections: 10 # encode uri-encoding: UTF-8 # 链接超时时间 connection-timeout: 2m # 最大链接排队数 accept-count: # 提交表单的最大大小 max-http-form-post-size: 2MB # 是否支持 http2 http2.enabled: false spring: # Spring 网络配置 http: encoding: # 编码格式 charset: UTF-8 enabled: true force: true # servlet 配置 servlet: # 文件上传配置 multipart: # 是否支持文件上传 enabled: true # 上传的文件的最大值 max-file-size: 100MB # request 的最大值 max-request-size: 100MB
Spring boot 对内嵌服务器的运用,会使用 ServletWebServerFactory :
// 接口 public interface ServletWebServerFactory { WebServer getWebServer(ServletContextInitializer... initializers); }
它的具体实现类有:
JettyServletWebServerFactory - eclipse jetty 内嵌服务对象的工厂类 TomcatServletWebServerFactory - apache tomcat 内嵌服务对象的工厂类 UndertowServletWebServerFactory - jboss undertow 内嵌服务对象的工厂类
本例中因为默认内嵌 Tomcat 服务包,因此 Spring boot 会使用 TomcatServletWebServerFactory:
// step 1 // TomcatServletWebServerFactory.class @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); // Tomcat 配置 baseDir,若是不配置的话会建立一个临时目录,用来存放一些 log 文件等临时文件 tomcat.setBaseDir(baseDir.getAbsolutePath()); // 建立 Connector,并存入 protocol Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); // 在 Tomcat 的 service 中存入 connector,因为是内嵌的 Tomcat,因此 service 只容许有一个 tomcat.getService().addConnector(connector); // 根据配置对 connector 进行配置 customizeConnector(connector); // 本质上这行代码和上述 tomcat.getService().addConnector(connector) 的功能是同样的,不太理解为何又从新 set 了一遍 tomcat.setConnector(connector); // 关闭 host 的自动扫描 tomcat.getHost().setAutoDeploy(false); // 配置 engine configureEngine(tomcat.getEngine()); // 添加一些默认的 connector,通常是空的 for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } // ServletContextInitializer 用于封装 servlet 的配置信息,并将 servlet 注册到 context 上 // 此处会建立一个 context 对象,并注册到 host 中 // 因为 spring mvc 内部实际上只有一个 dispatcher servlet,因此此处的数组通常只有一个 prepareContext(tomcat.getHost(), initializers); // 用一个 TomcatWebServer 对象包装原生的 Tomcat 对象 return getTomcatWebServer(tomcat); } // step 2 // TomcatServletWebServerFactory.class protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); } // step 3 // TomcatWebServer.class public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } // step 4 // TomcatWebServer.class private void initialize() throws WebServerException { // 记录日志 logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); // 给 context 增长生命周期监听器 Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { removeServiceConnectors(); } }); // 此处启动 tomcat this.tomcat.start(); // 此处检测一下是否启动成功,若是失败了就直接抛出错误 rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader( context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } // 启动一条主线程 // 因为 Tomcat 的全部现场都默认是守护线程,因此须要一条非守护线程来确保项目不退出 startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }