Tomcat服务启动官方的流程文档在源码的webapps/docs/architecture/startup目录下。
当我们执行startup脚本时,其实就是检查了下环境变量和把值传递给同目录下的catalina脚本,具体两个脚本分别做了什么看下面两篇博客就好,我还是不太懂脚本。
startup http://www.cnblogs.com/fantiantian/p/3620022.html
catalina http://www.cnblogs.com/fantiantian/p/3623740.html
而catalina最重要的就是执行了Tomcat的启动引导类Bootstarp的main方法。具体我们看main方法主要做了什么。
从文档中的图来看,Bootstrap主要做了四件事。
一、初始化类加载器
Bootstrap类作为Tomcat的启动引导类,在自身进行静态实例化之后,首先调用的是自身的init()方法,而init()方法中
第一件事情就是就是调用initClassLoaders()方法对类加载器进行初始化。
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
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方法中则是根据传递进来的参数拼接.loader
读取conf目录下的catalina.properties文件中所对应的jar文件的目录而进行加载。
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
下面的createClassLoader的一段代码
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
可以看出createClassLoader方法中读取server 和shared目录为空返回的还是传递进去的commonLoader,所以目前的三个类加载器加载的都是common.loader目录下的jar包
通过debug来看其实common.loader加载的就是当前Tomcat运行环境下Tomcat目录里的lib目录。
至于其他两个为空,应该是历史原因。
所以我去
http://archive.apache.org/dist/tomcat/tomcat-5/v5.5.35/bin/
下载tomcat5.5的包看了一下
common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
Tomcat5到8的目录结构也发生了挺大的变化,现在根目录下的lib就是包含了以前server和shared目录下lib的jar包。。
至于tomcat5的这两个类加载器除了加载这两个目录下的类还干了其他什么就不研究了 。
二、设置Tomcat的主线程类加载器为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
由catalinaLoader负责加载Tomcat的一些核心组件。
为什么需要这样设置呢?
这行代码注释后获取当前线程的类加载器为AppClassLoader,
AppClassLoader 是JVM的一个类加载器,主要负责加载当前classpath下的的jar。
那么用AppClassLoader加载Tomcat的核心包有什么问题呢?
首先我们要知道的是我们在JAVA中我们用包名+类名称来匹配到一个JAVA类,而在jvm中我们是用包名+类名+类加载器id来匹配到一个具体的类。所以说在同一个jvm会存在两个类名甚至包名完全一致的类,不过他们的类加载器是不同的。如果相同的话,classNotFound异常就随之而来。
其次,类加载器大多数会遵循JAVA的双亲委托机制,即在用当前类加载加载类的时候,首先会在自己的缓存中查找,其次不断向上委托父加载器去加载类,避免一个类加载器中会出现两个相同的类。
所以说这样设置主要还是为了避免ClassNotFound异常的出现
三、用catalinaLoader类加载器去加载Tomcat的核心组件
SecurityClassLoad.securityClassLoad(catalinaLoader);
这里涉及到了一个安全管理器的问题。
默认的安全管理器是没有的。所以这个时候这些里面的类是不会进行预加载的。
关于打开SecurityManger的问题。这里不做讨论。
四、调用Catalina的setParentClassLoader 为sharedLoader
这个时候Bootstrap的init方法算是执行完毕了。
daemon.setAwait(true);
daemon.load(args);
daemon.start();
这三个方法都是通过反射调用Catalina中的方法
setAwait只是设置一个标记
load 方法中主要加载了server.xml。用Digester这个玩意来解析xml。
主要是创建并启动了一个server的实例。
然后在start方法里面调用start会直接掉该接口的实现类WebappClassLoaderBase 的start方法
WebResource classes = resources.getResource("/WEB-INF/classes");
if (classes.isDirectory() && classes.canRead()) {
localRepositories.add(classes.getURL());
}
WebResource[] jars = resources.listResources("/WEB-INF/lib");
for (WebResource jar : jars) {
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
localRepositories.add(jar.getURL());
jarModificationTimes.put(
jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
主要是加载了web/inf/lib和jar和web/inf/classess中的类。
到此,Tomcat的启动服务周期结束(当然。。还干了很多事,只不过知识有限。。。)
尴尬。
关于Tomcat是如何避免ClassNotFound异常,参考如下博客
http://demo.netfoucs.com/wangyangzhizhou/article/details/40249221
关于Tomcat其他源码分析请参考微信公众号:tomcat0000(Tomcat那些事)