tomcat 4.1.30启动过程的源码分析

时间:2022-05-12 16:49:26

tomcat 4.1.30启动过程的源码分析
                                      


前几天为了解决sinpool兄的《多线程的问题。》一帖,专门看了一下tomcat 4.1.30的源码,
其中重点研究了tomcat的启动这一部分,个人感觉tomcat的源码还是写的很清楚易懂,值得一看。
(以前看过struts的部分代码,感觉也比较经典)
然后我看后的代码整理了一下,附在下面,希望对其他人有用,也希望感兴趣的兄弟可以多看看好的代码,
肯定对自己的程序设计和代码质量颇有益处。

一. 启动类(包含main()方法的类):
org.apache.catalina.startup.Bootstrap
这个类是tomcat的启动类,主要按照如下步骤,进行主要的启动工作:
1. 创建3个ClassLoader:common,catalina和share,它们对应tomcat的3个Classloader,我想对tomcat
的classloader有研究的兄弟对这个肯定不陌生,其中common classloader是紧跟在系统的classloader(也就是
系统环境变量中设置的CLASSPATH所对应的classloader),而catalina classloader是common的子classloader,是tomcat
运行所需要的类的classloader,而shared classloader也是common的子classloader,是和catalina平级的classloader,
之所以说是shared classloader,是因为它是所有tomcat下面发布的webapp classloader(每一个web app都有一个自己的classloader)
的父classloader。它们这3个classloader分别读取tomcat home下面的common, server和shared三个目录里面的classes和lib目录,
用于初始化自己所控制的类库和资源。

2. 创建启动一个的org.apache.catalina.startup.Catalina类的实例,并调用它的process方法,这里使用的是java的reflection技术。
然后调用这个实例的process方法,并把Bootstrap接受到的命令行参数传递进去了,这里Bootstrap类并没有解析传给它的命令行参数。
当然在调用process之前还使用setParentClassLoader方法设置了一下父classloader。这里简单介绍一下有关classloader的一个重要
特性,就是如果classloader要load一个类时,不是自己先找,而是先把这个任务委派给自己的父classloader,然后自己的父classloader
也不找,在把这个任务委派给自己的父classloader,直到找到最顶层的classloader,然后再自顶向下的找对应的这个要load的类的定义,
如果那个classloader先找到,就返回。所以接合上面第一点介绍的tomcat中3个classloader,大家就可以明白tomca的classloader找类
的顺序了,这个对程序开发人员来说特别重要。我想使用过tomcat或者其他app server的兄弟肯定碰到过一个类明明存在可就是找不到,
或者总是找到一个老的版本,我想主要是在多个地方放置的原因,或者哪里有重名的类:-)

二.org.apache.catalina.startup.Catalina类
现在程序转到org.apache.catalina.startup.Catalina类里面的process方法。
这个方法首先设置一下catalina的home和base目录,然后通过arguments方法解析命令行参数,
最后调用execute()方法启动server。而execute方法很简单,就是根据arguments解析的命令行参数,
决定是启动server,还是stop server,如果是start server,就调用start方法,而下面重点讲一下这个start()方法,
因为才算是一个真正开始的启动tomcat的地方:-)
1. start方法首先使用Digester(这个东东是jakarta commons里面的一个用于解析xml文件的工具包,一开始是专门用于解析struts配置文件的,
后来被抽象成现在的一个通用工具,主要还是用来解析xml配置文件,根据一些定义的rule自动生成对应的类的实例,具体信息可以参考
apache网站上的文档)来设置tomcat配置文件,也就是/conf/server.xml这个文件的解析规则
然后通过如下代码来将配置文件中的数据转化成内存中的实例:

代码:
   File file = configFile();

        try {

            InputSource is =

                new InputSource("file://" + file.getAbsolutePath());

            FileInputStream fis = new FileInputStream(file);

            is.setByteStream(fis);

            digester.push(this);

            digester.parse(is);

            fis.close();

        } catch (Exception e) {

            System.out.println("Catalina.start: " + e);

            e.printStackTrace(System.out);

            System.exit(1);

        }   
转换的规则如下(我只作一些简单的介绍),例如配置文件中的
a. Server对应可以产成一个org.apache.catalina.core.StandardServer类(这个类很重要,是tomcat server的实现)
b. Server/GlobalNamingResources对应生成org.apache.catalina.deploy.NamingResources类
而大家比较熟悉的监听8080端口的类配置如下:
c. Server/Service/Connector:org.apache.catalina.connector.http.HttpConnector
d. Server/Service/Engine/Host/Context/:org.apache.catalina.core.StandardContext
有兴趣的兄弟可以参考jakarta commons里面的Digester文档和org.apache.catalina.startup.Catalina
这个类里面的createStartDigester方法.
在这段代码之后,一个叫server的变量已经通过Digester工具生成了,它将会用于启动tomcat。
2. 然后程序进行了一些server启动前的设置工作,例如重定向log输出流等等。而server启动的代码如下:

