Tomcat容器的Server模块有管理容器的启动和关闭、管理了容器内的服务组件Service、管理了全局JNDI资源的功能,对Tomcat容器的生命周期管理有重要意义。Tomcat的服务组件则是Tomcat的两个核心组件连接器和servlet容器之间的桥梁。本文会对Tomcat容器的服务器组件Server和服务组件Service进行介绍。
服务器组件Server
我们知道Tomcat容器启动之后就可以一直保持服务,即使请求出现异常也不会退出,只有在收到特定的容器关闭命令时才会退出。Tomcat容器是怎么实现容器的启动?启动之后是如何保证容器一直保持运行?在收到容器关闭命令的时候怎么优雅关闭的呢?这就是Tomcat容器中的Server的功能了。
每个Tomcat容器都会唯一包含一个Server组件,对应于Tomcat安装文件夹下面的server.xml。下面为Tomcat10安装包中conf/server.xml的默认配置。分析xml可知,server节点有 port和shutdown属性,包含Listener、GlobalNamintResources和Service三部分子节点。下文我们会分别对这些内容进行介绍。
Server的启动/停止
通过上文可以知道,Server组件重要的就是控制Tomcat容器的启动/停止,然而启动停止并不是简单的启动JVM关闭JVM就可以了,Tomcat容器启动/停止是还必须调用容器内所有组件的生命周期方法,启动时需要所有的组件进行初始化,结束时需要所有的组件进行销毁和资源释放。
JVM的启动/停止
JVM的启动比较简单,我们在运行tomcat启动脚本的时候,会启动tomcat的Jar文件,从而启动JVM。
关于JVM的退出则稍微复杂一些,JVM退出的方式分为以下三种类型:
- 正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
- 异常关闭:运行中遇到RuntimeException异常等。
- 强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
对于正常关闭和异常关闭,JVM都有机会执行关闭的Hook方法,对于强制关闭则不一定会执行关闭时的hook方法。所以我们在日常使用中应该尽量避免使用kill -9等方法退出JVM。
JVM注册Shutdown Hook的方法如下所示:
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
//
}
});
Tomcat容器启动的时候会通过Runtime.getRuntime().addShutdownHook(Runnable run)
方法向JVM注册关闭回调方法CatalinaShutdownHook,从而实现容器的优雅关闭。
Tomcat关闭接口
我们上面讲了JVM退出的情况下Tomcat怎么实现优雅的关闭,Tomcat也可以主动关闭程序,我们在配置server.xml文件的时候,会指定server的port和shutdown指令,在需要关闭Tomcat容器的时候,我们只需要向指定端口发送关闭指令,Tomcat就会主动退出服务。
生命周期的控制
Tomcat中需要实现声明周期管理的组件都会实现Lifecycle接口。通过上文我们知道Tomcat的启动/停止是由Server控制的,那么Server是如何通知容器内的其它组件(如container、connector)启动/停止相关事件的呢?我们先看看Tomcat的结构图,我们可以看到Tomcat容器的组件之间是一层层包含关系,一个Server包含多个Service,一个Service包含多个Container等等。
Tomcat容器在关闭的时候会通知所有的子组件(service组件)容器关闭事件,service组件再通知它的所有子组件容器关闭事件。事件通过父子关系层层传递到各个组件,从而实现组件之间的生命周期管理。
事实上Tomcat容器的生命周期事件不仅仅包含启动/关闭,而是更详细的划分了启动关闭的各个阶段,分为以下代码示例中的各个事件。
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";
public static final String CONFIGURE_START_EVENT = "configure_start";
public static final String CONFIGURE_STOP_EVENT = "configure_stop";
Service服务组件
Server中Service的配置如下所示,Service组件包含两种组件:连接器和Servlet容器,其中servlet容器只有一个,连接器可以由多个。多个连接器可以使Tomcat为多种不同的请求协议提供服务,比如一个处理HTTP请求,另外一个处理HTTPS请求。
连接器负责将Socket请求解析为Request和Response,而Servlet容器则负责根据业务逻辑处理请求中的Request和Response,Service服务组件则负责把二者关联起来。我会在其它文章中详细介绍Servlet容器和连接器Connector。
每个连接器组件Connector都可以指定一个Servlet容器处理其解析得到的Request和Response,所以Service的功能比较简单,就是为Service中的每个组件设置Servlet容器。
Server的其它配置
全局资源GlobalNamingResources
提供了容器级别的JNDI资源配置。比如下面的默认配置,就提供了Tomcat用户数据的JNDI,存储在conf/tomcat-users.xml中。容器资源对容器的依赖性比较高,现在的使用场景比较少。
监听器Listener
监听器用来监听容器的特定事件,如容器的启动关闭事件等。如下所示,默认的server.xml中包含了5个监听器,我们接下来会简单介绍默认监听器的功能。
- VersionLoggerListener:在容器启动前打印各种版本信息,如JVM版本、操作系统版本、tomcat版本等信息。
- AprLifecycleListener:APR的生命周期处理,APR(Apache portable Run-time libraries,Apache可移植运行库)的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。
- JreMemoryLeakPreventionListener:用于处理上下文类加载器可能出现的内存泄露问题,启动Java内存自动回收任务,每小时触发FullGC。
- GlobalResourcesLifecycleListener:Tomcat启动时实例化JNDI资源的MBean,Tomcat停止时销毁MBean.
- ThreadLocalLeakPreventionListener:在Context关闭的时候清空线程上下文,防止ThreadLocal内存泄露。
我是御狐神,欢迎大家关注我的微信公众号:wzm2zsd
本文最先发布至微信公众号,版权所有,禁止转载!