锁定老帖子 主题:Tomcat解析之初始化类加载器(截图)
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2010-01-30
最后修改:2010-02-01
之前有写过关于tomcat中常用的一些类结构的文章。 今天来关注一下,tomcat的类加载器相关的内容。 PS: 由于前一篇文章内容比较简单, 有朋友冠以我标题党之嫌,对于此种说法,本人深感抱歉,可能标题确实有点大,但是这些常用的类,我更多的时候只关注其用法,而忽略了内部实现,所以也就把这些内容总结了一下,发了出来。别无标题党之意,请各位eyer海涵。
OK, 现在进入正题. Tomcat类加载器初始化. 开始之前,我们首先需要了解一下几个基本的知识点;
1.tomcat中类加载器的结构与关系。 这里,我引用tomcat文档的一个简图来说明一下, 有兴趣深究的朋友,可以去翻看tomcat的文档,理解更多信息.
(tomcat5.5) (由于参考的是tomcat6.0的源代码,这里纠正一下类加载器的图(如下),以免给别的朋友造成误解,同时也多谢asialee给出的提醒)
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共同交流,达到共同提高的目录。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-02-01
难得的重口味啊,楼主,顶你了,这样的贴才有养分啊。
|
|
返回顶楼 | |
发表时间:2010-02-01
这些基础原理的东西值得探讨
|
|
返回顶楼 | |
发表时间:2010-02-01
内功贴,不错
|
|
返回顶楼 | |
发表时间:2010-02-01
可以肯定的是这个是tomcat5.5后的类加载器结构,以前是有一个sharedClassLoader和server class Loader,现在那两个properties的值为空,所以就没有了。 不知道tomcat重构这个是基于什么的考虑。
|
|
返回顶楼 | |
发表时间: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指出错误。 |
|
返回顶楼 | |
发表时间:2010-02-02
赞楼主的钻研精神,推荐一本Tomcat内幕的书,不过是关于Tomcat 4和5,对Tomcat的主要架构的介绍还是很清楚地。《How Tomcat Works》
|
|
返回顶楼 | |
发表时间:2010-02-02
最后修改:2010-02-02
不错
|
|
返回顶楼 | |
发表时间:2010-02-02
值得学习LZ的钻研精神啊
|
|
返回顶楼 | |
发表时间:2010-02-02
Good article.
|
|
返回顶楼 | |