Tomcat从零开始(十)Loader

第十课:

不知不觉就10篇blog了,说实话,我是第一次更这么长时间的Blog。
嗯,今天说说Loader,在以前的课程中,也就是内个能使用最初级的servlet的那一节,我们使用了URLClassLoader加载Servlet,但是这是不科学的,因为如果用系统自带的Loader加载,servlet能访问类库太多就太不安全了。所以我们需要实现自己的一个加载器,那我们首先看看Java的Loader。
JVM在运行的时候,会产生3个classLoader,分别是Boostrap,ClassLoader,Extension Classloader、AppClassLoader。
首先第一个Bootstrap,根据他的意思就知道是启动用的,它是用C++编写的,而且实际上不是classLoader的子类,而是JVM自身实现的。在JVM源码中,我们可以看到用static const char classpathFormat []  数组来存储 所需加载的jar之后就是extension classLoader这个类了,他是负责加载/lib/ext这个文件夹下的类的,或者java.ext.dirs这个系统属性指定(别说不知道是什么,我们在以前的课程中,经常使用的user.dir也是系统属性),因为默认的Ext.dirs对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的JAR类包对所有的JVM和system classloader都是可见的。剩下的一个就是AppClassLoader了,我们用的url内个也是它的儿子,用来加载classpath。ClassLoader加载类用的是靠上级的方式(这个名字有点忘了 AppClassLoader->ExtClassLoader->好像是全局委托吧),就是加载类的时候,上级先找,找不到下级来。所以就是 bootstrap。我们需要思考一下为什么要采用这种模式来处理,因为如果我们编写一个具有危险性的类,注意这个类要和系统存在的类重名,比如java.lang.math,那么当加载的时候这个有危险的类就不会加载到用户,因为bootstrap就不会加载这个类,因为那时候已经有原有的java.lang.Math了。如果不采用,那就直接在用户机器上运行,破坏安全。
这里举一个例子,比如你有两个类,一个放到了ext.dirs中 这个类引用了另外一个类,另外一个放到classpath中,那么 就会报错了。因为第二个类默认用Extloader来搜索,他不会让appLoader去查找。

妥了,那现在说说Tomcat为什么要使用自己的加载器,一个原因在上面已经说了,另外就是需要缓存以前的类,和预先加载一些类做预备。当然,说这么多J2SE的loader是为了让大家不与tomcat的loader混淆, j2se是用来加载类的,而tomcat实际上是一个webApp的Loader,Loader组件必须要实现org.apache.catalina.Loader接口。
Tomcat的Loader其实也是像J2SE中那样的。

首先是Commonclass Loader,主要是加载tomcat 的common下的所有jar和类。他的上级是appclassloader

之后就是server和shared Class Loader,两只个的区别是,前者是加载tomcat的核心类,主要是tomcat的server目录。而后者是加载 web app的类(公共类)。他们的上级是common class loader

再之后就是 webappclass loader了,这个是用来加载每个app的WEB-INF/ classes和lib的。他的上级是shared class loader。但是它的加载 和 之前的不一样,刚才说了Tomcat的loader是有缓存的,如果之前的类没加载,那么就给它 上级来加载(这里就是j2se的加载了),之后,如果加载失败了,那就查找WEB-INF/classes 和 lib下,这个是怎么实现的,我下面就会说。如果再没找到,就给shared class loader。 如果以前加载过,直接从缓存取就行了。

其实这里没什么说的,其实主要理解一下Java的loader 的委托模式。

我们先来看看Loader这个接口,其中我们注意与Repository有关的方法,Repository这个单词的意思是仓库。那么思考一下,这里的repository就是代表类库的意思,add就是添加,find就是查找。那么,我们来看看

Loader的实现类,是如何实现的。这个类在org.apache.catalina.loader.WebappLoader中。

    public void addRepository(String repository) {

        if (debug >= 1)
log(sm.getString("webappLoader.addRepository", repository)); for (int i = 0; i < repositories.length; i++) {
if (repository.equals(repositories[i]))
return;
}
String results[] = new String[repositories.length + 1];
for (int i = 0; i < repositories.length; i++)
results[i] = repositories[i];
results[repositories.length] = repository;
repositories = results; if (started && (classLoader != null)) {
classLoader.addRepository(repository);
setClassPath();
}
//我们可以看到,这里就是一个数组的操作
}

之后我们看看他的Start()方法,这个方法在Lifecycle中说过了。那么,在运行的初期,系统就会默认调用一个方法setRepositories(),这个在start()方法可以自己找到,那我贴一段setRepositories方法。

        // 加载Context的工作目录
File workDir =
(File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
if (workDir == null)
return;
log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath())); DirContext resources = container.getResources(); //这里,就是将这个 classes目录设置到repository中,所以不用看了,我们的apploader
//一定默认加载了 /WEB-INF/classes目录的实现。
String classesPath = "/WEB-INF/classes";
DirContext classes = null;

看到了么,这里就是默认加载classes的地方,其实/lib目录也是开始的时候就被加到repositories中了,这个希望可以自己找到。毕竟读代码也是需要多多练习的~。
我们都知道,这个东西是要跟一个容器关联的,Engine,host,Context,Wrapper这四个容器中,他关联的是context。也不是说必须关联,只是context必须要有这个加载器。另外,我们在用tomcat的时候,会发现另外一个功能,就是当我们更改完servlet之后一般都不需要重启服务器,因为tomcat存在一种reloader机制。这个reloader也是一个接口,每个加载器都必须实现这个,其实就是实现以下reloader接口的 modified方法,来看  是否被更改,更改了就重新加载。
今天先说这么多,明天我会具体的分析一下这个appclassloader这个类。其实我觉得讲了这么多了,这个类其实大家也都能看懂了吧。另外说一下,用下面这段代码,可以看bootstrap在启动的时候到底加载了什么jar。注意!   这里不能用Eclipse去用这段代码。因为eclipse的Access Rule的问题。具体的设置,我会在下面贴图。

public class Test {
public static void main(String args[]){
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toString());
} }
}

首先看一下运行截图。
Tomcat从零开始(十)LoaderTomcat从零开始(十)Loader

 
 
 
 
上一篇:EC读书笔记系列之1:条款1、条款2、条款3


下一篇:phper