在Tomcat源码:Bootstrap启动流程文章中简单介绍过Java类加载器和Tomcat的类加载器,可以知道,每一个应用都有一个单独的类加载器来加载位于该应用下的WEB-INF/lib中的jar文件和WEB-INF/classes中的class文件,该应用不可以访问其他路径的class或jar。
Tomcat中实现自定义加载器还有另一个原因,就是为了提供自动重载的功能,如果当WEB-INF/classes目录或WEB-INF/lib目录中的类发生变化时,Web应用会重新加载这些类。
下面来看一下Tomcat中类加载器的类图:
Loader接口
要实现Tomcat的类载入器必须遵守一些规则,例如,Web应用程序必须只能使用WEB-INF/classes目录或WEB-INF/lib目录中的类,不能访问其他路径中的类,即使这些类已经包含在当前Tomcat的JVM的CLASSPATH环境变量中。
Web应用程序的类加载器必须实现org.apache.catalina.Loader
接口,其有一个实现类是org.apache.catalina.loader.WebappLoader
,下面来看一下Loader接口的定义:
|
|
org.apache.catalina.loader.WebappLoader
类作为Loader接口的实现,它的实例使用了org.apache.catalina.loader.WebappClassLoader
作为其类加载器。
WebappLoader类
WebappLoader类同样实现了org.apache.catalina.LifeCycle
接口,可以通过与其相关联的容器来启动或关闭,下面看一下用于WebappLoader启动的startInternal方法代码:
|
|
在startInternal方法中,主要做了以下几种工作:
- 创建类加载器
- 设置类加载器的WebResourceRoot
- 设置类路径
- 设置访问权限
- 启动WebappClassLoader
创建类加载器
在前面介绍的Loader接口中,可以看到声明了getClassLoader()
方法,但其中并没有声明setClassLoader()
方法,是不是就只能使用默认的类加载器呢?
可以看到,在WebappLoader类中有一个属性是loaderClass
,该属性的定义如下:
|
|
它是String类型,默认是ParallelWebappClassLoader
类的全限定名,同时有一个setLoaderClass
方法用来设置该属性:
|
|
可见,默认情况下loaderClass
的值是org.apache.catalina.loader.ParallelWebappClassLoader
,可以通过继承WebappClassLoaderBase
类的方式来实现自己的类加载器,同时调用setLoaderClass
方法来使用自己的类加载器。下面看一下createClassLoader
方法的代码:
|
|
可见该方法返回的类型是WebappClassLoaderBase
类型,所以如果要自定义类加载器的话要继承该类。
设置类路径
通过调用setClassPath
方法来设置类路径,这里会遍历调用classLoader以及其父加载器的repositories,并保存到classpath
变量中,这里先不过多介绍了。
设置访问权限
若使用了安全管理器,则setPermissions
方法会为类加载器设置相关的目录访问权限,例如只能访问WEB-INF/classes和WEB-INF/lib目录,若没有使用安全管理器,则该方法并不做任何处理。
WebappClassLoaderBase类
该类的实例是具体负责类的载入工作的。它继承自java.net.URLClassLoader
类。该类在载入的时候做了优化的方案,它会先缓存已经载入的类用来提升性能,同时,还会缓存载入失败的类,如果再次加载同一个类时,会从缓存中找,如果存在则直接抛出ClassNotFoundException异常,不会再尝试加载该类了。
下面看一下该类中几个重要的方法。
首先看下loadClass方法,这里对代码做了简化,只保留了最核心的功能:
|
|
从代码中可以看到,在载入类时,会执行一下步骤:
- 因为已经载入的类会缓存起来,所以先从缓存中查找;
- 若缓存中没有,则检查parent的缓存,即调用
java.lang.ClassLoader
类中的findLoadedClass()方法; - 若以上两步都没有找到,则使用扩展类加载器进行加载,防止Web应用程序中的类覆盖JavaSE中的类;
- 判断是否需要代理加载,判断依据是若
delegate
或filter(name)
为true,则让parent来加载; - 从当前classLoader中的库加载,如果加载成功则写入缓存中;
- 以上都加载失败,若delegateLoad为
false
,则通过parent代理加载(因为为true
时已经执行过了,所以不用考虑); - 若仍然未找到类,则抛出
ClassNotFoundException
。
其中的filter
方法用来判断要加载的类是否需要被过滤,有一些特殊的包以及子包下的类是不允许被载入进来的,具体可以参考代码。
下面看一下findLoadedClass0
方法的代码:
|
|
该方法从缓存中查找名字为name的类,resourceEntries
是一个ConcurrentHashMap
类型的实例,它保存了加载成功的类,每一个类的信息会封装成一个ResourceEntry
的实例,该类的定义如下:
|
|
loadClass会调用findClass方法,下面是findClass的精简过后的代码,去掉了securityManager和log:
|
|
这里调用了findClassInternal
方法,看下这个方法精简后的代码:
|
|
逻辑还是比较简单的:
- 从缓存中查找,若没有找到执行第2步,找到执行第4步;
- 判断class是否存在,不存在返回null,否则执行第3步;
- 创建一个entry,添加到resourceEntries中;
- 判断entry.loadedClass是否为空,为空返回null;
- 判断class是否存在,不存在返回null;
- 调用defineClass方法加载;
- 设置entry.loadedClass。
本文中介绍了Tomcat类加载器有关的类和方法的介绍,下篇文章会介绍一下具体的使用过程以及Tomcat是如何加载每个Web应用中的类。