背景apache
Tomcat 是一款开源轻量级 Web 应用服务器,是一款优秀的 Servlet 容器实现。无论在以前War包打天下,仍是如今的分布式、集群、虚拟容器化等至关成熟的阶段,主流的JAVA服务容器领域,它的地位都不可轻易被替代。对Tomcat停留在使用层面,不免在服务优化和配置的选型等方面存在误区和遇到一些坑,那么对其源码和设计思路的了解就至关有必要了。编程
Tomcat的源码有不少,今天咱们就来重点分析一下启动流程设计、生命周期组件、容器及管道机制。选用的源码版本为目前的主流稳定版8.5;bootstrap
启动架构设计设计模式
咱们在启动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);
}
}
1、启动设计之初始化部分
并发
跟进源码进入到Tomcat的主服务Catalina类,load()方法app
经过生命周期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()方法内;
2、启动设计之启动部分
跟进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源码中精妙之处远不止这种优秀的总体设计与机制(固然学习优秀的源码,我认为主要是学习它的总体设计思路)。但不少实现的环节也用到并发编程的相关技术,我认为一样也可圈可点,如线程池、异步任务、可重入锁、读写锁、阻塞队列、写时复制容器、原子类等,固然也综合运用了其余的设计模式如访问者、适配器等。推荐你们有时间可多读一下!