startup.bat
会最终运行Bootstrap
的main
方法,而main
会先调用init
方法。
1.init
1.1 setCatalinaHome();、setCatalinaBase();
setCatalinaHome();
setCatalinaBase();
点进它们的实现,发现这两个方法都调用到System.getProperty(Globals.CATALINA_HOME_PROP)
,其中Globals.CATALINA_HOME_PROP
为"catalina.home"
。这个调用的返回值不会是空,因为在之前的idea启动设置中:
catalina.home
环境变量设置为了tomcat的home目录。
这两个函数会设置Globals.CATALINA_BASE_PROP
和Globals.CATALINA_HOME_PROP
的系统变量。
1.2 initClassLoaders
创建commonLoader
、catalinaLoader
、sharedLoader
类加载器(默认情况下这三个类加载器指向同一个对象。它们都调用了createClassLoader
。
1.2.1 createClassLoader
装载 catalina.properties
里配置的目录下的文件和jar包,后两个加载器的父加载器都是第一个,最后注册了 MBean,可以用于 JVM 监控该对象)。
String value = CatalinaProperties.getProperty(name + ".loader");
CatalinaProperties
在static代码块内会读取{home}/conf/catalina.properties
,加载到properties
变量。之后便可调用getProperty
获取其中的属性。
换句话说,CatalinaProperties的属性都可在文件catalina.properties
找到。
value = replace(value);
把value
中的${catalina.base}
和${catalina.home}
替换为实际的字符串。
中间的一段代码是根据value
分割的每个路径各自生成一个Repository
,添加到repositories
里。
最后调用
return ClassLoaderFactory.createClassLoader(repositories, parent);
其中getCanonicalPath
可查阅file的getPath getAbsolutePath和getCanonicalPath的不同。
该函数会先解析repositories
,把路径和需要添加的jar包的URL
都保存到set
中。
最后调用
return AccessController.doPrivileged(
new PrivilegedAction() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
其中AccessController.doPrivileged和URLClassLoader可看教程(这两个知识点分别能占据不少学习时间)。
总之,createClassLoader
会根据repositories
找到路径和路径下的jar包,将它们合起来,创建一个基于这些URL的URLClassLoader。
1.2.2 回到initClassLoaders
commonLoader = createClassLoader("common", null);
会将commonLoader
绑定一个URLClassLoader
,而
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
其实都是第一个commonLoader
,因为createClassLoader
内有:
if ((value == null) || (value.equals("")))
return parent;
1.3中间步骤
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
Thread.currentThread().setContextClassLoader(catalinaLoader);
是为了设置该线程的上下文ClassLoader
,以便以后调用Class.forName(string name)
加载时,用给好的catalinaLoader
。SecurityClassLoad.securityClassLoad(catalinaLoader);
是会加载各种需要用到的类,以防以后被安全检查阻止。
1.4 调用Catalina的setParentClassLoader
后面的这些代码就是用反射机制完成以下事情:
- 加载一个
org.apache.catalina.startup.Catalina
对象 - 调用其
public void setParentClassLoader(ClassLoader parentClassLoader)
方法,且参数传入为sharedLoader
2. 获取参数
然后执行以下代码,获取最后一个参数。
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
由于传来的最后一个参数是start
(startup.bat
中传入的),故会执行以下代码,也就是load
和start
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
3. load
就是通过反射调用 catalinaDaemon 对象的 load 方法,catalinaDaemon 对象在上面的 init 方法中已经实例化过了。
4. start
start 方法与 load 方法相似,也是通过反射调用 catalinaDaemon 对象上的 start 方法
总结
1.就是设置"catalina.home"和"catalina.base"环境变量
- 创建URLClassLoader用于加载
lib
下的jar包(源码中lib
是空的,正式软件中lib
有很多需要的jar
包)、预加载各种类。 - 设置那个
URLClassLoader
为各种loader以及线程的ClassLoader - 创建了一个
Catalina
服务器实例,并调用其load
和start
方法。
下面一篇文章将分析 catalinaDaemon 对象中的 load、start 两个方法,里面会涉及一个有趣的话题 —— Digester 的使用。