`
jarfield
  • 浏览: 200578 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Tomcat 5.5.26源代码分析——启动过程(二)

阅读更多
  1. init方法
    1. Catalina_Home和Catalina_Base
    2. 初始化类加载器体系
      1. Tomcat的类加载器体系
      2. initClassLoaders的代码
      3. 一个疑问
    3. Catalina对象
  2. load方法
    1. Catalina类的命令行参数
    2. 加载过程
  3. start方法
  4. await状态
    1. setAwait方法
    2. await方法
上一篇文章 主要分析了Bootstrap main 方法的总体流程,并讨论 了JDK兼容性和启动参数。本篇开始深入细节。

Tomcat 的 运行时视图,简单地看,其实就是一些相互关联的组件。这些组件相互协作,完成一定的任务(比如部署Web 应用、处理到HTTP 请求等)。Tomcat 启动过程中所做的主要工作,也就是创建这些组件,并建立组件之间的关联。当 然,要创建哪些组件,组件之间怎么关联,这是根据配置文件来定制的。

服务器程序的启动过程一般都有“三段式”,Tomcat 也不例外,它的三段式分别是initloadstart

init 方 法

我们先看看Bootstrapinit 方法。
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;
    }
 
该 方法的主要工作依次是:
  1. 设置CatalinaTomcat Servlet 容器的代号)的路径:CATALINA_HOMECATALINA_BASE
  2. 初 始化Tomcat 的类加载器体系
  3. 创建org.apache.catalina.startup.Catalina 对象(启动阶段剩余的 工作由Catalina类 完成)

Catalina_Home 和Catalina_Base

首先,我们看看这两个路径有何区别。Tomcat的启动脚本已经设置了CATALINA_HOMECATALINA_BASE 的 值,而且两者的值是相同的,都是Tomcat的根目录。那么为什么还要设置这两个变量呢?

我们可以从Tomcat 5.5 的配置文档(http://tomcat.apache.org/tomcat-5.5-doc/config/host.html ) 中找到答案:

The description below uses the variable name $CATALINA_HOME to refer to the directory into which you have installed Tomcat 5, and is the base directory against which most relative paths are resolved. However, if you have configured Tomcat 5 for multiple instances by setting a CATALINA_BASE directory, you should use $CATALINA_BASE instead of $CATALINA_HOME for each of these references.

从这段描述可以看出CATALINA_HOMECATALINA_BASE 的区别。简单的说,CATALINA_HOMETomcat 的安装目 录,CATALINA_BASETomcat 的工作目录。如果我们想要运行Tomcat 的 多个实例,但是不想安装多个Tomcat 软件副本。那么我们可以配置多个工作 目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。


Tomcat 每个运行实例需要使用自己的conflogstempwebappsworkshared 目录,因此CATALINA_BASE 就 指向这些目录。 而其他目录主要包括了Tomcat 的二进制文件和脚本,CATALINA_HOME 就指向这些目录。

如果我们希望再运行另一个Tomcat 实例,那么我们可以建立一个目录,把conflogstempwebappsworkshared 拷贝 到该目录下,然后让CATALINA_BASE 指向该目录即可。

下 面,我们看看Bootstrap 是如何设置CATALINA_HOME和CATALINA_BASE。
private void setCatalinaHome() {
        if (System.getProperty("catalina.home") != null)
            return;
        File bootstrapJar =
            new File(System.getProperty("user.dir"), "bootstrap.jar");
        if (bootstrapJar.exists()) {
            try {
                System.setProperty
                    ("catalina.home",
                     (new File(System.getProperty("user.dir"), ".."))
                     .getCanonicalPath());
            } catch (Exception e) {
                // Ignore
                System.setProperty("catalina.home",
                                   System.getProperty("user.dir"));
            }
        } else {
            System.setProperty("catalina.home",
                               System.getProperty("user.dir"));
        }
    }
 
CATALINA_HOME 保 存在系统变量catalina.home 中。setCatalinaHome 方法首先检查catalina.home 系统变量是否设置。如果已经设置,则直接返回;否则,就检查Tomcat 的启动目录(系统变量user.dir)。如果启动目录是bin,那么启动目录下就存在bootstrap.jar故CATALINA_HOME 就是bin 的父目录;如果启动目录下没有bootstrap.jar ,那么就假定启动目录就是CATALINA_HOME
    private void setCatalinaBase() {
        if (System.getProperty("catalina.base") != null)
            return;
        if (System.getProperty("catalina.home") != null)
            System.setProperty("catalina.base",
                               System.getProperty("catalina.home"));
        else
            System.setProperty("catalina.base",
                               System.getProperty("user.dir"));
    }
 
CATALINA_BASE 保存在系统变量catalina.base 中。setCatalinaBase 方 法首先检查catalina.base 系统变量是否设置,如果已经设置,就直接返回。 否则,就检查catalina.home 系统变量是否设置。如果已经设置,则以CATALINA_HOME 作为CATALINA_BASE 。 否则,就以Tomcat 的启动目录(系统变量user.dir )作为CATALINA_BASE

catalina.bat 中已经设置了catalin.homecatalina.base 的值,详见下面代码:
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
 
我们可以通过修改catalina.batCATALINA_HOMECATALINA_BASE 的值 ,来设置catalina.homecatalina.base 这两个系统变量。

初始化类加载器体系

顾名思义,initClassLoaders() 负责初始化类加载器。看代码之前,我们先看看Tomcat 的类加载器体系。

Tomcat 的类加载器体系

各种组件容器(TomcatJBossGlassFishGeronimo 等), 都会有自己的类加载器体系。这主要是为了要把开发者编写的各种组件应用(WAREAR 等)部署到容器中,并实现组件应用之间的隔离。

Tomcat 也实现了自己的类加载器体系。这个在Tomcat 的官方文档中有详细介绍,详见http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html 。 这里做点简单介绍。

Tomcat 的类加载器体系如下图所示:

     Bootstrap
         |
       System
         |
       Common
        / \
    Catalina Shared
              / \
        Webapp1 Webapp2 ...


BootStrap 就是JVM 的启动类加载器,负责加载Java 核心类库和系统扩展类库(%JAVA_HOME%/jre/lib/ext 下的jar 文件)。有的JVM实现提供了两个类加载器,分别加载核心类库和系统扩展类库。我们这里仍用一个Bootstrap 类加载器表示,不影响理解。

System 是JVM的系统类加载器,负责加载CLASSPATH 下的jar 文件,这些文件包 括:
  1. %CATALINA_HOME%/bin/bootstrap.jar
  2. %JAVA_HOME%/lib/tools.jar
  3. %CATALINA_HOME%/bin/commons-logging-api-x.y.z.jar
  4. %CATALINA_HOME%/bin/tomcat-juli.jar
  5. %CATALINA_HOME%/bin/tomcat-daemon.jar
  6. %CATALINA_HOME%/bin/jmx.jar (即之前提到的JDK 1.4 兼容包)
其中,12 是在catalina.bat 中指定的,3-6 是 在bootstrap.jarMETA-INF/MANIFEST.MF 文 件中指定的。

Common 是公共类加载器,负责加载Tomcat 内部和Web 应用程序都可以看到的类。%CATALINA_HOME%/conf/catalina.properties 文 件中指定了这些类:

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar

可 见,Common 加载的是%CATALINA_HOME%/common 目录下的jar 文件。

Catalina 负责加载Tomcat 内部使用的类,这些类对于Web 应用程序不可见。同样,%CATALINA_HOME%/conf/catalina.properties 文件中指定了 这些类:

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar

可 见,Catalina 加载的是%CATALINA_HOME%/server 目录下的jar 文 件。

Shared 负责加载 在Web应用程序之间共享的类,这些类对于Tomcat 内部是不可见的。同样,%CATALINA_HOME%/conf/catalina.properties 文件中指定了 这些类:

shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

可见,Catalina 加载的是% CATALINA_BASE %/shared 目录下的jar 文 件。注意,这里是CATALINA_BASE shared 属于Tomcat 的 工作目录。

Webapp 负责加载Web 应用程序的类,这些类只对本Web 应用程序可见。很显然,每个Web 应用程 序都有一个独立的Webapp 类加载器。它们加载的类包括WAR 包中/WEB-INF/classes/WEB-INF/lib 目录下的类。

需要注意的是,Webapp 并没有遵循类加载器委派模型。Webapp 优先从自己的搜索路径中加载类,而不是委派给父亲Shared 。 这样做的原因应该是保证Web 应用程序优先使用自己的类。但是,System 负责加载的类不应该被覆盖,因此,Webapp 会首先委派给System , 然后自己加载,接着才会委派给父亲Shared 。这个可以参考org.apache.catalina.loader.WebappClassLoaderloadClass 方法。

 

PS:Tomcat 7 的类加载体系有所简化,如下图所示:

     Bootstrap
         |
       System
         |
       Common
        / \
  Webapp1 Webapp2 ...

 

在这种体系下,Tomcat 内部实现类对Web 应用是可见的。如果担心安全风险,可以以-security 方式启动Tomcat

sh startup.sh -security

这样,绝大部分内部实现类对Web 应用均不可见。

 

了解Tomcat 类加载器体系之后,我们来看看initClassLoaders 方法的代码。

initClassLoaders 的代码

 private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }
 

可见,该方法以通过createClassLoader 依次创建了commonservershared 类加载器,并且设置三者之间的父子关系。这三者就是前面提到的CommonCatalinaShared 类加载器。如果%CATALINA_HOME%/conf/catalina.properties 中没有指定Common 的搜索路径,那么就是用当前类的类加载器——系统类加载器作为Common

下面,我们看看createClassLoader 的代码:

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        // CatalinaProperties类读取并封装了catalina.properties中的配置信息
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        // 解析catalina.properties中配置的类搜索路径
        // 将类路径中的${catalina.home}替换成CATALINA_HOME的值,将${catalina.base}替换成CATALINA_BASE的值
        ArrayList repositoryLocations = new ArrayList();
        ArrayList repositoryTypes = new ArrayList();
        int i;
 
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken();

            // Local repository
            boolean replace = false;
            String before = repository;
            while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
                replace=true;
                if (i>0) {
                repository = repository.substring(0,i) + getCatalinaHome()
                    + repository.substring(i+CATALINA_HOME_TOKEN.length());
                } else {
                    repository = getCatalinaHome()
                        + repository.substring(CATALINA_HOME_TOKEN.length());
                }
            }
            while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
                replace=true;
                if (i>0) {
                repository = repository.substring(0,i) + getCatalinaBase()
                    + repository.substring(i+CATALINA_BASE_TOKEN.length());
                } else {
                    repository = getCatalinaBase()
                        + repository.substring(CATALINA_BASE_TOKEN.length());
                }
            }
            if (replace && log.isDebugEnabled())
                log.debug("Expanded " + before + " to " + replace);

            // 区分四种类型的路径
            // Check for a JAR URL repository
            try {
                URL url=new URL(repository);
                repositoryLocations.add(repository);
                repositoryTypes.add(ClassLoaderFactory.IS_URL);
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositoryLocations.add(repository);
                repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
            } else if (repository.endsWith(".jar")) {
                repositoryLocations.add(repository);
                repositoryTypes.add(ClassLoaderFactory.IS_JAR);
            } else {
                repositoryLocations.add(repository);
                repositoryTypes.add(ClassLoaderFactory.IS_DIR);
            }
        }

        String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
        Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
 
        // 创建类加载器对象
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (locations, types, parent);

        // 类加载器被注册成MBean
        // Retrieving MBean server
        MBeanServer mBeanServer = null;
        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
            mBeanServer =
                (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
        } else {
            mBeanServer = MBeanServerFactory.createMBeanServer();
        }

        // Register the server classloader
        ObjectName objectName =
            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
        mBeanServer.registerMBean(classLoader, objectName);

        return classLoader;
    }

 

创建类加载器,必须要知道类搜索路径是什么。前面提到,Tomcat 各类加载器的类搜索路径都定义在%CATALINA_HOME%/conf/catalina.properties 中。该配置文件的主要内容如下:

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

 

配置文件内容的读取是由CatalinaProperties类 完成的。另外,CatalinaProperties 类还提供了额外的功能:

  1. 配置文件的位置可以通过系统变量catalina.config 进行自定义
  2. 如 果没有定义配置文件,或配置文件不存在,那么将使用bootstrap.jar 中的org/apache/catalina/startup/catalina.properties

CatalinaProperties 在最大程度上保证了配置文件总是存在的。

上 述配置的${catalina.home}${catalina.base} 还需要解析,替换成真实的值。例如,将${catalina.home}/common/classes 替 换成D:\ProgramFiles\apache-tomcat-5.5.26/common/classes

类搜索路径被分为四种:URL*.jarjar 和目录。ClassLoaderFactory 类在创建类加载器时,会分别处理这四种类型,具体就不细看了。

类加载器创建之后,将被注册成MBean ObjectName Catalina :type=ServerClassLoader,name= XXX ,其中XXX 表示类加 载器的名称(即commoncatalinashared )。这样,我们就可以JMX 观察每个类加载器的信息,比如加载了哪些类。

一个疑问

至此,类加载器体系基本建立。但是,init 方法又做了两件事件:

  1. Catalina 类加载器被设置成线程的上下文类加载器。
  2. SecurityClassLoad 类,使用Catalian 类加载器预先加载了一些类


1 件事情不足为奇,Tomcat运行过程中,主线程如果需要使用Catalina类加载器,就可以 直接使用线程上下文类加载器。

2 件事情就想不通了,虽然代码注释告诉我们,这是为了在启用安全管理器的情况下避免defineClassInPackage 权限错误。但是,我没有理解。

望各位不吝赐教!

Catalina 对象

init 方法接下来的工作就很简单了:

  1. 通过反射机制,创建一个org.apache.catalina.startup.Catalina 对象
  2. 再 通过反射机制,调用该Catalina 对象的setParentClassLoader 方法,将Shared 类加载器设置成其parentClassLoader


也 许你会奇怪,parentClassLoader 是什么用呢?其实这还是和前面提到的Webapp 类加载器有关。Catalina 对象的parentClassLoader 其实是Webapp 的 父亲,即Shared 类加载器。

我们知道,initClassLoader 方法指定了Common Catalina Shared 之间的父子关系,那么谁来指定Shared Webapp 之间的父子关系呢? 显然应该是Webapp 的创建者。Webapp 的创建者是org.apache.catalina.loader.WebappLoader 对 象,它从Catalina 对象获取Webapp 的父亲类加载器——parentClassLoader

另外,关于Catalina 对象的另一个话题就是:BootstrapCatalina 之间的关系。

仔细阅读Bootstrap 类 的代码,发现init 之后的工作(loadstartstop 等), 都是委派给Catalina 类的同名方法。也就是说,后续的启动和停止,都是Catalina 类完成的。

那么,Tomcat 的启动过程为什么要放在BootstrapCatalina 两个类中呢?而且,Bootstrap 还 是通过反射机制调用Catalina ,感觉上是在走弯路。

《How Tomcat Works》17 章的开头提到这个问题。

This chapter focuses on Tomcat startup using two classes in the org.apache.catalina.startup package, Catalina and Bootstrap. The Catalina class is used to start and stop a Server object as well as parse the Tomcat configuration file, server.xml. The Bootstrap class is the entry point that creates an instance of Catalina and calls its process method. In theory, these two classes could have been merged. However, to support more than one mode of running Tomcat, a number of bootstrap classes are provided. For example, the aforementioned Bootstrap class is used for running Tomcat as a stand-alone application. Another class, org.apache.catalina.startup.BootstrapService, is used to run Tomcat as a Windows NT service.

理论上,这两个类是可以合并的。但是,为了支持以多种方式 启动,Tomcat 将启动的核心逻辑(即Catalina 类)和不同启动方式 (比如 Bootstrap类)分开,并且通过提供多种Bootstrap来实现不同的启动方式。

目前,Tomcat 共支持三种启动方式:

  1. 作为独立的程序,从命令行启动
  2. 作为嵌入式程序,从其他进程中启动
  3. 作为Windows 服务,自动启动

Tomcat 5.5 中:

  1. 第一种启动方式就是由Bootstrap类和Catalina类实现
  2. 第二种启动 方式是由org.apache.catalina.startup.Embedded 类 实现的(其实Catalina 类就是Embedded的子类)
  3. 第三 种启动方式仍然由Bootstrap类和Catalina类实现


不过,在Tomcat 4.x 中,第三种启动方式由专门的Bootstrap Service类和Catalina Service 类来实现的。

可见,Tomcat 的发展历史中,还是体现了“BootstrapCatalina 分 离,Bootstrap 封装不同启动方式”的策略。但是,现在的代码看,这个分离已经不是很清楚。

OKinit 方法的讨论到此结束。下面我们看看loadstart 方法。Bootstrap 的 这两个方法,其实都是直接调用Catalina 类的同名方法,因此,我们主要分析的其实是Catalina 类的相关代码。

load 方法

下面是Boostrap 类的load 方法。

 private void load(String[] arguments)
        throws Exception {
        // Call the load() method
        String methodName = "load";
        Object param[];
        Class paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);
    }

 

可以看出,Catalinaload 方法有两个版本:有参数和无参 数。

有参数版本,首先调用了arguments方法来处理参数。如果处理成功,则再调用无参数版本。因此,load的核心逻辑在无参数版本中。

    public void load(String args[]) {

        try {
            if (arguments(args))
                load();
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }

 

无参数版本的load 方法做的工作,主要是根据Tomcat 的配置文件,创建各个组件,并建立组件之间的关联。比如,创建核心的Server 组件、Service 组 件、Manager 组件、 Loader 组 件等。 这个细节我们等会讨论,首先简单看看arguments 方法。arguments 方法对理解启动过程不是很关键,如果不感兴趣可以跳过。

Catalina 类的命令行参数

Catalina.bat 脚本的参数,其实是直接传递到Catalina 类的。arguments 方法正是处理这些参数的。

Catalina 类可以识别的参数包括:

  1. -config {pathname} 设置配置文件的路径。如果是相对路径,则是相对于CATALINA_HOME 。默认值是conf/server.xml
  2. -noaming 不启用命名服务
  3. -help 打印帮助信息
  4. -start 启动当前的Catalina 实 例
  5. -stop 停止当前的Catalina 实例

PS:Tomcat 7 新加了-configtest 选项。


arguments 的代码比较简单。

 protected boolean arguments(String args[]) {

        boolean isConfig = false;

        if (args.length < 1) {
            usage();
            return (false);
        }

        for (int i = 0; i < args.length; i++) {
            if (isConfig) {
                configFile = args[i];
                isConfig = false;
            } else if (args[i].equals("-config")) {
                isConfig = true;
            } else if (args[i].equals("-nonaming")) {
                setUseNaming( false );
            } else if (args[i].equals("-help")) {
                usage();
                return (false);
            } else if (args[i].equals("start")) {
                starting = true;
                stopping = false;
            } else if (args[i].equals("stop")) {
                starting = false;
                stopping = true;
            } else {
                usage();
                return (false);
            }
        }

        return (true);
    }

 
Catalina 类的成员变量startingstopping 分别表示要启动和停止Tomcat

加载过程

下面我们看看Catalina 类的load 方法的代码。 

 public void load() {

        // 初始化CATALINA_HOME、CATALINA_BASE和临时目录,该方法在Embedded类中。
        initDirs();

        // 初始化命名服务的基本配置,包括java.naming.factory.url.pkgs和java.naming.factory.initial
        // java.naming.factory.url.pkgs的默认值为 org.apache.naming
        // java.naming.factory.initial 的默认值为org.apache.naming.java.javaURLContextFactory

        // Before digester - it may be needed
        initNaming();

        // 创建Digester对象。该对象被用来解析配置文件(默认为conf/server.xml)
        // Create and execute our Digester
        Digester digester = createStartDigester();
        long t1 = System.currentTimeMillis();

        Exception ex = null;
        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            // 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource("file://" + file.getAbsolutePath());
        } catch (Exception e) {
            ;
        }

        // 如果配置文件不存在,则在类路径中加载
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                ;
            }
        }

        // 如果类路径中也找不到,则加载server-embed.xml
        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if( inputStream==null ) {
            try {
                inputStream = getClass().getClassLoader()
                .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                ;
            }
        }
        

        // 如果没能加载配置文件,则报错,中断启动
        if ((inputStream == null) && (file != null)) {
            log.warn("Can't load server.xml from " + file.getAbsolutePath());
            return;
        }

        // 使用Digester对象解析配置文件,解析的过程中会创建各种组件,包括Server组件。
        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
            inputStream.close();
        } catch (Exception e) {
            log.warn("Catalina.start using "
                               + getConfigFile() + ": " , e);
            return;
        }

        // 将系统标准输出(System.out)和系统错误输出(System.err)重定向到定制的SystemLogHandler。
         // SystemLogHandler可以将每个线程的输出隔离到不同的输出流中。
        // Stream redirection
        initStreams();

         // 初始化Server组件。成员变量server就代表Server组件
        // Start the new server
        if (server instanceof Lifecycle) {
            try {
                server.initialize();
            } catch (LifecycleException e) {
                log.error("Catalina.start", e);
            }
        }

        long t2 = System.currentTimeMillis();
        if(log.isInfoEnabled())
            log.info("Initialization processed in " + (t2 - t1) + " ms");

    }

 

Catalina 加载的主要流程参见上述代码中的中文注释,应该比较清楚,这里就不一一赘述了。

需 要注意的是Digester ,它是Apache 基金会的另一个项目,主要负责解析XML 并执行一定的操作。其主要原理是,为XML 的每个元素配置特定的规则,规则描述 了Digester 遇到该元素时需要执行的操作。

Tomcat 使用Digester 来 解析配置文件(默认是conf/server.xml ),并根据配置创建各种组件,并 建立组件之间的关联。创建和关联,都是通过自定义的规则来实现的。我们以conf/server.xml 的部分内容和部分规则为例,解释一下 Digester 的原理。

配置文件的部分内容:

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">

  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Service name="Catalina">
    <Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />
    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost" appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
        ...
      </Host>
    </Engine>
  </Service>
</Server>
 


其中, 每个XML 元素代表一个组件对象,元素中属性对应了组件的成员变量。例如,<Server> 就代表Server 组件,该组件对象有两个成员变量portshutdown 。不难猜到,它们其实是之前提到SHUTDOWN 端口和SHUTDOWN 命令。

Server 组件包含了 Listener 对象和Service 组件。Service 组件又包含了Connetor组件和Engine 组件。Engine 组件又包含了Host 组 件。Host 组件也包含了...这就是组件之间的关联。

至于这些组件的作用是什么,关联关系为什么是这样的,我们会在后面的文章中看到。

下 面我们在看看Digester 规则。规则以方法调用的定义。Tomcat 启动相关的规则定义在createStartDigester 方法中,部分代码如 下:

 protected Digester createStartDigester() {
       
        // ...

        // Configure the actions we will be using

        // 创建Server组件,即StandardServer对象
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        // 设置StandardServer的成员变量port和shutdown
        digester.addSetProperties("Server");
        // 建立Catalina对象和StandardServer对象之间的关联,前者包含后者
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

        //...
        // 创建Listener对象,类由className属性决定
        digester.addObjectCreate("Server/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        // 设置Listener对象的成员变量
        digester.addSetProperties("Server/Listener");
        // 建立StandardServer对象和Listener对象之间的关联,前者包含后者
        digester.addSetNext("Server/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");

         // 创建Service组件,即StandardService对象
        digester.addObjectCreate("Server/Service",
                                 "org.apache.catalina.core.StandardService",
                                 "className");
        // 设置StandardService的成员变量 name
        digester.addSetProperties("Server/Service");
        // 建立StandardServer对象和StandardService对象之间的关联,前者包含后者
        digester.addSetNext("Server/Service",
                            "addService",
                            "org.apache.catalina.Service");

         // ...
        return (digester);

    }

 

概括地 说,addObjectCreate 表示创建对象,addSetProperties 表 示设置对象的属性,addSetNext 表示设置对象的包含对象。这都是 Digester 的常用规则。当然,Tomcat 也定制了一些规则,以执行更加复杂的操作。

OKload 方法就算看完了。总之,该方法结束后,Tomcat 的运行时视图已经被建立,各大组件及关联关系均以建立。 下一步,就是启动Catalina 了。

start 方法

public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
        method.invoke(catalinaDaemon, (Object [])null);
    }
 

该方法其实仅仅是调用了Catalina 类的start 方法,因此我们重点看Catalina 类的start 方法。

 public void start() {

        //确保load方法已经被调用。load方法会创建StandardServer 实例,并赋值给成员变量server。
        if (server == null) {
            load();
        }

        long t1 = System.currentTimeMillis();
   
        //调用server的生命周期方法start
        // Start the new server
        if (server instanceof Lifecycle) {
            try {
                ((Lifecycle) server).start();
            } catch (LifecycleException e) {
                log.error("Catalina.start: ", e);
            }
        }

        long t2 = System.currentTimeMillis();
        if(log.isInfoEnabled())
            log.info("Server startup in " + (t2 - t1) + " ms");

        //注册JVM的shutdown钩子
        try {
            // Register shutdown hook
            if (useShutdownHook) {
                if (shutdownHook == null) {
                    shutdownHook = new CatalinaShutdownHook();
                }
                Runtime.getRuntime().addShutdownHook(shutdownHook);
            }
        } catch (Throwable t) {
            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
            // fine without the shutdown hook.
        }

        //如果await设置成true,则进入await状态
        if (await) {
            await();
            //退出await状态后,就停止Tomcat
            stop();
        }
    }

 

start 方法执行之前,需要确保load 方法已经执行。如果load 方法已经执行,那么成员变量server 肯定被赋值。因此,start 方法首先判断成员变量 server 是 否为null ,如果是,则调用load 方法。

接着,调用server 的start方法,启动 Catalina。启动阶段的工作和加载阶段还是不同的。例如,Connector组件负责网络通信,加载阶段只是创建组件对象,启动阶段才会监听端口。

然 后,Catalina 的主线程会进入await状态,如果成员变量await被设置成 true的话。Tomcat 5.5.26 源代码分析——启动过程(一)》中也提到,如果await被设置成true,那么Tomcat的主线程将监听SHUTDOWN 端口,等待SHUTDOWN 命 令,从而我们可以在Tomcat进程外部通过网络停止Tomcat的运行。Tomcat收到SHUTDOWN命令之后,主线程就会退出await状 态,await方法也执行结束, 从而stop方法被调用,Tomcat停止运行。

这里我们需要注意一下server instanceof Lifecycle 的代码。Lifecycle是一个生命周期接口,定义了各种生命周期方法start和 stop。只有实现了该接口的组件才能拥有生命周期方法。生命周期方法的调用是嵌套的,父组件的生命周期方法会调用子组件的同名方法。这样,只需调用顶层 组件的start方法,就可以启动所有子孙组件。stop也一样。因此,生命周期方法和组件关联关系,使得Tomcat 很容易管理各个组件的启动和停止。

OK,start方法也介绍完了。各组件的start方法被调用之后,Tomcat已经处于就绪状态,等待请求的到来。

本文的最后,详细讨论一下await状态的实现细节。

await状态

setAwait方法

Bootstrap 类的main方法中,处理start 启动参数时,会调用 setAwait 方法。

 public void setAwait(boolean await)
        throws Exception {
        Class paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = new Boolean(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);
    }


上 述代码其实调用了Catalina 类的setAwait 方法。CatalinasetAwait 方法定义在于其父类Embedded主要就是设置成员变量await的值

    public void setAwait(boolean b) {
        await = b;
    }

 

await 变成true 之后,Tomcat 就进入await 状态,这一点在start 方法中已经分析 过。

await方法

如果成员变量awaittrue ,那么Catalina 类的await 方法就会被调用。

    public void await() {

       //直接调用StandardServer的await方法
        server.await();
    }

 

下面我们看看StandardServer 类的await 方法。

 public void await() {
        // port是SHUTDOWN端口。如果值为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现await状态。
        // 如果port为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现 await状态。
        // 如果port为-1,则表示通过简单循环的方式来实现 await状态。此方法适合嵌入式Tomcat。
        // 如果port为其他值,则表示通过监听网络端口的方式来实现 await状态。

        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
        if( port == -2 ) {
            // undocumented yet - for embedding apps that are around, alive.
            return;
        }
        if( port==-1 ) {
            while( true ) {
                try {
                    Thread.sleep( 100000 );
                } catch( InterruptedException ex ) {
                }
分享到:
评论
6 楼 jakehuu 2011-02-24  
佩服楼主 现在正在看 《how tomcat works》 ,楼主能够提供这本书里面的例子程序啊?我的邮箱 huqiao86@gmail.com
5 楼 jackdown 2010-04-06  
jarfield 写道
jackdown 写道
分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。


《How Tomcat Works》,你可以上百度文库搜一下,里面有的。


嗯,看到了,感觉不错,谢谢。
4 楼 jarfield 2010-03-31  
jackdown 写道
分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。


《How Tomcat Works》,你可以上百度文库搜一下,里面有的。
3 楼 jackdown 2010-03-31  
分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。
2 楼 sw1982 2010-03-27  
分析的很精辟啊,而且你推荐的那本书确实牛,看了一节就很大收益
1 楼 lkjust08 2010-03-27  
lz写的很详细,可能是小弟知识不足,具体的内容还是看不懂,看来,小弟还是要加强学习呀。

相关推荐

Global site tag (gtag.js) - Google Analytics