由于工作需要开始阅读tomcat的源码。目前分析tomcat的文章和书籍很多,但是有的tomcat版本较低,因此自己动手实践了一遍。本文主要介绍tomcat的启动过程。
1、源码下载和导入
从tomcat官网下载tomcat7的源码,由于tomcat7的源码是通过ant管理的,先安装ant,然后在tomcat源码目录下执行ant ide-eclipse,该命令会在tomcat7目录下创建.project和.classpath两个文件,执行完成后,就可以将源码导入eclipse了。
这个过程遇到的问题:
1) 由于tomcat7支持的jdk版本是1.6,本机环境1.8,导致执行ide-eclipse命令失败。下载jdk1.6,修改java home路径,重新执行就成功了。
2) 导入之后的编译错误。
2、tomcat的核心类
Tomcat启动时会初始化并启动多个组件service,connector,container等。从UML类图可以看出,Server、Service、Connector、Container、Engine、Host、Context和Wrapper这些核心组件的作用范围是逐层递减,并逐层包含。最上层是Server,一个Server可以包含一个或多个Service,Service是具体提供服务的地方,Service包含了Connector(连接器)和Container(容器),其中Connector处理客户端的连接请求,Container则负责对这些请求进行处理,并生成响应,在Tomcat中Container有几个层次,最上层是Engine,代表一个servlet引擎,接下来是Host,代表一个虚拟机,然后是Context,代表一个应用, Wrapper已经对应了一个个的Servlet了。
容器Engine和Host不是必须的。实际上一个简单的tomcat只要connector和Container就可以了,但tomcat为了统一管理connector和Container等组件,额外添加了服务器组件(server)和服务组件(service)。
3、tomcat启动过程
由于tomca组件之间的包含关系,一个组件中可以包含多个子组件,且这些组件都实现了Lifecycle接口。因此只要启动最上层的server组件,它包含的所有组件也会跟着启动了,例如启动server,所有子service都会跟着启动,service启动了,它的container和connector等也会跟着启动。因此,tomat要启动,只需要启动server就可以了。
tomcat启动的入口是Bootstrap的main方法,主要做了两件事情,
1、 初始化tomcat的类加载器
2、 加载组件,启动组件。这两个操作都是通过反射的方式调用Catalina中的同名方法来处理的,是tomcat服务启动的重点。
public static void main(String args[]) {
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap();//实例化该类的一个实例
try {
bootstrap.init();//初始化类加载器 commonLoader、catalinaLoader、sharedLoader
} catch (Throwable t) {
...
}
daemon = bootstrap;
}
//...
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
//...
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);//执行load,生成组件实例并初始化
daemon.start();//启动各个组件
}
//...
}
从上面代码可以看到,tomcat启动时执行了3个关键方法,init、load、和start。load用于实例化组件,start启动服务。分别调用Catalina的load方法和start方法完成。
然后我们进入Catalina的load方法:
public void load() {
//...
//初始化Digester组件,定义了解析规则
Digester digester = createStartDigester();
//...
File file = null;
try {
file = configFile();//server.xml文件
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
//...
}
//...
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);//通过Digester解析这个文件,在此过程中会初始化各个组件实例及其依赖关系
} catch (Exception e) {
//...
}
//...
try {
getServer().init();//调用Server的init方法,初始化各个组件
} catch (LifecycleException e) {
//...
}
}
在上面的代码中,关键的任务有两个,
1、
使用Digester组件按照给定的规则解析server.xml。
2、
调用Server的init方法。Digester组件的使用,后续会继续研究,而Server的init方法中,会调用各个Service的实现类StandardService中的initInternal方法,这个方法中又会调用Container和Connector中的init方法,从而级联完成各个组件的初始化。我们在这里重点关注connector的初始化。
protected void initInternal() throws LifecycleException {
super.initInternal();
// 该适配器会完成请求的真正处理
adapter = new CoyoteAdapter(this);
//对于不同的实现,会有不同的ProtocolHandler实现类
protocolHandler.setAdapter(adapter);
//...
try {
protocolHandler.init();
} catch (Exception e) {
//...
}
}
在AbstractProtocol的init方法中,核心代码如下:
public void init() throws Exception {
//...
String endpointName = getName();
//endpoint为AbstractEndpoint的实现类
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
try {
endpoint.init();//核心代码就是调用 AbstractEndpoint的初始化方法
} catch (Exception ex) {
//...
}
}
我们看到初始化方法都会调到AbstractEndpoint的init方法,此方法中的bind()会调用具体的实体类中的bind方法,完成最终的初始化操作。
public final void init() throws Exception {
testServerCipherSuitesOrderSupport();
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
我们接下来看JIoEndpoint中的bind方法,该方法主要完成初始化serverSocket,让服务器监听tomcat的端口:
public void bind() throws Exception {
if (acceptorThreadCount == 0) {//接受请求的线程数
acceptorThreadCount = 1;
}
//...
if (serverSocket == null) {
try {
if (getAddress() == null) {
//创建一个ServerSocket对象,监听特定端口,准备接受请求
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog());
} else {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog(), getAddress());
}
} catch (BindException orig) {
//...
}
}
}
从以上代码中,可以看出bind方法通过ServerSocketFactory工厂,创建了一个ServerSocket对象。用于接收客户端请求。
组件初始化完成之后,就可以通过Catalina的start方法启动服务了。
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
//...
try {
getServer().start();
} catch (LifecycleException e) {
//...
}
//...
}
此时会调用StandardServer的start方法启动服务,此方法中又会调用StandardService中的startInternal方法启动service,service中启动Container和Connector。在这里我们还是重点关注Connector
组件的startInternal方法,此方法中通过protocolHandler调用Endpoint的
startInternal方法。在此我们以
JIoEndpoint的startInternal为例。
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// 初始化处理连接的线程
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
//初始化接受请求的线程
startAcceptorThreads();
//...
}
以上代码主要有两个关键的方法createExecutor()和startAcceptorThreads()。createExecutor主要是初始化处理连接的线程池,待执行的任务存放TaskQueue中,用TaskQueue的对象来初始化线程池。我们接下来看startAcceptorThreads方法:
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
从这个方法可以看出,Acceptor才是真正接受请求的对象,用于接受请求并分派给Processor处理。
public void run() {
int errorDelay = 0;
while (running) {
//...
try {
//...
Socket socket = null;
try {
// 接受发送过来的请求
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
//...
}
errorDelay = 0;
if (running && !paused && setSocketOptions(socket)) {
//处理这个请求
if (!processSocket(socket)) {
countDownConnection();
closeSocket(socket);//关闭连接
}
}//...
} catch (IOException x) {
//...
}
}
//...
}
从这里我们可以看到,Acceptor接受Socket请求,并调用processSocket方法,此方法中将请求socket包装,然后交由SocketProcessor处理。
至此,tomcat启动完毕,等待客户端请求的到来。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者何欢授权发布。