为什么servlet需要实现一个自定义的载入器
因为servlet容器不应该完全信任它正在运行的servlet类。当使用系统载入器载入某个servlet类所使用的全部类,那么servlet就能够访问所有的类,包括当前运行的JVM中环境变量CLASSPATH指明的路径下的所有类和库, 这是非常危险的。servlet应该只允许载入WEB-INF/classes目录及其子目录下的类, 和从部署的库到WEB-INF/lib目录中载入类。
所以,Servlet容器需要实现一个自定义的载入器.
Java的类载入器
jvm使用三种类载入器来载入所需要的类:
- 引导类载入器(bootstrap class loader)
用于引导启动java虚拟机。当调用javax.exe程序时,就会启动引导类载入器。引导类载入器是本地代码实现的(什么意思), 因为它用来载入运行jvm所需要的类, 以及所有的java核心类。 - 扩展类载入器(extension class loader)
负责载入扩展目录中的类,有利于程序开发。Sun公司的jvm的标准扩展目录是/jdk/jre/lib/ext. - 系统类载入器(system class loader)
默认的类载入器,它会搜索在环境变量CLASSPATH中指明的路径和JVR文件。
三种载入器之间是父子继承关系:
graph BT
系统类载入器-->扩展类载入器
扩展类载入器-->引导类载入器
具体的一个类究竟是由哪个加载器加载的呢,答案在于类加载器的代理模型-双亲委派模型, 这种模型可以有效的解决类载入过程中的安全性问题。
当一个类需要载入时,首先调用系统载入器,但它不会立即载入对应的类,而是交给其父载入器-扩展类载入器, 而扩展类载入器又会将载入任务讲给其父载入器-引导载入器.
代理模型的重要用途就是解决类载入过程中的安全性问题。
Tocmat的载入器
Tomcat中的载入器指的是web应用程序载入器,而不仅仅是指类载入器,载入器必须实现org.apache.catalina.Loader接口,在载入器的实现中,会使用一个自定义的类载入器(WebappClassLoader的实例)
Tomcat的载入器挺长会与一个Context级别的servlet容器相关联,Loader接口的getContainer()和setContainer()用来将载入器与某个erlet容器相关联.
Context创建WebappLoader的步骤:
- start()函数中
setLoader(new WebappLoader(getParentClassLoader()));
- setLoader函数中会有
loader.setContainer(this);
设置loader对应的容器; - 设置完容器之后,会调用loader的start()函数。
在Context容器启动时,有如下代码:
public class StandardContext
extends ContainerBase
implements Context {
//......
public synchronized void start() throws LifecycleException {
//......
// 为Context创建配套的类加载器
if (getLoader() == null) { // (2) Required by Manager
if (getPrivileged()) {
if (debug >= 1)
log("Configuring privileged default Loader");
setLoader(new WebappLoader(this.getClass().getClassLoader()));
} else {
if (debug >= 1)
log("Configuring non-privileged default Loader");
setLoader(new WebappLoader(getParentClassLoader()));
}
}
// 设置当前线程的类加载器为刚才生成的
// Binding thread
ClassLoader oldCCL = bindThread();
//...启动context里面的内容
//最终unbindThread(oldCCL); 还原 类加载器
//.......
}
}
上面启动的流程保证了当前Context流程下的类的生成都是由start()里生成的载入器加载的,**start()**之后还原,保证了线程在加载依次加载其他Context容器时,加载Context使用的都是同样的类加载器.
那我们如何在运行不同应用程序的时候,使用Context对应的类加载器呢?
关键在于Host容器的基础阀中的流程:
final class StandardHostValve
extends ValveBase {
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
//...
//匹配当前请求要使用的Context容器,并将线程的类加载器设置为Context容器的类加载器
// Bind the context CL to the current thread
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
//...
}
}
host容器的基础阀中会将线程的类加载器设置为Context容器的载入器里的类加载器,所以后续的类加载相关操作,都是使用Context容器里的类加载器。
WebappLoader类
Context容器里使用的就是这种载入器,也就是web应用程序的载入器。
看看WebappLoader的start()函数
public class WebappLoader
implements Lifecycle, Loader, PropertyChangeListener, Runnable {
public WebappLoader(ClassLoader parent) {
super();
this.parentClassLoader = parent;
}
public void start() throws LifecycleException {
// 主要操作
// 1. 创建一个类加载器;
// 2. 设置仓库;
// 3. 设置类路径;
// 4. 设置访问权限;
// 5. 启动一个县城来支持自动重载
}
}
WebappClassLoader类
WebappClassLoader类的设计考虑了优化和安全两方面。
1. 优化:它会缓存之前已经加载的类来提升性能,此外,它还会缓存加载失败的类的名字, 加载类的效率更高了。
//WebappClassLoader类里的两个缓存类的变量
/**
* The cache of ResourceEntry for classes and resources we have loaded,
* keyed by resource name.
*/
protected HashMap resourceEntries = new HashMap();
/**
* The list of not found resources.
*/
protected HashMap notFoundResources = new HashMap();
此外,java.lang.ClassLoader类会维护一个Vector对象,保存已经载入的类,防止这些类在不使用的时候被当做垃圾而回收。在这种情况下,缓存是由父类来管理的.
2. 安全性:WebappClassLoader不允许载入指定的某些类,这些类的名字存储在一个字符串数组变量triggers中,当前仅有一个元素:
/**
* The set of trigger classes that will cause a proposed repository not
* to be added if this class is visible to the class loader that loaded
* this factory class. Typically, trigger classes will be listed for
* components that have been integrated into the JDK for later versions,
* but where the corresponding JAR files are required to run on
* earlier versions.
*/
private static final String[] triggers = {
"javax.servlet.Servlet" // Servlet API
};
某些特殊的包及其子包下的类也是不允许载入的,也不会将载入类的任务委托给系统类载入器去执行:
/**
* 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
};