最近面试一些1~3年的开发人员,问他们tomcat的基础架构发现都是一脸懵逼,这里开贴大概写一下tomcat的整体架构以及启动流程,版本基于7.0.85,后续会不断对文章进行更新。
一、tomcat基础架构
上个图,这张图算是说的比较明白的。
大家可以去翻一下server.xml,精简后会发现如下结构:
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost">
<Context path="" docBase="WORKDIR" reloadable="true"/>
</Host>
</Engine>
</Service>
</Server>
这个配置文件是tomcat在启动时的核心配置,后面会用到。下面简单说下各个组件的作用:
1.Server
Server表示整个的Catalina Servlet容器。Tomcat提供了Server接口的一个默认实现,这通常不需要用户自己去实现。在Server容器中,可以包含一个或多个Service组件。
2.Service
Service是存活在Server内部的中间组件,它将一个或多个连接器(Connector)组件绑定到一个单独的引擎(Engine)上。在Server中,可以包含一个或多个Service组件。Service也很少由用户定制,Tomcat提供了Service接口的默认实现,而这种实现既简单又能满足应用。
3.Connector
连接器(Connector)处理与客户端的通信,它负责接收客户请求,以及向客户返回响应结果。在Tomcat中,有多个连接器可以使用。
4.Engine
在Tomcat中,每个Service只能包含一个Servlet引擎(Engine)。引擎表示一个特定的Service的请求处理流水线。作为一个Service可以有多个连接器,引擎从连接器接收和处理所有的请求,将响应返回给适合的连接器,通过连接器传输给用户。用户允许通过实现Engine接口提供自定义的引擎,但通常不需要这么做。
5.Host
Host表示一个虚拟主机,一个引擎可以包含多个Host。用户通常不需要创建自定义的Host,因为Tomcat给出的Host接口的实现(类StandardHost)提供了重要的附加功能。
6.Context
一个Context表示了一个Web应用程序,运行在特定的虚拟主机中。什么是Web应用程序呢?在Sun公司发布的JavaServlet规范中,对Web应用程序做出了如下的定义:“一个Web应用程序是由一组Servlet、HTML页面、类,以及其他的资源组成的运行在Web服务器上的完整的应用程序。它可以在多个供应商提供的实现了Servlet规范的Web容器中运行”。一个Host可以包含多个Context(代表Web应用程序),每一个Context都有一个唯一的路径。用户通常不需要创建自定义的Context,因为Tomcat给出的Context接口的实现(类StandardContext)提供了重要的附加功能。
这部分参考原文https://blog.youkuaiyun.com/sundacheng1989/article/details/79064112 ,原文还介绍了一下tomcat的启动脚本,感兴趣的同学可以稍微看一下,需要注意的是首先bootstrap.jar 和 tomcat-juli.jar要添加到classpath下,剩下的就是执行Bootstrap类中的main方法。所以所谓的tomcat容器,其实就是一个普通的java程序。只不过tomcat写了一个await方法,让这个java程序可以一直执行,直到收到shutdow通知,或者进程被kill。
二、类图分析
如何编译tomcat源码,参考文末链接。不复杂,如果你熟悉ant编译的话会更简单。这里先上一张启动过程中要涉及的几个类
1.Lifecycle
tomcat中核心组件都实现了该接口,该接口的作用跟它的名字一样,定义了tomcat容器各组件的生命周期。在源码文件的类注释上详细描述了tomcat容器各个状态以及转换方式:
Lifecycle类中只定义了几个管理生命周期的基本方法:init,start,stop,destroy,getState,getStateName,另外还提供了三个跟生命周期Listener相关的方法,暂时不看。
2.LifecycleMBeanBase
tomcat把自己管理的bean统一命名为MBean,LifecycleMBeanBase通过实现MBeanRegistration的方式来把自己变成一个可被tomcat管理的bean,同时继承LifecycleBase。LifecycleBase是Lifecycle接口的一个比较重要的实现,实现了Lifecycle接口中对生命周期的管理方法。
@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
}
protected abstract void initInternal() throws LifecycleException;
init方法的代码不复杂,阅读可知其主要是负责各个组件的状态装换,至于组件初始化的具体细节,通过initInternal方法交由各个组件自己实现。大家可以看一下其他几个方法例如start,destroy等,基本上都是同一个设计思路。
3.Server,Servie,Container
这三个接口均直接实现了Lifecycle接口。其中Server,Service前面已经介绍过了,与配置文件对应。这里简单说下Container。
tomcat核心组件由六部分组成,Server,Service有独立接口,Connector直接是实现类,剩下的三个组件,即Service管理的Engine,Host,Context均实现Container接口,Container接口要对外提供能够接受请求并且返回响应,简单来说,这三个组件构成了tomcat的核心servlet处理引擎,因为tomcat本质上就是一个web容器,其中Server,Service,Connector都是tomcat特有的概念,而Container下面的三个概念,是所有支持servlet模型的web容器所都要实现的功能,主要提供对servlet生命周期的管理功能。
三、启动流程
1、Bootstrap
分析catalina.sh可知tomcat的启动入口是Bootstrap类的main方法:
public static void main(String args[]) {
//是否已经启动
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
//初始化catalina
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
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);
daemon.load(args);
daemon.start();
} 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);
}
}
main方法主要负责两件事:1.初始化catalina,2.通过反射调用catalina的方法。简单看下Bootstrap的init方法:
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
通过反射获取Catalina类,然后反射注入类加载器,main方法中后续对daemon对象调用的start,stop方法,其实都是通过反射调用Catalina类的相应方法:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
所以我们接下来的重点要关注Catalina类。
2、Catalina
先看start方法:
public void start() {
if (getServer() == null) {
//初始化tomcat
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
/