一.将tomcat源码导入到eclipse
在apach官网上下载tomcat源码后,学习源码通过导入到eclipse里,然后用其调试功能来学习是的方便。对于eclipse导入tomcat源码可以参考:http://www.cnblogs.com/huangfox/archive/2011/10/20/2218970.html 对于tomcat依赖的jar,可以通过后面的附件来下载。
二.运行tomcat
首先,tomcat在启动时,会读取一些配置文件,也就是${CATALINA_HOME}/conf下面的所有配置文件,我们可以将conf文件夹及其里面的所有配置文件拷到eclipse中来,提供tomcat启动需要的配置信息。
在org.apache.catalina.startup这个包下面的Bootstrap.java是启动tomcat的类,它里面有个main函数,是启动tomcat的入口,代码如下
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
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 if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
通过上面源码可以看出,tomcat先进行初始化,主要进行的功能有:设置catalina的路径(主要有catalina.home和catalina.base这两个路径),初始化catalina的加载类(主要有三个类别:common.loader,server.loader,shared.loader,这些加载类都是通过读取conf下面的catalina.properties配置文件来获取相应的加载类,而且这些类加载都是通过tomcat里设置的安全验证的),设置catalina的(这个类的路径为:org.apache.catalina.startup.Catalina。其中默认加载类为org.apache.catalina.loader.StandardClassLoader,加载时需要的配置文件路径为:conf/server.xml)。 tomcat在初始化完上面的功能后,就根据启动时的命令参数,来进行相应命令的动作,通过上面的源码可以看出,在启动Bootstrap.java这个类时,可以传入的命令参数有:startd(默认值)、stopd、configtest,具体相应命令的功能,就不再进行说明。
三,分析init初始化哪些内容
下面来具体看看tomcat在启动时,都初始化了哪些内容。首先看一下init()这个方法的源码:
/**
* Initialize daemon.
*/
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
log.info("catalina.home:" + System.getProperty(Globals.CATALINA_HOME_PROP));
setCatalinaBase();
log.info("catalina.base:" + System.getProperty(Globals.CATALINA_BASE_PROP));
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;
}
3.1 catalina的两个路径设置
其中,setCatalinaHome()和setCatalinaBase();这两个方法就是设置catalina的两个路径的,具体的实现,首先参看setCatalinaHome()方法的源码:
/**
* Set the <code>catalina.home</code> System property to the current
* working directory if it has not been set.
*/
private void setCatalinaHome() {
if (System.getProperty(Globals.CATALINA_HOME_PROP) != null)
return;
File bootstrapJar =
new File(System.getProperty("user.dir"), "bootstrap.jar");
if (bootstrapJar.exists()) {
try {
System.setProperty
(Globals.CATALINA_HOME_PROP,
(new File(System.getProperty("user.dir"), ".."))
.getCanonicalPath());
} catch (Exception e) {
// Ignore
System.setProperty(Globals.CATALINA_HOME_PROP,
System.getProperty("user.dir"));
}
} else {
System.setProperty(Globals.CATALINA_HOME_PROP,
System.getProperty("user.dir"));
}
}
通过上面的代码可以看出,它设置的catalina.home的值,就是获取系统user.dir属性的值,也就是你当前eclipse导入tomcat源码建的项目的路径,如果你项目路径如下所示:
它的location在E:\study\tomcat7下面,那么System.getProperty("user.dir")的值也是E:\study\tomcat7.因为我们导入的源码中不会有bootstrap.jar,所以它在E:\study\tomcat7\bootstrap.jar是不存在的,最后setCatalinaHome()这个方法执行的代码是:
else {
System.setProperty(Globals.CATALINA_HOME_PROP,
System.getProperty("user.dir"));
}
也就是将catalina.home的值设置成为:E:\study\tomcat7。对于setCatalinaBase()的源码类似,就不再贴出,分析。
3.2 初始化作为catalina的加载类
对于这个功能,我们可以参看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) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
通过源码,在这个方法中我们看不到实质的内容,它们主要都是通过createClassLoader(String name, ClassLoader parent)这个方法来创建三个类型(commonLoader、catalinaLoader、sharedLoader)的加载类的。那我们就通过源码来看下这个方法的功能:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<Repository>();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken().trim();
if (repository.length() == 0) {
continue;
}
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(repositories, parent);
// Retrieving MBean server
MBeanServer mBeanServer = null;
if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
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;
}
通过源码可以看出,这个方法主要是两个功能:首先,通过CatalinaProperties这个类,来获取相应要加载进来的jar包,然后通过调用replace(),来定位到相应的jar包,也就是把jar包的路径解析出来。然后,通过jmx将这些jar包的组件注册到tomcat中来;
对于这个方法中用到的CatalinaProperties,这个类主要是读取catalina配置的,这个类首先会执行静态块中的loadProperties()方法
/**
* Load properties.
*/
private static void loadProperties() {
InputStream is = null;
Throwable error = null;
try {
String configUrl = getConfigUrl();
if (configUrl != null) {
is = (new URL(configUrl)).openStream();
}
} catch (Throwable t) {
handleThrowable(t);
}
if (is == null) {
try {
File home = new File(getCatalinaBase());
File conf = new File(home, "conf");
File propsFile = new File(conf, "catalina.properties");
is = new FileInputStream(propsFile);
} catch (Throwable t) {
handleThrowable(t);
}
}
if (is == null) {
try {
is = CatalinaProperties.class.getResourceAsStream
("/org/apache/catalina/startup/catalina.properties");
} catch (Throwable t) {
handleThrowable(t);
}
}
if (is != null) {
try {
properties = new Properties();
properties.load(is);
is.close();
} catch (Throwable t) {
handleThrowable(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();
}
// 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);
}
}
}
因为,这时还没有设置catalina.config系统属性,所以String configUrl = getConfigUrl();获取不到值,代码会接着执行:
File home = new File(getCatalinaBase());
File conf = new File(home, "conf");
File propsFile = new File(conf, "catalina.properties");
is = new FileInputStream(propsFile);
通过这可以看出是读取conf/catalina.properties这个属性配置文件,然后通过下面代码加载到属性文件中
if (is != null) {
try {
properties = new Properties();
properties.load(is);
is.close();
} catch (Throwable t) {
handleThrowable(t);
error = t;
}
}
所以,通过以上的代码可以看出CatalinaProperties这个类最终读取的是conf/catalina.properties配置文件,这样在Bootstrap.java这个类中的createClassLoader(String name, ClassLoader parent)方法的第一句
String value = CatalinaProperties.getProperty(name + ".loader");
读取的值就是conf/catalina.properties里面配置的值。这个方法后面的代码就是对于加载进行来conf/catalina.properties配置中的server.loader、common.loader、shared.loader三个值中的组件的注册工作。到些初始化catalina类的加载类分析工作已经完成,那接着看初始catalina。
3.3 初始化catalina
初始化catalina类的代码主要是Bootstrap.java类中init()方法后面的代码:
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;
首先通过前面已经初始化好的catalina的加载类,也就是org.apache.catalina.loader.StandardClassLoader来加载org.apache.catalina.startup.Catalina这个类,然后设置org.apache.catalina.startup.Catalina类的parentClassLoader为org.apache.catalina.loader.StandardClassLoader(这其中用到反射机制)。
通过以上的简单讲解,让大家大致了解到tomcat在启动时做的部分工作。