分析tomcat源码首先就要从tomcat的入口(tomcat的main方法)开始,tomcat的main方法在Bootstrap的这个类中,tomcat的启动main方法就在这里边,当jvm加载Bootstrap这个类的时候会执行其静态代码块,在bootstrap中有有一段静态代码块如下(省略了部分代码):
static {
// Will always be non-null 就是配置中的work_directory
String userDir = System.getProperty("user.dir");
// Home first "catalina.home" 在系统变量中有定义
String home = System.getProperty(Globals.CATALINA_HOME_PROP);
File homeFile = null;
//初始化 catalinaHomeFile 在load()中使用
catalinaHomeFile = homeFile;
//将catalina.home的值放入到系统变量中
System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
// Then base catalina.base=catalina-home
String base = System.getProperty(Globals.CATALINA_BASE_PROP);
System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
在这段静态代码中主要时读取了环境变量中的值,这些值在真实的tomcat中都被放置在了启动文件中。并且设置了catalina.home,catalina.base的值放置在环境变量中。
接下来看下main方法中的代码逻辑:
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) {
}
//将bootstrap赋值给daemon 后期将通过daemon调用start方法
daemon = bootstrap;
} else {
//将类加载器放入线程变量 保证上下文的线程变量
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")) {
// 设置 await值,会在start方法中服务器启动完成后来判断是否进入等待状态
// true :后续会去监听8005端口
// false:运行完成后就退出
daemon.setAwait(true);
//执行init方法
daemon.load(args);
//执行start方法
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
}
} catch (Throwable t) {
}
}
在main方法中首先进行了bootstrap中init方法的调用,在这个方法中首先就是初始化了类加载器,关于这个类加载器就是本章节的主要内容,在这个方法中主要初始化了三种类加载器,他们分别是 commonLoader ,catalinaLoader, sharedLoader三种类加载器的加载范围是不同的,commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;他们与jvm的类加载器的关系如图:
这种机制其实是打破了双亲jvm的双亲委派机制,CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。各个不同的应用程序可能需要不同的版本类库,但是双亲委派机制就是为了能够使得类文件在jvm中只加载一份,所以如果继续使用jvm的双亲委派机制显然无法实现tomcat部署多个应用的需求。并且如果实现jsp文件的热部署要通过卸载类加载器的方式进行所以每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件,所以tomcat必须得打破这种双亲委派机制才可以使得程序能够正常运行。
但是值得关注的是在jdk1.6之前对这三个类加载器还是有所区分的,在jdk1.7以后三个类加载器都是相同的,具体的代码逻辑如下:
commonLoader = createClassLoader("common", null);
//没有配置文件,默认为该加载程序-我们可能位于“单例”环境中。
commonLoader=this.getClass().getClassLoader();
//即去catalina.properties中加载server.loader 但是server.load和shared.load的配置皆为空
//此时的catalinaLoader为commonLoader看191行具体实现 此时父类加载器为appclasssloader应用类加载器
catalinaLoader = createClassLoader("server", commonLoader);
//即去catalina.properties中加载shared.loader
sharedLoader = createClassLoader("shared", commonLoader);
此时的server.load和shared.load的配置皆为空所以会在createClassLoader直接将commomLoader返回,代码如下:
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//如果配置为空会返回父类加载器即commonLoader
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals(""))) {
return parent;
}
}
所以此时三种类加载器是没有父子关系的他们的父类加载器都为应用类加载器。但是此时的双亲委派机制还是被打破的,因为每个应用程序都会有自己单独的webAppClassLoader,webAppClassLoader的父类加载器为commomLoader并且在webAppClassLoader重写了loadClass方法,正是通过这种方式打破了双亲委派的模型。