之前有写过关于tomcat中常用的一些类结构的文章。
今天来关注一下,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共同交流,达到共同提高的目录。