代码:
// Start the new server

        if (server instanceof Lifecycle) {

            try {

                server.initialize();

                ((Lifecycle) server).start();

                try {

                    // Register shutdown hook

                    Runtime.getRuntime().addShutdownHook(shutdownHook);

                } catch (Throwable t) {

                    // This will fail on JDK 1.2. Ignoring, as Tomcat can run

                    // fine without the shutdown hook.

                }

                // Wait for the server to be told to shut down

                server.await();

            } catch (LifecycleException e) {

                System.out.println("Catalina.start: " + e);

                e.printStackTrace(System.out);

                if (e.getThrowable() != null) {

                    System.out.println("----- Root Cause -----");

                    e.getThrowable().printStackTrace(System.out);

                }

            }

        }
其中server这个变量就是在刚才Digester解析时创建好的。
(当时这个地方我看了很长时间,后来才发现是这样的,因为以前不太了解Digester这个东东)。
然后大家可以看到server启动主要是分3步:
1. initialize方法进行server启动的初始化操作
2. start方法启动server,主要是server中的的service和service中的connector
3. await方法等待server shutdown
其中我重点给大家介绍一下initialize方法和start方法
initialize方法:
这里面只有一个主要任务,就是逐次调用server中所有定义的service的initialize方法,
而每个service的initialize方法中调用这个service中定义的所有connector的initialize方法,
而connector的initialize方法则是创建一个serversocket用于接受客户端的请求就结束了。
如果大家看一下tomcat下面conf/server.xml,就可以发现,tomcat默认只定义了一个service叫做Tomcat-Standalone,
而下面只有默认定义了3个connector:
1. 8080端口的http connector
2. 8443端口的http ssl connector
3. 8009端口的Coyote/JK2 AJP 1.3 Connector
我想大家对这3个端口一定不陌生吧。
start方法:
这个方法里面有一个tomcat很重要,也是我认为tomcat设计对一个亮点,就是Lifecycle这个东东,它很象一个bus(总线)。
我想大家进行过程序设计的一定知道,开始设计的时候总要根据一个原则分出几个模块来,是为了代码分块,或者将
一部分功能相似的代码组织成一个模块,这样比较清楚,例如一个进销存系统会有采购,销售,库存和财务等模块,但是
我想很多人也碰到过这样的情况就是虽然分了模块但是如果在开发完毕以后,另外一个客户说只想要其中的销售模块,我想
大部分的开发人员肯定傻眼,因为虽然当时设计的时候分了模块,但是这些模块编写的时候却是交织在一起,互相的接口定义
很模糊,基本上都是直接调用另一个模块的方法,这样肯定分不开。而tomcat的这个Lifecycle的设计理念就可以解决这个问题的
一部分,它的原理就象是一个bus(总线),例如一个模块做完一个动作以后,例如销售模块创建好一个订单后,本来要直接调用
库存模块的api锁住一部分库存(我只是随便举个例子,实际业务不一定是这样),这样销售模块就需要依赖库存模块了。但是使用了
bus方式。我们就可以在订单创建后,向bus上发送一个订单创建的消息,而总线有一个事件注册机制,有点象swing的event,listener,
例如库存模块有一个listener专门用于监听订单创建的消息,进行处理,这样2个模块就互不依赖了。有兴趣的兄喜可以看看jcp上面
的一个叫做infobus的专题。
当然这个方式只是解决有效降低模块偶合度的一个方面(因为有的时候必须要直接调用另外一个模块的接口,
例如库存模块一定要直接缺德一个销售订单的信息,那么就需要定义一个接口类来描述订单的详细信息啦,这里就不具体解释了,
有空可以专门发个帖子跟大家探讨这个问题:-) ),就是不要显式触发另一个模块的某个动作,而是通过bus机制来发送消息,
而每个模块都有一个自己的handler,会监听bus,对自感兴趣的事件进行处理。tomcat的Lifecycle就是这个东西。
下面再回到start方法:
1. 它首先向总线发送了2个事件:BEFORE_START_EVENT和START_EVENT
2. 然后调用每个service的start方法,最后发送AFTER_START_EVENT消息通知其他程序
而service的start方法主要进行的动作如下:
1. 发送BEFORE_START_EVENT消息
2. 调用container的start方法
3. 然后调用connector的start方法
4. 最后发送AFTER_START_EVENT消息.
而connector的start方法就是大家最熟悉的socket编程了,大家可以参看org.apache.catalina.connector.http.HttpConnector这个类,
主要是使用java里面的多线程操作,初始化一个HttpProcessor的线程池,然后通过wait方法阻塞住每个HttpProcessor线程,只有
当接受到一个http请求时,在通过notify方法激活HttpProcessor线程,让其处理用户的http请求。