论坛首页 Java企业应用论坛

Tomcat解析之初始化类加载器(截图)

浏览 16844 次
该帖已经被评为精华帖
作者 正文
   发表时间:2010-01-30   最后修改:2010-02-01

  之前有写过关于tomcat中常用的一些类结构的文章。

 解析Tomcat之HttpServlet(截图记录)

  今天来关注一下,tomcat的类加载器相关的内容。

  PS: 由于前一篇文章内容比较简单, 有朋友冠以我标题党之嫌,对于此种说法,本人深感抱歉,可能标题确实有点大,但是这些常用的类,我更多的时候只关注其用法,而忽略了内部实现,所以也就把这些内容总结了一下,发了出来。别无标题党之意,请各位eyer海涵。

 

  OK, 现在进入正题. Tomcat类加载器初始化.

  开始之前,我们首先需要了解一下几个基本的知识点;

 

1.tomcat中类加载器的结构与关系。

   这里,我引用tomcat文档的一个简图来说明一下, 有兴趣深究的朋友,可以去翻看tomcat的文档,理解更多信息.

  


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(tomcat5.5)

 (由于参考的是tomcat6.0的源代码,这里纠正一下类加载器的图(如下),以免给别的朋友造成误解,同时也多谢asialee给出的提醒

 
(tomcat6.0)
 

2.每种类加载器分别加载什么资源: 

     这些内容,可以在tomcat文档的  Class Loader HOW-TO 找到.

    这里我要说明的是, 在tomcat中,这些内容是记录在哪里的。既(程序怎么让tomcat知道,需要加载哪些类)

    答案是-----

     其通过一个配置文件来指定的:(catalina.properties),这个文件默认存放在

     tomcat路径下的 bin/bootstrap.jar中。

    如图

    

 

   
    打开文件,其内容如下:

 

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageAccess unless the
# corresponding RuntimePermission ("accessClassInPackage."+package) has
# been granted.
package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.,sun.beans.
#
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageDefinition unless the
# corresponding RuntimePermission ("defineClassInPackage."+package) has
# been granted.
#
# by default, no packages are restricted for definition, and none of
# the class loaders supplied with the JDK call checkPackageDefinition.
#
package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.

#
#
# List of comma-separated paths defining the contents of the "common" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common" 
# loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository
common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar

#
# List of comma-separated paths defining the contents of the "server" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server" 
# loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository
server.loader=

#
# List of comma-separated paths defining the contents of the "shared" 
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Examples:
#     "foo": Add this folder as a class repository
#     "foo/*.jar": Add all the JARs of the specified folder as class 
#                  repositories
#     "foo/bar.jar": Add bar.jar as a class repository 
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
shared.loader=

#
# String cache configuration.
tomcat.util.buf.StringCache.byte.enabled=true
#tomcat.util.buf.StringCache.char.enabled=true
#tomcat.util.buf.StringCache.trainThreshold=500000
#tomcat.util.buf.StringCache.cacheSize=5000

 

 

 此文件,下面会有详细的介绍.

 

 

OK,到此,我们初步了解到tomcat关于类加载器的一些知识。 下面来详细看看,tomcat内部是怎么来初始化这些类加载器的吧.

 

 首先, 我们知道, java程序都需要一个入口(main方法), 而在tomcat中,这个入口在

  org.apache.catalina.startup.Bootstrap 这个类中。

  看其结构:

 


    定位到方法内部:

    

public static void main(String args[]) {

        if (daemon == null) {
            daemon = new Bootstrap();
            try {
            	//初始化资源   (今天来了解的.)
                daemon.init();
            } catch (Throwable t) {
                t.printStackTrace();
                return;
            }
        }
        
        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 {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

 

  在tomcat启动之前, 需要初始化一些系统资源, 初始化的详细工作都定义在init()方法内部了。

 

    OK,我们继续追踪一下。 定位到init()方法中.\

 

 

    public void init()
        throws Exception
    {
        // Set Catalina path 设置catalina基本路径
        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;
    }

 

  可以看到, 上面的代码中,用来初始化类加载器、验证类加载器、

  以及使用类加载器来加载类"org.apache.catalina.startup.Catalina"等等操作。

  这篇文章,主要来探讨一下,tomcat初始化类加载器的方式, ,所以,我们追踪到方法initClassLoaders()中:

  

   这里主要介绍一下,下面的流程, tomcat会调用initClassLoaders()方法。

   用来初始化common ,catalina,shared三种类加载器,而这个操作是通过方法

   createClassLoader(String name, ClassLoader parent)来完成的。

   而后2个都属于common的子级,

   所以下面给出2个方法的源代码(其中相关信息,都以注释给出):

  

 

 /**
     * 初始化类加载器:
     * 加载三种:
     *        common.
     *        /           \
     *  catalina   shared. 
     */
    private void initClassLoaders() {
        try {
        	//创建common类加载器
            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();
            }
            //创建catalina类加载器,指定其父级别的加载器为commonLoader.
            catalinaLoader = createClassLoader("server", commonLoader);
            //创建sharedLoader类加载器,指定其父级别的加载器为commonLoader.
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

   

  

/**
     * 创建类加载器
     * 
     * @param name
     * @param parent 指定上一级别的类加载器
     * @return
     * @throws Exception
     */
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
    	//这里以common为例: 从catalina.properties中获取common.loader  类加载信息 
    	//如:
    	//common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 如果没有任何信息,则返回父加载器
        if ((value == null) || (value.equals("")))
            return parent;
        
        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;
            //是否含有"${catalina.home}"
            while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
                replace=true;
                if (i>0) {
                	//替换成tomcat路径  替换后的形式如下: c:/opensource/tomcat5/lib.
                repository = repository.substring(0,i) + getCatalinaHome() 
                    + repository.substring(i+CATALINA_HOME_TOKEN.length());
                } else {
                    repository = getCatalinaHome() 
                        + repository.substring(CATALINA_HOME_TOKEN.length());
                }
            }
            //是否含有"${catalina.base}"
            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 " + repository);

            // 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);

        // Retrieving MBean server
        MBeanServer mBeanServer = null;
        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
            mBeanServer =
                (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
        } else {
            mBeanServer = ManagementFactory.getPlatformMBeanServer();
        }

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

 

 

  到这里,我们可以确定,tomcat文档中的类加载器之间关系是准确的,并非凭空说的。 

    到这里,我们可能对于createClassLoader()方法的

CatalinaProperties.getProperty(name + ".loader");

  有点疑问, 到底tomcat是如果通过配置文件来获取需要初始化类加载器的相关信息的呢/

 

 前面我们看到 catalina.properties中记录了tomcat三种类加载器中分别需要加载一些什么类的信息。

  而CatalinaProperties类正是用来解析此文件的,以告诉tomcat,哪种类加载器,加载哪些类。

 

  我们来看看这个类的源代码:

 

/**
 * Utility class to read the bootstrap Catalina configuration.
 * 读取tomcat 的配置文件 catalina.properties
 * @author Remy Maucherat
 * @version $Revision: 467222 $ $Date: 2006-10-24 11:17:11 +0800 (星期二, 24 十月 2006) $
 */

public class CatalinaProperties {
    // ------------------------------------------------------- Static Variables
    private static org.apache.juli.logging.Log log=
        org.apache.juli.logging.LogFactory.getLog( CatalinaProperties.class );
    private static Properties properties = null;

    static {
        loadProperties();
    }


    // --------------------------------------------------------- Public Methods


    /**
     * Return specified property value.
     */
    public static String getProperty(String name) {
        return properties.getProperty(name);
    }


    /**
     * Return specified property value.
     */
    public static String getProperty(String name, String defaultValue) {
        return properties.getProperty(name, defaultValue);
    }


    // --------------------------------------------------------- Public Methods


    /**
     * 加载配置信息
     * Load properties.
     */
    private static void loadProperties() {
        InputStream is = null;
        Throwable error = null;
        //第一步: 从系统变量中查找
        try {
        	//getConfigUrl()方法的内容为:   System.getProperty("catalina.config");
            String configUrl = getConfigUrl();
            if (configUrl != null) {
                is = (new URL(configUrl)).openStream();
            }
        } catch (Throwable t) {
            // Ignore
        }

        //第二步:再从tomcat的conf目录下去找
        if (is == null) {
            try {
                File home = new File(getCatalinaBase());
                File conf = new File(home, "conf");
                File properties = new File(conf, "catalina.properties");
                is = new FileInputStream(properties);
            } catch (Throwable t) {
                // Ignore
            }
        }
        
        //还没找到: 则从类路径中加载.
        if (is == null) {
            try {
                is = CatalinaProperties.class.getResourceAsStream
                    ("/org/apache/catalina/startup/catalina.properties");
            } catch (Throwable t) {
                // Ignore
            }
        }
        
        // 到这里的话,如果找到了,就将配置文件中加载过来
        if (is != null) {
            try {
                properties = new Properties();
                properties.load(is);
                is.close();
            } catch (Throwable t) {
                error = t;
            }
        }

        if ((is == null) || (error != null)) {
            // Do something
            log.warn("Failed to load catalina.properties", error);
            // That's fine - we have reasonable defaults.
            properties=new Properties();
        }
        
        //将配置文件的key-value 设置为 系统变量.
        // Register the properties as system properties
        Enumeration enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()) {
            String name = (String) enumeration.nextElement();
            String value = properties.getProperty(name);
            if (value != null) {
                System.setProperty(name, value);
            }
        }

    }


    /**
     * Get the value of the catalina.home environment variable.
     */
    private static String getCatalinaHome() {
        return System.getProperty("catalina.home",
                                  System.getProperty("user.dir"));
    }
    
    
    /**
     * Get the value of the catalina.base environment variable.
     */
    private static String getCatalinaBase() {
        return System.getProperty("catalina.base", getCatalinaHome());
    }


    /**
     * Get the value of the configuration URL.
     */
    private static String getConfigUrl() {
        return System.getProperty("catalina.config");
    }


}

 

 OK, 到此,tomcat初始化类加载器的过程,我们都已经了解了。

 可能看到这里,有的人觉得还是不太理解。 好,让我们来总结一下,这个顺序。

   我们按照我们平时常用的操作来看;

  1.我要启动tomcat .. (调用Bootstrap的main  方法)

      (1)tomcat启动之前,需要加载类,需要类加载器。 于是,它去做初始化工作. -----> init()方法.

      (2)init()方法开始工作它再去调用------>initClassLoaders()方法.

      (3)发现需要初始化3个类型的类加载器,再调用---> createClassLoader(name,parent) ,告诉它,我要初始化哪种类型的,它的老爸是谁。

      (4)通过CatalinaProperties 类去联络catalina.properties,获得,这个哪种类加载器加载哪些类的信息。

      (5) 完成初始化,并返回.

  2.tomcat 加载其他资源(待续),启动成功……

  

 OK, 文章写完了, 这些内容都是本人自己学习的记录,难免有错误之处,还望大家多提意见,希望能跟各位javaeyer共同交流,达到共同提高的目录。

  • 大小: 3.8 KB
  • 大小: 41 KB
  • 大小: 12 KB
  • 大小: 2.3 KB
   发表时间:2010-02-01  
难得的重口味啊,楼主,顶你了,这样的贴才有养分啊。
0 请登录后投票
   发表时间:2010-02-01  
这些基础原理的东西值得探讨
0 请登录后投票
   发表时间:2010-02-01  
内功贴,不错
0 请登录后投票
   发表时间:2010-02-01  
可以肯定的是这个是tomcat5.5后的类加载器结构,以前是有一个sharedClassLoader和server class Loader,现在那两个properties的值为空,所以就没有了。 不知道tomcat重构这个是基于什么的考虑。
0 请登录后投票
   发表时间:2010-02-01  
asialee 写道
可以肯定的是这个是tomcat5.5后的类加载器结构,以前是有一个sharedClassLoader和server class Loader,现在那两个properties的值为空,所以就没有了。 不知道tomcat重构这个是基于什么的考虑。

  是的,我是参考的tomcat6.0的源代码, 文章中类加载器的图当时是参考的tomcat5.5的文档发上来了。
   这里纠正一下我的错误,把tomcat6的最新类加载器图发上来。
  

 
  另外,对于新的类加载器的各个描述,这里给出链接地址,有兴趣的朋友可以去看看,
   http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html
同时也多谢asialee指出错误。
  • 大小: 2.3 KB
0 请登录后投票
   发表时间:2010-02-02  
赞楼主的钻研精神,推荐一本Tomcat内幕的书,不过是关于Tomcat 4和5,对Tomcat的主要架构的介绍还是很清楚地。《How Tomcat Works》
0 请登录后投票
   发表时间:2010-02-02   最后修改:2010-02-02
不错  
0 请登录后投票
   发表时间:2010-02-02  
值得学习LZ的钻研精神啊
0 请登录后投票
   发表时间:2010-02-02  
Good article.
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics