tomcat的启动从bootstrap的main方法开始,在main方法中主要是做了三件事,调用init方法初始化自己,调用catalinaDaemon对象
的setAwait方法设置它的await属性为true,最后调用自己的start方法。
首先看看init方法:
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
//初始化三个类加载器
initClassLoaders();
//线程构造后将从它的父线程中继承相应的上下文类加载器. 如果在整个应用中你不做任何特殊设置,
//所有的线程将都以系统类加载器(system classloader)作为自己的线程上下文类加载器.
//这里从Bootstrap构造的线程都会继承改线程的上下文类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
//加载conf下的catalina.properties配置文件下的common.loader和server.loader所指定的类。因为server.loader为空时
//catalinaLoader和commonLoader是指向的同一对象,不为空时commonLoader是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;
}
这里的主要内容就是调用了initClassLoaders();初始化了commonLoader,catalinaLoader,sharedLoader三个类加载器,它们是bootstrap的
成员变量。并通过SecurityClassLoad.securityClassLoad(catalinaLoader)这步代码来加载conf/catalina.properties文件下common.loader,
server.loader,shared.loade三个属性指定的jar包和class。securityClassLoad会通过一系列环境变量替换等处理来找到相关的类并加载它们。
最后一段代码初始化Catalina对象,再设置它的父类加载器为sharedLoader。这里的一些疑问稍后再说。
先看看initClassLoaders()方法:
initClassLoaders源码:
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);//1,创建commonLoader类加载器
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);//2,
sharedLoader = createClassLoader("shared", commonLoader);//3,
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
第一步是通过common关键字初始化了一个commonLoader对象。
进到createClassLoader方法中:
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);
........
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(repositories, parent);
........
return classLoader;
}
这里应该很清楚,CatalinaProperties.getProperty(name + ".loader");就是获取配置文件里的属性的方法。value = replace(value)替换环境变量
等一系列的转换获取了一些类路径的url最后将这些url传给ClassLoaderFactory.createClassLoader方法。CommonclassLoader和其他两个类加载器都
是通过该方法获取的,它们是一个StandardClassLoader实例。StandardClassLoader只是简单的从URLClassLoader继承,并实现了
StandardClassLoaderMBean接口来实现JMX监控。所以可以认为这三个类加载器就是URLClassLoader对象,
createClassLoader(repositories, parent),其实就是调用了URLClassLoader的构造方法,传入类路径的URL集合和父加载器。如果打开
conf/catalina.properties配置文件,server.loader,shared.loade默认为空。从createClassLoader方法中if判断就可以知道
commonLoader == catalinaLoader == sharedLoader。
这里先说一点设置这三个类加载器的用意,以后再用源码证明。commonLoader被用来设置为catalinaLoader和sharedLoader的父加载器。
catalinaLoader用来加载tomcat自身程序所需要的类。sharedLoader用来加载一些webapp目录下的程序共享的class,并被WebappClassLoader
(用户web程序的类加载器)设置为父类(从这里就可以知道web程序的类是怎么实现相互隔离,但又可以共享某些jar包的。因为类加载器在加载
类时会先让父加载器去加载,父加载器找不到的话就自己来加载,在这里sharedLoader和WebappClassLoader都有不同的加载路径,所有的
WebappClassLoader都继承自sharedLoader,所以他们共享了sharedLoader的加载路径)。
再来看看SecurityClassLoad.securityClassLoad(catalinaLoader):
public static void securityClassLoad(ClassLoader loader)
throws Exception {
if( System.getSecurityManager() == null ){
return;
}
loadCorePackage(loader);
loadCoyotePackage(loader);
loadLoaderPackage(loader);
loadRealmPackage(loader);
loadServletsPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadValvesPackage(loader);
loadJavaxPackage(loader);
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
可以进每个方法看下,它是加载了指定的一些类,注意这些类名是指定的,如果你放了自己的jar包在里面,那么它的加载就不是catalinaLoader完成的。
最后就是初始化catalinaDaemon对象了,从最后一段代码可以看出catalinaDaemon被赋值为org.apache.catalina.startup.Catalina的实例,
并且通过反射调用调用了setParentClassLoader方法,参数是catalinaLoader。
这里设置父加载器的用意是什么?下面是这个方法的源码
public void setParentClassLoader(ClassLoader parentClassLoader) {
this.parentClassLoader = parentClassLoader;
}
方法很简单,看来问题不在这。在来看看Catalina的另一个方法:
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null) {
return (parentClassLoader);
}
return ClassLoader.getSystemClassLoader();
}
其实这里parentClassLoader属性不是说加载Catalina的类加载器的父类加载器,它只是一个普通的属性。
在来看看哪里调用了getParentClassLoader,在StandardServer中有这样一个方法:
@Override
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (catalina != null) {
return (catalina.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
然后他的下级组件StandardService:
@Override
public ClassLoader getParentClassLoader() {
if (parentClassLoader != null)
return (parentClassLoader);
if (server != null) {
return (server.getParentClassLoader());
}
return (ClassLoader.getSystemClassLoader());
}
与之对应的都有一个setParentClassLoader方法。
engin容器也有这个方法。最后在WebappLoader中有个如下的方法:
private WebappClassLoader createClassLoader()
throws Exception {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if (parentClassLoader == null) {
parentClassLoader = container.getParentClassLoader();
}
Class<?>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
Constructor<?> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoader) constr.newInstance(args);
return classLoader;
}
在这个方法中调用了org.apache.catalina.loader.WebappClassLoader的构造方法将容器container的parentClassLoader的属性最为classLoader的
父类加载器。这里可以得出结论从 Server 》 service 》 engin(container) 》没一个级别都在尝试设置parentClassLoader的值,而parentClassLoader
将作为WebappClassLoader的父类加载器。也就是说parentClassLoader是针对WebappClassLoader而言的,而每一级别都在尝试修改它。·
现在已经介绍完了bootstrap的init方法,下一步是来介绍setAwait(true)方法的作用。通过反射设置了Catalina的await属性为true。Catalina成功启动后
会通过检查该值来调用它的成员变量server的await方法:
public void start() {
........
if (await) {
await();
stop();
}
}
public void await() {
getServer().await();
}
Catalina的成员变量server是一个StandardServer对象,通过调用该对象的await方法,会让他一直监控server.xml配置文件中的这段配置:
<Server port="8005" shutdown="SHUTDOWN">,所定义的端口发送过来的命令。
最后看看start方法:
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
start最终通过反射调用了catalinaDaemon(也就是Catalina)的start方法。(这里看到的多处位置是通过反射调用catalinaDaemon的方法,
因为catalinaDaemon是一个object的引用指向的Catalina对象所有它只能调用object对象的方法,想要调用Catalina的方法只能通过反射。)
总结:bootstrap的主要任务就是初始化commonLoader,catalinaLoader,sharedLoader,通过catalinaLoader加载所需要的类,
初始化catalinaDaemon对象,调用它的start方法。
疑问:bin/bootstrap.jar 和lib/catalina.jar中都有bootstrap,脚本调用的是bootstrap.jar中的main方法,bootstrap在初始三个类加载器后,
加载类时并没有加载org.apache.catalina.startup包下的类,也就是说虽然两个jar包都含有bootstrap类,但只是系统的类加载器加载了
bin/bootstrap.jar里的bootstrap。而其他lib 目录下的所有的类都是有bootstrap里面的三个类加载器加载的。不清楚为什么这样做。