本文接下来分析tomcat的类载入器,tomcat需要实现一个自定义的载入器,而不能使用系统类载入器
(1)限制serlvet访问当前运行的java虚拟机中环境变量CLASSPATH指明的路径下的所有类和库,而只允许载入WEB-INF/class目录及其子目录下的类,和从部署的库到WEB-INF/lib目录载入类
(2)提供自动重载的功能,即当WEB-INF/class目录或WEB-INF/lib目录下的类发生变化时,Web应用程序会重新载入这些类
我们先来回顾一下java的类载入器,当我们创建java类的实例时,都必须先将类载入到内存中,java虚拟机使用类载入器来载入需要的类
JVM使用三种类型的类载入器来载入所需要的类,分别为引导类载入器(bootstrap class loader)、扩展类载入器(extensions class loader)和系统类载入器(system class loader)
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
JVM采用一种代理模型的类载入机制,可以解决类载入过程中的安全性问题;每当系统需要载入一个类的时候,会首先调用系统类载入器,而系统类载入器将载入类的任务委派给其父载入器,即扩展类载入器,同样扩展类载入器将载入类的任务委派给其父载入器,即引导类载入器;因此引导类载入器会首先执行载入某个类的任务,如果引导类载入器找不到需要载入的类,那么扩展类载入器尝试载入该类,如果扩展类载入器也找不到该类,就轮到系统类载入器继续执行载入任务。如果系统类载入器还是找不到该类,则会抛出java.lang.ClassNotFoundException异常
Tomcat中的载入器是指Web应用程序载入器,而不仅仅指类载入器,载入器必须实现org.apache.catalina.Loader接口
public
interface
Loader {
public
ClassLoader getClassLoader();
public
Container getContainer();
public
void
setContainer(Container container);
public
DefaultContext getDefaultContext();
public
void
setDefaultContext(DefaultContext defaultContext);
public
boolean
getDelegate();
public
void
setDelegate(
boolean
delegate);
public
String getInfo();
public
boolean
getReloadable();
public
void
setReloadable(
boolean
reloadable);
public
void
addPropertyChangeListener(PropertyChangeListener listener);
public
void
addRepository(String repository);
public
String[] findRepositories();
public
boolean
modified();
public
void
removePropertyChangeListener(PropertyChangeListener listener);
}
下面我们来具体来分析tomcat容器中 Web应用程序载入器的具体实现,即org.apache.catalina.loader.WebappLoader类实现了上面的Loader接口,负责载入Web应用程序中所使用到的类。
WebappLoader类会创建org.apache.catalina.loader.WebappClassLoader类的实例作为其类载入器;
同时WebappLoader类也实现了org.apache.catalina.Lifecycle接口,可以由其相关联的容器来启动或关闭;
此外,WebappLoader类还实现了java.lang.Runnable接口,通过一个线程不断地调用其类载入器的modified()方法。如果modified()方法返回true, WebappLoader的实例会通知其关联的servlet容器(在这里是Context类的实例),然后由Context实例来完成类的重新载入。
当调用WebappLoader类的start()方法时,会完成以下几项重要工作:
(1)创建一个类载入器
(2)设置仓库
(3)设置类路径
(4)设置访问权限
(5)启动一个新线程来支持自动重载
WebappLoader类会调用其私有方法createClassLoader()方法来创建默认的类载入器
/**
* Create associated classLoader.
*/
private
WebappClassLoader createClassLoader()
throws
Exception {
Class clazz
=
Class.forName(loaderClass);
WebappClassLoader classLoader
=
null
;
if
(parentClassLoader ==
null
) {
//
Will cause a ClassCast is the class does not extend WCL, but
//
this is on purpose (the exception will be caught and rethrown)
classLoader =
(WebappClassLoader) clazz.newInstance();
}
else
{
Class[] argTypes
= { ClassLoader.
class
};
Object[] args
=
{ parentClassLoader };
Constructor constr
=
clazz.getConstructor(argTypes);
classLoader
=
(WebappClassLoader) constr.newInstance(args);
}
return
classLoader;
}
String loaderClass 的默认值为org.apache.catalina.loader.WebappClassLoader
启动方法后面的设置仓库、设置类路径、设置访问权限等都与类载入器的初始化相关
WebappLoader类支持自动重载功能,如果WEB-INF/class目录或WEB-INF/lib目录下的某些类被重新编译了,那么这些类会自动重新载入,而无需重启tomcat。WebappLoader类使用一个线程周期性地检查每个资源的时间戳,我们可以调用setCheckInterval()方法设置间隔时间
/**
* The background thread that checks for session timeouts and shutdown.
*/
public
void
run() {
if
(debug >= 1
)
log(
"BACKGROUND THREAD Starting"
);
//
Loop until the termination semaphore is set
while
(!
threadDone) {
//
Wait for our check interval
threadSleep();
if
(!
started)
break
;
try
{
//
Perform our modification check
if
(!
classLoader.modified())
continue
;
}
catch
(Exception e) {
log(sm.getString(
"webappLoader.failModifiedCheck"
), e);
continue
;
}
//
Handle a need for reloading
notifyContext();
break
;
}
if
(debug >= 1
)
log(
"BACKGROUND THREAD Stopping"
);
}
在上面run()方法里面的循环中,首先使线程休眠一段时间,然后调用 WebappLoader实例的类载入器的 modified()方法检查已经载入的类是否被修改,肉没有修改则重新执行循环;否则调用私有方法notifyContext(),通知与WebappLoader实例相关联的Context容器重新载入相关类
/**
* Notify our Context that a reload is appropriate.
*/
private
void
notifyContext() {
WebappContextNotifier notifier
=
new
WebappContextNotifier();
(
new
Thread(notifier)).start();
}
上面方法中的WebappContextNotifier为内部类,实现了Runnable接口
/**
* Private thread class to notify our associated Context that we have
* recognized the need for a reload.
*/
protected
class
WebappContextNotifier
implements
Runnable {
/**
* Perform the requested notification.
*/
public
void
run() {
((Context) container).reload();
}
}
下面我们来分析web应用程序中负责载入类的类载入器org.apache.catalina.loader.WebappClassLoader,该类继承自java.net.URLClassLoader,同时实现了org.apache.catalina.loader.Reloader接口
public
interface
Reloader {
public
void
addRepository(String repository);
public
String[] findRepositories();
public
boolean
modified();
}
该接口用来与仓库操作相关,同时检查web应用程序中的某个servlet或相关的类是否被修改
每个由WebappClassLoader载入的类,都视为资源,由org.apache.catalina.loader.ResourceEntry类的实例表示,存储了类的相关信息
public
class
ResourceEntry {
public
long
lastModified = -1
;
public
byte
[] binaryContent =
null
;
public
Class loadedClass =
null
;
public
URL source =
null
;
public
URL codeBase =
null
;
public
Manifest manifest =
null
;
public
Certificate[] certificates =
null
;
}
在WebappClassLoader类中,所有已经缓存的类存储在名为resourceEntries的HashMap类型的变量中,而载入失败的类被存储在另一个名为notFoundResources的HashMap类型的变量中
下面是WebappClassLoader的loadClass()方法的具体实现
public
Class loadClass(String name,
boolean
resolve)
throws
ClassNotFoundException {
if
(debug >= 2
)
log(
"loadClass(" + name + ", " + resolve + ")"
);
Class clazz
=
null
;
//
Don't load classes if class loader is stopped
if
(!
started) {
log(
"Lifecycle error : CL stopped"
);
throw
new
ClassNotFoundException(name);
}
//
(0) Check our previously loaded local class cache
clazz =
findLoadedClass0(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
//
(0.1) Check our previously loaded class cache
clazz =
findLoadedClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
//
(0.2) Try loading the class with the system class loader, to prevent
//
the webapp from overriding J2SE classes
try
{
clazz
=
system.loadClass(name);
if
(clazz !=
null
) {
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
//
Ignore
}
//
(0.5) Permission to access this class when using a SecurityManager
if
(securityManager !=
null
) {
int
i = name.lastIndexOf('.'
);
if
(i >= 0
) {
try
{
securityManager.checkPackageAccess(name.substring(
0
,i));
}
catch
(SecurityException se) {
String error
= "Security Violation, attempt to use " +
"Restricted Class: " +
name;
System.out.println(error);
se.printStackTrace();
log(error);
throw
new
ClassNotFoundException(error);
}
}
}
boolean
delegateLoad = delegate ||
filter(name);
//
(1) Delegate to our parent if requested
if
(delegateLoad) {
if
(debug >= 3
)
log(
" Delegating to parent classloader"
);
ClassLoader loader
=
parent;
if
(loader ==
null
)
loader
=
system;
try
{
clazz
=
loader.loadClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
//
(2) Search local repositories
if
(debug >= 3
)
log(
" Searching local repositories"
);
try
{
clazz
=
findClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from local repository"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
//
(3) Delegate to parent unconditionally
if
(!
delegateLoad) {
if
(debug >= 3
)
log(
" Delegating to parent classloader"
);
ClassLoader loader
=
parent;
if
(loader ==
null
)
loader
=
system;
try
{
clazz
=
loader.loadClass(name);
if
(clazz !=
null
) {
if
(debug >= 3
)
log(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
//
This class was not found
throw
new
ClassNotFoundException(name);
}
在WebappClassLoader实例载入类时,首先是检查缓存,然后再载入指定类(如果设置了安全管理。在代理载入器载入前还要检测类型的安全)
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179 # 163.com ( #改为@ )

