1. 概述
(1)需要定制加载类的原因:
- 安全性:每个context需要有自己的class loader来加载所有内部的servlet类。而不能用系统自带的system loader来加载,否则违反安全性约束
- 重新加载:当WEB-INF/classes中的类改变时,需要重新加载
2. Java类加载器
(1)JVM每次加载java类,先创建class对象,后载入内存
(2)JVM加载类时,会先搜索core java lib,和CLASSPATH环境变量下所有文件夹
(3)JVM三个类加载器:Bootstrap class loader, extension class loader, system class loader
- Bootstrap class loader使用native code,加载core java类,如java.lang,java.io包,搜索core lib如rt.jar, i18n.jar
- Extension class loader: 加载标准拓展目录, 如 /jdk/jre/lib/ext
- System class loader: 加载CLASSPATH环境变量下的jar和目录
(4)JVM使用双亲委派模型: system->extension->bootstrap。底层先被调用,然后会把加载要求先层级委派给parent。当parent无法加载,再自己加载,都找不到则throw ClassNotFoundException
(5)Tomcat定制化的类加载器的作用:
a. 制定特殊的加载类
b. 缓存已经加载的类
c. 预加载已经准备好使用的类
3. Loader接口
(1)servlet只能由访问WEB-INF/lib目录的权限
(2)Tomcat loader是一个web application loader而不是类加载
(3)定义了仓库集合:一个web应用的WEB-INF/classes和WEB-INF/lib
(4)loader通常绑定给Context,因为需要reload,所以接口有modify方法(完成return true,通过context.reload方法进行调用), 允许重新加载在context配置文件中配置<Context … reloadable="true"/>
(5)loader的实现类也可以设置是否委派给parent class loader
代码:
package com.cisco.tomcat.loader;
import java.beans.PropertyChangeListener;
import com.cisco.tomcat.lifecycle.Container;
public interface Loader {
public ClassLoader getClassLoader();
public Container getContainer();
public void setContainer(Container container);
public DefaultContext getDefaultContext();
public void setDefaultContext(DefaultContext defaultContext);
public String getInfo();
/**
* Reload Servlet class
* @return
*/
public boolean modified();
public boolean getDelegate();
public void setDelegate(boolean delegate);
public boolean getReloadable();
public void setReloadable();
public void addRepository(String repository);
public String[] findRepositories();
public void removeRepository(String repository);
public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener);
public PropertyChangeListener getPropertyChangeListener();
}
(6)类加载UML结构图:
4. Reloader接口:
package com.cisco.tomcat.loader;
public interface Reloader {
public void addRepository(String repository);
public String[] findRepositories();
public boolean modified();
}
(1)start方法会做五个动作:
创建一个class loader->设置repository->设置class path->设置permission->启动auto-load线程
(2)创建一个类加载器
public class WebappLoader implements Loader{
private String loaderClass;
private ClassLoader parentClassLoader = null;
private WebappClassLoader createClassLoader() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<?> clazz = Class.forName(loaderClass);
WebappClassLoader classLoader = null;
if(parentClassLoader == null) {
classLoader = (WebappClassLoader)clazz.newInstance();
}else {
Class<?>[] argType = {ClassLoader.class};
Object[] args = {parentClassLoader};
Constructor<?> constructor = clazz.getConstructor(argType);
classLoader = (WebappClassLoader)constructor.newInstance(args);
}
return classLoader;
}
}
(3)设置repository:
/WEB-INF/classes传给loader.addRepository(), /WEB-INF/lib传给classloader.setJarPath()
(4)设置class path:
setClassPath,Jasper JSP compiler会使用这部分数据
(5)设置permission会把如/WEB-INF/classes和/WEB-INF/lib的权限给classloader,若未设置,立即返回
(6)为Auto-reload启动新线程
@Override
public void run() {
while(!threadDone) {
// 等待检查间隔时间
threadSleep();
if(!started) {
break;
}
// 未结束继续
if(!classLoader.modified()) {
continue;
}
// reload结束则通知context
notifyContext();
break;
}
}
private void threadSleep() {}
private void notifyContext() {
WebappContextNotifier notifier = new WebappContextNotifier();
(new Thread(notifier)).start();
}
protected class WebappContextNotifier implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
((Context) container).reload();
}
}
6. WebappClassLoader:
(1)缓存:使用ResourceEntry来代表先前加载过的class,使用HashMap<ResourceEntry>来缓存加载后的class资源
package com.cisco.tomcat.loader;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;
public class ResourceEntry {
public long lastModified = -1;
// 资源的二进制内容
public byte[] binaryContent = null;
public Class<?> loadedClass = null;
// 资源的加载url地址
public URL source = null;
public URL CodeBase = null;
public Manifest manifest = null;
public Certificate[] certificates = null;
}
(2)加载类的过程
WebappClassLoader使用以下规则:
a. 所有先前使用的类都缓存在本地缓存,所以先查询本地缓存
b. 如果本地缓存没查询到,检查内存(ClassLoader.findLoadedClass)
c. 如果两种内存都未查到,使用system's class loader来阻止覆盖J2EE类
d. 如果使用了SecurityManager,如果这个类不允许使用,抛ClassNotFoundException
e. 如果delegate = on或者这个类属于package trigger,使用parent classloader加载,如果parentclassloader=null使用system class loader
f. 从当前repository加载类
g. 若不再当前repository, 则委派给parent class loader若无则委派给systemclasssloader
h. 若仍找不到,抛ClassNotFoundException
7. Bootstrap启动:
Connector.start()->context.start(),在context.start()中会调用loader.start()