载入器
载入器是Catalina最重要的组件,servlet容器需要实现一个自定义的类载入器,出于以下原因:
- 将WEB-INF/classes目录下的应用类与更高级别的容器类用不同的classloader隔离开来
- 根据classes文件是否发生变化实现类的自动重新装载
Java的类加载器
类加载器的双亲委托模型
Loader接口
Loader接口指的是Web应用程序载入器,而不仅仅是类载入器。
1 | public interface Loader { |
WebappLoader类
WebappLoader类实现了Loader接口,其实例就是Web应用的载入器。
像其他Catalina组件一样,WebappLoader类也实现了Lifecycle接口,可以由其关联的容器来启动、关闭组件。
此外,WebappLoader类还实现了Runnable接口,可以指定一个线程来不断调用modifed()方法来检查class是否被修改。
当调用WebappLoader类的start()方法时,会完成以下工作:
创建类加载器
WebappLoader类并没有声明setClassLoader()方法来直接设置classloader,而是声明了setLoaderClass()来设置其私有变量loaderClass的值。这个私有变量保存了字符串类型的classloader类全名,其默认值是"org.apache.catalina.loader.WebappClassLoader"
。
WebappLoader类在启动时调用其方法createClassLoader()来创建类加载器。
1 | private WebappClassLoader createClassLoader() |
设置仓库
调用setRepositories()方法,利用classloader.addRepository()将"/WEB-INF/classes"
新增仓库,利用classLoader.setJarPath()将"/WEB-INF/lib"
设置为jar仓库。
设置类路径
调用setClassPath()方法,解析classloader链条中的repository集合,组装classpath信息。
设置访问权限
调用setPermissions()方法为类载入器设置访问相关目录的权限。
开启新线程执行类的重新载入
1 | public void run() { |
循环调用classLoader.modified()方法检查是否被修改。一旦为true,通知context容器去重新载入类。
WebappClassLoader类
WebappClassLoader类的设计方案考虑到了优化和安全两方面,例如:
- 缓存之前已经载入成功或失败的类来提升性能
- 禁止载入指定类(如javax.servlet.Servlet)
- 委托系统类载入器载入指定包下的类(如javax, org.xml.sax等)
类缓存
每个由WebappClassLoader载入的类都被视作“资源”,对应class是org.apache.catalina.loader.ResourceEntry
1 | public class ResourceEntry { |
所有已加载的类都保存在protected HashMap resourceEntries
中,其key就是资源的名称。
所有载入失败的类都保存在protected HashMap notFoundResources
中。
载入类
载入类时,遵循如下规则:
- 先检查本地缓存
- 如果本地缓存中没有,则检查上一层缓存,即调用java.lang.ClassLoader#findLoadedClass(String)方法
- 若2个缓存都没有,则使用系统的类载入器加载,防止Web应用中的类覆盖JEE类
- 若采用了SecurityManager,则检查是否允许载入该类
- 若打开delegate标志位,或待载入的类属于
1
2
3
4
5
6
7
8
9
10
11/**
* Set of package names which are not allowed to be loaded from a webapp
* class loader without delegating first.
*/
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
};
则委托父载入器来加载相关类
- 从仓库中加载类
- 如果delegate关闭,委托父载入器加载类
- 抛出ClassNotFoundException