载入器
载入需要的servlet。之前我们都使用系统类加载器。所以servlet可以访问所有的类(包括jvm的JDK)。这样是不安全的。
servlet应该只允许载入WEB-INF/classes目录以及子目录下的类。这就需要我们自定义的载入器,定义某种规则来载入类。
Catalina中,载入器是org.apache.catalina.Loader接口的实例。
Tomcat需要实现自定义载入器。1.为了提供自动重载的功能,即当WEB-INF/classes目录或者WEB-INF/lib目录下的类发生变化的时候,web会重新载入这些类。(在实现中,类载入器使用一个额外的线程来不断检查servlet类和其他文件的时间戳) 如果支持自动重载功能,则载入器必须实现org.apache.catalina.loader.Reloader接口。
JAVA类载入器
我们每次创建Java类的实例时,必须将类载入到内存中。JVM使用类载入器来载入需要的类。
一般情况下,类载入器会在一些Java核心类库,以及环境变量CLASSPATH指明的目录搜索相关类。
java类加载器,从上到下,引导类加载器、扩展类加载器、系统类加载器。
引导类加载器,用于引导启动Java虚拟机。当调用javax.exe程序时,就会启动引导类加载器,它是使用本地代码实现的。因为用来载入允许JVM需要的类,和java核心类(java.lang、java.id)。启动类加载器会在rt.jar和i18n.jar等java包搜索要载入的类。
扩展类加载器,负责载入标准扩展目录中的类。我们只需要将jar文件复制到扩展目录,就可以被类加载器搜索到。/jdk/jre/lib/ext
系统类加载器,是默认的类加载器,他会搜索环境变量CLASSPATH中指明的路径和jar文件。
具体我们使用哪个类加载器,根据其代理模型,可以解决类载入过程的安全问题。
就是说,我们在载入一个类的时候,先调用系统类加载器。但不会立即载入这个类。相反,他会将载入类的任务交给父类加载器,扩展类加载器。而扩展类加载器会交给引导类加载器。 所以,引导类加载器,一直是 首先执行类加载任务。如果找到则放返回,如果引导类找不到这个类,则往下继续找。如果到最后系统类加载器都没有找到。则抛出ClassNotFoundException异常。
为什么要执行这一循环呢?
代理模型,最重要的用途就是解决安全问题。(防止我们破坏JAVA核心代码,自己写一个String类。)
比如:我们使用安全管理器来限制某类的访问路径。你编写一个叫做java.lang.Object的类,该类可以访问硬盘任何目录。由于JVM是信任java.lang.Object类的,所以它不会监视这个类的活动。引入代理模型后。
当某个地方调用java.lang.Object类时,先有引导类来加载,这样我们自定义的类,就不会被加载。
Tomcat的类加载器,通过继承java.lang.ClassLoader类,自定义的。
1. 在类载入器定一些规则。 2.为了缓存已经载入的类 3.实现类的预载入,方便使用
Loader接口(org.apache.catalina.Loader)
servlet只能引用部署在WEB-INF/classes目录及其子目录下的类。
servlet只能访问WEB-INF/lib目录下的库。
这里的载入器,是指we应用程序载入器。不仅指类。 载入器的实现中,会使用一个自定义类载入器。它是org.apache.catalina.loader.WebappClassClassLoader的一个实例。
还定义了一些对仓库集合的操作。而上面2个限制,作为仓库添加到载入器中。addRepository()
Tomcat的载入器,通常与一个Context级别的servlet容器相关联。Loader接口的get、setContainter()。如果Context容器中的一个或者多个类改变了,载入器也支持对类的自动重载。这样就不需要重启tomcat了。 Loader使用modified()方法支持类的自动重载,如果重载了,返回true。
但是载入器本身不能自动重载,它会调用context接口的reload()方法来实现。通过get、setReloadable()指明是否支持载入器的自动重载。
<Context path="/myApp" docBase="myApp" debug = "0" reloadable="true">
通过get、setDelegate()指明,是否要委托给一个父类载入器。
每次加载的时候,先调用getLoader()方法,来获取类载入器实例
Reloader接口
为了支持自动重载。重要方法,modified()方法。如果web程序某个servlet被修改,该方法返回true。
WebappLoader类
实现loader接口,其就是web应用程序中的载入器。负责载入web应用程序中所使用的类。
继承了Lifecycle接口,和Runable接口。
这样就可以指定一个线程,不断调用类载入器的modified(),如果返回true,则通知相关的Context容器。然后由容器来完成类载入。
start方法
1.创建一个类载入器
在内部使用一个类载入器。loader接口只有getClassLoader()没有set,那么我们只能使用默认载入器?。
该实例,提供了get、setClassLoader(),来改变私有变量的值。
我们可以继承WebappLoader类,来自定义类载入器。这个类通过私有方法createClassLoader(),使用的是默认类载入器
2.设置仓库
调用setRepositories()将仓库添加到类载入器。WEB-INF/class目录被传入到类载入器的addRepositories(),WEB-INF/lib目录被传入类载入器 的setJarPath()。
3.设置类路径
调用setClassPatch()方法。会在servlet上下文中为Jasper JSP编译器设置一个字符串形式的属性来表明类路径信息。
4.设置访问权限
运行Tomcat时,使用了安全管理器。则setPermissions()会为类载入器设置访问相关目录的权限。
5.启动一个线程,支持自动加载
如果modifited()返回为true,则continue,否则调用notifyContext()。通知与WebappLoader关联的Context容器重新加载相关联。
private void notifyContext(){
WebContextNotifier notifier = new WebContextNotifier ();
(new Thread(notifier)).start();
}
WebContextNotifier是一个内部类
protected class WebContextNotifier implements Runable{
public void run(){
((Context) container).reload();
}
}
负责,web应用程序,载入类的 类载入器。该类,继承java.net.URLClassLoader类。
该类考虑到优化和安全两个方面。
优化: 它会缓存之前已经载入的类来加载。会缓存加载失败的类的名字,当再次请求的时候直接抛异常。它会在仓库和指定的jar文件搜索类。
安全:不允许载入指定的某些类。这些类的名字存储在一个字符串数组变量triggers中。特殊包下的特殊类也不会被载入,也不会将载入类的任务委托给系统类加载器。
private static final String[] packageTriggers = {
"javax","org.xml.sax"
}
类缓存
基于上面说的优化,java.lang.ClassLoader类,会维护一个Vector对象,保存已经载入的类。防止这些类因为不使用,而被当作垃圾回收。
org.apache.catalina.loader.ResourceEntry类,实例会保存 资源的class文件的字节流、最后修改时间、如果来自jar会有,mainfest信息。
已经载入的类会存在,resourceEntries的HashMap中,key就是资源名称。如果失败,存放在notFoundResources的HashMap中。
载入类
1.先检查本地缓存
2.如果没有,则检查上一级缓存 即 调用java.lang.ClassLoader的findLoadedClass()方法
3.如果2个都没有,则使用系统的类加载器进行加载,防止web程序的类覆盖JDK的类。
4.如果启用了SecurityManager,则检查是否载入该类。如果是禁止载入的,则classNotFoundException
5.如果打开标志位delegate,或者待载入的类是属于包触发器,的包名,则调用父类载入器 来载入。如果父类载入器返回null,则使用系统的类载入器。
6.从当前仓库载入类
7.如果当前仓库没有需要的类,且标志位delegate关闭,则使用父类载入器。如果父类载入器返回null,则使用系统的类载入器。