Java的类载入器



首先说明两个术语


仓库(repository),表示类加载器会在哪里搜索要载入的类;


资源(resource),知道一个类载入器中的DirContext对象,它的文件跟路径指的就是上下文的文件跟路径。


在tomcat中,我们使用了自定义载入器,原因有三:


为了在载入器中指定某些规则;


为了缓存已经载入的类;


为了实现类的预载入;



Loader接口

在加载servlet及相关类的时候,需要遵守一些规则。例如,应用程序中的servelt只能引用部署在web-inf/classes目录下及其子目录下的类,只能访问web-inf/lib目录下的库。


我们在tomcat里说的载入器是指web应用程序载入器,而不仅仅是类载入器。(载入器里面有个类载入器!!!)


载入器应该实现org.apache.catalina.Loader接口,类载入器默认为WebappClassLoader。


package org.apache.catalina;

import java.beans.PropertyChangeListener;

public interface Loader {
public ClassLoader getClassLoader();
public Container getContainer(); //载入器通常与一个context级别的容器相联
public void setContainer(Container container);
public DefaultContext getDefaultContext();
public void setDefaultContext(DefaultContext defaultContext);
public boolean getDelegate(); //Delegate 代表 委托
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(); //如果容器中的一个或多个类被修改了 modified就会返回true
public void removePropertyChangeListener(PropertyChangeListenerlistener);
}


默认情况下,在context的标准实现---org.apache.catalina.core.StandContext中是不支持自动重载的,因此要想开启自动重载功能,就需要在server.xml文件中添加一个Context元素,如下


<Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>


在我们这一节的程序中Catalina 提供了 org.apache.catalina.loader.WebappLoader 作为 Load 接口的实现。WebappLoader 对象包含一个org.apache.catalina.loader.WebappClassLoader 类的实例,该类扩展了Java.netURLClassLoader 类。


当与某个载入器相关联的容器需要使用某个servlet时,或者说就是要调用某个servlet的某个方法时,容器首先会调用载入器的getClassLoader()方法返回类载入器,然后再调用类载入器的loadClass()方法来加载这个servlet类。


uml图如下


How Tomcat Works 读书笔记 八 载入器 上_安全


WebAppLoader类

当webapploader类的start方法启动时,会完成以下几项工作:


1 创建一个类载入器


2 设置仓库


3 设置类路径


4 设置访问权限


5 启动一个新线程来支持自动重载 (在webapploader的run方法中)



创建类载入器

private WebappClassLoader createClassLoader()
throws Exception {

//loadClass为字符串
//默认为 private String loaderClass ="org.apache.catalina.loader.WebappClassLoader";
//可通过setLoadClass方法更改
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;

//在构造WebAppLoader时 又构造函数的参数指定
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;
}


当然我们可以通过setLoadClass来改变类加载器的实现,不过


(WebappClassLoader) constr.newInstance(args);


所以,我们自定义的类也要继承WebappClassLoader。



设置仓库

调用setRepositories,设置WEB-ING/classes目录与WEB-INF/lib目录



设置类路径

和jasper JSP编译器有关



设置访问权限

setPermissions 可以设置类载入器访问相关路径的权限。例如只能访问WEB-INf/classes目录与WEB-INF/lib目录



开启新线程执行类的重新载入



public void run() {

if (debug >= 1)
log("BACKGROUND THREAD Starting");

// Loop until the termination semaphore is set
//整段代码包含在while循环中
//在前面threadDone已经被设置为false
//直到程序关闭时,threadDone才会变为true
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");

}

private void threadSleep() { //让程序休眠一段时间 时间由checkInterval指定
try {
Thread.sleep(checkInterval * 1000L);
} catch (InterruptedException e) {
;
}
}


checkInterval,是每隔若干秒来检查一次容器中的类是否有更改!


一旦有更改classLoader.modified()会返回true,直接调用notifyContext();


private void notifyContext() {
WebappContextNotifier notifier = new WebappContextNotifier();
(new Thread(notifier)).start();
}
protected class WebappContextNotifier implements Runnable {

/**
* Perform the requested notification.
*/
public void run() {
((Context) container).reload();
}
}

WebappContextNotifier是webapploader的内部类。


这里重新启了一个线程,避免了拥塞。



WebappClassLoader类

该类继承自java.net.URLClassLoader类,后者我们在前面章节已经用过;


WebappClassLoader类的设计考虑了安全与优化两个方面。


WebappClassLoader 类不允许一些特定的类被加载。这些类被存储在一个 String 类型的数组中,现在仅仅有一个成员。


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
};

类缓存

为了达到更好的性能,会缓存已经载入的类,这样一来下次在使用这个类的时候,就不用再起加载了。

缓存分两级,一级在本地执行,由webappclassloader实例来管理。


此外,java.lang.ClassLoader类也会维护一个Vector对象,保存已经载入的类,此时缓存由父类管理。



每个由WebappClassLoader载入的类,都视为资源。是org.apache.catalina.loader.ResourceEntry类的实例,里面包含所代表的class文件的字节流,最后一次修改时间等等:


package org.apache.catalina.loader;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;
public class ResourceEntry {
public long lastModifled = -1;
// Binary content of the resource.public byte[] binaryContent = null;
public Class loadedClass = null;
// URL source from where the object was loaded.
public URL source = null;
// URL of the codebase from where the object was loaded.
public URL CodeBase = null;
public Manifest manifest = null;
public Certificate[] certificates = null;
}



所有缓存的源被存放在一个叫做 resourceEntries 的 HashMap 中,键值为载入的资源名称,所有找不到的资源都被放在一个名为 notFoundResources 的 HashMap 中。





至于真正的加载类,我们放在下一节说。