背景
Tomcat 是一款开源轻量级 Web 应用服务器,是一款优秀的 Servlet 容器实现。不管在之前War包打天下,还是现在的分布式、集群、虚拟容器化等相当成熟的阶段,主流的JAVA服务容器领域,它的地位都不可轻易被替代。对Tomcat停留在使用层面,难免在服务优化和配置的选型等方面存在误区和遇到一些坑,那么对其源码和设计思路的了解就相当有必要了。
Tomcat的源码有很多,今天我们就来重点分析一下启动流程设计、生命周期组件、容器及管道机制。选用的源码版本为目前的主流稳定版8.5;
启动架构设计
我们在启动Tomcat服务的时候,通常去运行的是安装目录的bin路径下startup(.sh/bat) 脚本,其实打开它的源码就会看到里面会调用catalina(.sh/.bat)这个启动脚本,而这个脚本最终运行到Tomcat启动类Bootstrap.class。在Bootstrap中首先会进行类加载器定义和类加载(下次详细介绍),然后进行server.xml解析、进而初始化各个部件,最后才是顺序运行各个部件、后台任务处理线程和事件监听等。
Bootstrap启动类入口
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
/**
* 定义初始化各个类加载器、进行类加载
*/
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
/**
* Tomcat线程上下文,默认的类加载器
*/
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
/**
* server.xml配置解析和各个部件初始化的入口
*/
daemon.load(args);
/**
* 各个部件运行、后台任务处理及事件监听的入口
*/
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
一、启动设计之初始化部分
跟进源码进入到Tomcat的主服务Catalina类,load()方法
通过生命周期initInternal()方法进入Server部件实现类StandardServer的initInternal()方法内;
通过生命周期initInternal()方法进入Service部件实现类StandardService的initInternal()方法内;
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
/**
* 这里初始化服务的引擎部件
*/
if (engine != null) {
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
/**
* 这里初始化各个事件监听器
*/
mapperListener.init();
/**
* 这里初始化各个配置的连接器
*/
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
通过生命周期initInternal()方法进入Engine部件实现类StandardEngine的initInternal()方法内;
二、启动设计之启动部分
跟进Bootstrap启动类main()方法中的deamon.start()方法;
跟进上面的反射start()方法,进入Catalina主服务类的start()方法;
通过生命周期startInternal()方法进入Server部件实现类StandardServer的startInternal()方法内;
@Override
protected void startInternal() throws LifecycleException {
/**
* 流转和启动事件监听
*/
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
/**
* 遍历启动server.xml中注册的各个服务
*/
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
通过生命周期startInternal()方法进入Service部件实现类StandardService的startInternal()方法内;
通过生命周期startInternal()方法进入Engine部件实现类StandardEngine的startInternal()方法内;
生命周期管理设计
Tomcat通过生命周期组件,对其中运行的所有部件(包括容器本身)和机制的管理。通过底层的Lifecycle约定,抽象出生命周期业务处理骨架LifecycleBase,然后通过它对所有部件在生命周期内各个阶段进行总体业务流程控制设计。LifecycleBase本身就设计模式而言采用了模板方法设计模式,统一抽象出业务处理的骨架,然后把部分变化的步骤延迟到子类中实现的这种方式。
部件与生命周期的关系
所有的业务部件在各个阶段的动作都需要通过Lifecycle进行总体设计,而部件实现本身只处理和自己密切相关的定制业务。这种解耦化的设计让Tomcat核心运作流程更加灵活、插拔度更好、更易于维护和扩展出各个版本的新功能。
容器设计
Servlet容器作为Tomcat业务的核心的部分,它包括了Engine、Host(域相关配置)、Context和Servlet Wrapper包装等主要部件。这些部件被Container统一设计、然后通过ContainerBase统一抽象出容器管理骨架。
管道机制
在一个比较复杂的大型系统中,如果一个对象或数据流需要进行繁杂的逻辑处理,我们可以选择在一个大的组件中直接处理这些繁杂的业务逻辑, 这个方式虽然达到目的,但扩展性和可重用性较差, 因为可能牵一发而动全身。更好的解决方案是采用管道机制,用一条管道把多个对象(阀门部件)连接起来,整体看起来就像若干个阀门嵌套在管道中一样,而处理逻辑放在阀门上。
阀门(Valve)在管道中运行流程
Tomcat中的管道设计
管道的实现采用了经典的责任链设计模式,并且作为Servlet容器设计的一部分。
总结
Tomcat源码中精妙之处远不止这种优秀的整体设计与机制(当然学习优秀的源码,我认为主要是学习它的整体设计思路)。但很多实现的环节也用到并发编程的相关技术,我认为同样也可圈可点,如线程池、异步任务、可重入锁、读写锁、阻塞队列、写时复制容器、原子类等,当然也综合运用了其他的设计模式如访问者、适配器等。推荐大家有时间可多读一下!