加载器
在How Tomcat Works的前七章中的应用中已经在使用简单的加载器来加载servlet类,这里将会讨论标准web加载器实现,Tomcat为什么没有使用Tomcat默认的加载器呢?因为如果所有的加载器都是通过jvm默认的加载器来加载的,那么每个servlet就都能够操纵classpath下的所有加载类和包,这会带来安全隐患。一个servlet仅仅允许加载WEB-INF/classes和WEB-INF/lib下的类,在servlet容器中的每一个应用(context)都有它自己的加载器,所有加载器都必须实现org.apache.catalina.Loader接口。
同时Tomcat需要支持热加载在应用WEB-INF/classes和WEB-INF/lib下修改的类,这就要求Tomcat加载器有一个线程不断的检测类是否有修改(通过检测每个文件的时间来实现)。所有需要实现热加载功能的加载器都必须实现org.apache.catalina.loader.Reloader接口。
这里会介绍Repository和Resource,repository是加载器搜寻类的地方,resources是一个加载器中DirContext类,指向的是context的基础路径。
Java加载器
每次创建Java类的实例的时候,你必须首先把类加载到内存当中,在jvm用类加载器(loader)加载类,从j2se1.2开始,jvm提供三个加载器bootstray class加载器、extension class加载器和system class加载器,这个三个加载器是一种父子关系,bootstray class 加载器在最上层,system class加载器在最下层。Bootstray 类加载器用来引导jvm,当你运行java.exe的时候开始工作,必须通过本地代码来实现,因为jvm它需要调用一些本地函数,同时它也负责加载所有java核心类,像java.lang和java.io包中的类,同时也会去加载核心库像rt.jar、il8n.jar等等,同时随着jvm版本和操作系统不同而有所不同;Extension class加载器负责加载扩张目录下的类,sun公司的jvm中的扩展类在/jdk/jre/lib/ext;System class加载器是默认的类加载器,负责加载在CLASSPATH环境变量中指定目录下的jar和java类。
在选择使用那个加载器的时候,jvm使用了委派模式,当需要加载一个类的时候,system class加载器并不马上开始工作,它会首先委派它的父加载器extension class加载器,如果父加载器加载失败它才会开始工作,同理extension加载类时也会首先委派给它的父加载器bootray加载器,当bootray加载器加载失败时它才会工作,当所有的加载器都没有找到类时就会跑出java.lang.ClassNotFoundException异常。
之所以使用委派模式是出于安全考虑,避免有人恶意提供一个java.lang.Object,同时java中你可以实现自己的加载器,只需要继承java.lang.ClassLoader即可。
基于以下的原因Tomcat需要自己的加载器:
1、 按照确定的规则加载类;
2、 缓存已经加载的类;
3、 重新加载已经修改的类。
加载器接口
Tomcat的加载器准确的说是web应用加载器,需要保证加载的servlet只能够使用WEB-INF/classes和WEB-INF/lib库中的类。所有的加载器必须实现org.apache.catalina.Loader接口。
Tomcat加载器通常是与Context一起作用的,所以Loader提供了setContainer和getContainer方法,Tomcat需要重新加载已经修改的类,具体加载的工作交给Context去做,但是加载器需要提供一个setReloadable和getReloadable方法,告诉Context是否需要重新加载,这个可以在server.xml文件中进行配置:
<Context path=”/myApp” docBase=”myApp” debug=”0” reloadable=”true”/>
加载器接口及其实现类类图如下:
Reloader接口
为了支持自动重新加载的功能,每个加载器都需要实现org.apache.catalina.loader.Reloader接口,其接口定义如下:
package org.apache.catalina.loader;
public interface Reloader {
public void addRepository(String repository);
public String[] findRepositories();
public boolean modified();
}
最关键的是modified方法,它在一个servlet或者servlet的支持类修改以后会返回true,addRepository方法为加载器添加一个储藏室。
WebappLoader类
根据前面的介绍可知,WebappLoader类实现了Loader接口,代表一个web应用加载应用下的所有类,同时它会创建一个org.apache.catalina.loader.WebappClassLoader实例作为它的类加载器,同时它也实现了org.apahe.catalina.Lifecycle接口,能够通过它所在的容器(往往是Context)来启动或停止它,WebappLoader实现Runable接口,用一个线程不断的调用它的类加载器的modified接口,来检测是否有类做了修改,在WebappLoader启动的时候会做下面几件事情:
1、 创建一个类加载器(class loader);
2、 设置它的储藏室;
3、 设置他的类路径;
4、 设置权限;
5、 启动一个线程进行自动加载。
WebappClassLoader类
org.apache.catalina.loader.WebappClassLoader类是web应用的类加载器,类加载器被优化和安全的,同时它会缓存预加载的类,当需要用到某类的时候它首先会去从缓存中找,没有找到时才会去加载。同时也会缓存查找失败的类,从安全方面考虑,WebappClassLoader不允许加载一些确定的类,这些类被存储在一个叫做triggers的数组当中:
private static final String[] triggers = {
"javax.servlet.Servlet" // Servlet API
};
同时还有一些包下的类也不允许加载:
private static final String[] packageTriggers = {
"javax", // Java extensions
"org.xml.sax", // SAX 1 & 2
"org.w3c.dom", // DOM 1 & 2
"org.apache.xerces", // Xerces 1 & 2
"org.apache.xalan" // Xalan
};
加载器类工作的过程如下:
1、 检测本地缓存(local cache);
2、 如果本地缓存中没有找到,则在缓存中查找,比如调用java.lang.ClassLoader类的findLoadedClass;
3、 如果在两个缓存中都没有找到,这调用系统的加载器,防止覆盖J2EE中的类;
4、 如果使用SecurityManager,如果不允许,抛出ClassNotFoundException;
5、 如果delegate标志打开或者类所在包在触发器中,则调用父加载器,如果没有父加载器则调用系统默认加载器;
6、 从储存器中加载类;
7、 如果当前储存器中类没有找到,并且delegate标志未打开,则调用父类加载器,如父加载器没有找到,则调用系统默认加载器;
8、 如果仍然没有找到类,则抛出ClassNotFoundException异常。