Tomcat源码分析-启动过程

由于工作需要开始阅读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启动时会初始化并启动多个组件serviceconnectorcontainer等。从UML类图可以看出,ServerServiceConnectorContainerEngineHostContextWrapper这些核心组件的作用范围是逐层递减,并逐层包含。最上层是Server,一个Server可以包含一个或多个ServiceService是具体提供服务的地方,Service包含了Connector(连接器)Container(容器),其中Connector处理客户端的连接请求,Container则负责对这些请求进行处理,并生成响应,在TomcatContainer有几个层次,最上层是Engine代表一个servlet引擎,接下来是Host代表一个虚拟机然后是Context,代表一个应用, Wrapper已经对应了一个个的Servlet了。

容器EngineHost不是必须的。实际上一个简单的tomcat只要connectorContainer就可以了,但tomcat为了统一管理connectorContainer等组件,额外添加了服务器组件(server)和服务组件(service)。


3、tomcat启动过程

由于tomca组件之间的包含关系,一个组件中可以包含多个子组件,且这些组件都实现了Lifecycle接口。因此只要启动最上层的server组件,它包含的所有组件也会跟着启动了,例如启动server所有子service都会跟着启动service启动了,它的containerconnector等也会跟着启动。因此,tomat要启动,只需要启动server就可以了。

tomcat启动的入口是Bootstrapmain方法,主要做了两件事情,

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个关键方法,initload、和startload用于实例化组件,start启动服务。分别调用Catalinaload方法和start方法完成。

然后我们进入Catalinaload方法:

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、 调用Serverinit方法。Digester组件的使用,后续会继续研究,而Serverinit方法中,会调用各个Service的实现类StandardService中的initInternal方法,这个方法中又会调用ContainerConnector中的init方法,从而级联完成各个组件的初始化。我们在这里重点关注connector的初始化。

protected void initInternal() throws LifecycleException {
        super.initInternal();
        // 该适配器会完成请求的真正处理
        adapter = new CoyoteAdapter(this);
        //对于不同的实现,会有不同的ProtocolHandler实现类
        protocolHandler.setAdapter(adapter);
       //...
        try {
            protocolHandler.init();
        } catch (Exception e) {
            //...
        }
}

AbstractProtocolinit方法中,核心代码如下:

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) {
            //...
        }
}

我们看到初始化方法都会调到AbstractEndpointinit方法,此方法中的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对象。用于接收客户端请求。

组件初始化完成之后,就可以通过Catalinastart方法启动服务了。

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) {
            //...
        }
        //...
}

此时会调用StandardServerstart方法启动服务,此方法中又会调用StandardService中的startInternal方法启动serviceservice中启动ContainerConnector。在这里我们还是重点关注Connector组件的startInternal方法,此方法中通过protocolHandler调用EndpointstartInternal方法。在此我们以JIoEndpointstartInternal为例。

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

本文来自网易实践者社区,经作者何欢授权发布。